From 41f7e7db10d9900755113a7883c90ce2e0602295 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 18 Oct 2026 04:09:28 +0200 Subject: [PATCH] feat(session-server): add session server for app launching, remove apps from init manifest Init now only starts servers. The session server holds ProcessCreate cap and launches apps on demand. Snake is no longer in the boot manifest. --- Cargo.toml | 1 + crates/init/src/boot.rs | 8 -- crates/init/src/manifest.rs | 27 ++-- crates/servers/session-server/Cargo.toml | 9 ++ crates/servers/session-server/src/lib.rs | 8 ++ crates/servers/session-server/src/protocol.rs | 77 ++++++++++++ crates/servers/session-server/src/server.rs | 115 ++++++++++++++++++ .../servers/session-server/src/tests/mod.rs | 2 + .../session-server/src/tests/protocol.rs | 46 +++++++ .../session-server/src/tests/server.rs | 78 ++++++++++++ 10 files changed, 347 insertions(+), 24 deletions(-) create mode 100644 crates/servers/session-server/Cargo.toml create mode 100644 crates/servers/session-server/src/lib.rs create mode 100644 crates/servers/session-server/src/protocol.rs create mode 100644 crates/servers/session-server/src/server.rs create mode 100644 crates/servers/session-server/src/tests/mod.rs create mode 100644 crates/servers/session-server/src/tests/protocol.rs create mode 100644 crates/servers/session-server/src/tests/server.rs diff --git a/Cargo.toml b/Cargo.toml index 1ef30e0..83dd806 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ members = [ "crates/servers/permission-manager", "crates/servers/power-server", "crates/servers/print-server", + "crates/servers/session-server", "crates/servers/storage-server", "crates/servers/sync-server", "crates/servers/telephony-server", diff --git a/crates/init/src/boot.rs b/crates/init/src/boot.rs index 88739da..307cbc0 100644 --- a/crates/init/src/boot.rs +++ b/crates/init/src/boot.rs @@ -94,14 +94,6 @@ pub fn boot(manifest: &BootManifest, create_cap: CapSlot) -> BootResult { } } - for app in manifest.apps { - let _pid = syscall::create_process(create_cap.value() as usize); - - for (_server_name, endpoint_name) in app.connects_to { - let _ep = endpoints.find(endpoint_name); - } - } - BootResult { storage_endpoint, create_cap, diff --git a/crates/init/src/manifest.rs b/crates/init/src/manifest.rs index 513583c..57a7dbd 100644 --- a/crates/init/src/manifest.rs +++ b/crates/init/src/manifest.rs @@ -32,19 +32,9 @@ pub struct ServerEntry { pub binary: BinarySource, } -#[derive(Debug)] -pub struct AppEntry { - pub name: &'static str, - pub priority: Priority, - pub memory_pages: usize, - pub connects_to: &'static [(&'static str, &'static str)], - pub binary: BinarySource, -} - #[derive(Debug)] pub struct BootManifest { pub servers: &'static [ServerEntry], - pub apps: &'static [AppEntry], } pub static SNAKE_MANIFEST: BootManifest = BootManifest { @@ -104,14 +94,19 @@ pub static SNAKE_MANIFEST: BootManifest = BootManifest { restartable: true, binary: BinarySource::Storage { path: "/servers/input.elf" }, }, - ], - apps: &[ - AppEntry { - name: "snake", + ServerEntry { + name: "session", priority: Priority::DEFAULT, memory_pages: 64, - connects_to: &[("display", "display"), ("input", "input")], - binary: BinarySource::Storage { path: "/apps/snake.elf" }, + hardware_caps: &[], + endpoints: &[ + EndpointBinding::Own { name: "session" }, + EndpointBinding::ConnectTo { server: "display", endpoint: "display" }, + EndpointBinding::ConnectTo { server: "input", endpoint: "input" }, + EndpointBinding::ConnectTo { server: "storage", endpoint: "storage" }, + ], + restartable: true, + binary: BinarySource::Storage { path: "/servers/session.elf" }, }, ], }; diff --git a/crates/servers/session-server/Cargo.toml b/crates/servers/session-server/Cargo.toml new file mode 100644 index 0000000..d00aad2 --- /dev/null +++ b/crates/servers/session-server/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "session-server" +version = "0.1.0" +edition = "2024" + +[dependencies] +types = { workspace = true } +userspace = { workspace = true } +server-framework = { workspace = true } diff --git a/crates/servers/session-server/src/lib.rs b/crates/servers/session-server/src/lib.rs new file mode 100644 index 0000000..08873ca --- /dev/null +++ b/crates/servers/session-server/src/lib.rs @@ -0,0 +1,8 @@ +#![no_std] + +pub mod protocol; +pub mod server; + +#[cfg(test)] +#[path = "tests/mod.rs"] +mod tests; diff --git a/crates/servers/session-server/src/protocol.rs b/crates/servers/session-server/src/protocol.rs new file mode 100644 index 0000000..8365b9a --- /dev/null +++ b/crates/servers/session-server/src/protocol.rs @@ -0,0 +1,77 @@ +use server_framework::{Decode, Encode}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct AppId(u16); + +impl AppId { + pub const fn new(id: u16) -> Self { Self(id) } + pub const fn raw(&self) -> u16 { self.0 } +} + +#[derive(Debug, PartialEq)] +pub enum SessionRequest { + Launch { app: AppId }, + Kill { app: AppId }, + ListApps, + ActiveApp, +} + +#[derive(Debug, PartialEq)] +pub enum SessionResponse { + Launched { app: AppId }, + Killed, + AppList { count: usize }, + Active { app: AppId }, + NotFound, + Error, +} + +impl Encode for SessionRequest { + fn encode(&self) -> [usize; 4] { + match self { + SessionRequest::Launch { app } => [0, app.raw() as usize, 0, 0], + SessionRequest::Kill { app } => [1, app.raw() as usize, 0, 0], + SessionRequest::ListApps => [2, 0, 0, 0], + SessionRequest::ActiveApp => [3, 0, 0, 0], + } + } +} + +impl Decode for SessionRequest { + fn decode(words: [usize; 4]) -> Option { + match words[0] { + 0 => Some(SessionRequest::Launch { app: AppId::new(words[1] as u16) }), + 1 => Some(SessionRequest::Kill { app: AppId::new(words[1] as u16) }), + 2 => Some(SessionRequest::ListApps), + 3 => Some(SessionRequest::ActiveApp), + _ => None, + } + } +} + +impl Encode for SessionResponse { + fn encode(&self) -> [usize; 4] { + match self { + SessionResponse::Launched { app } => [0, app.raw() as usize, 0, 0], + SessionResponse::Killed => [1, 0, 0, 0], + SessionResponse::AppList { count } => [2, *count, 0, 0], + SessionResponse::Active { app } => [3, app.raw() as usize, 0, 0], + SessionResponse::NotFound => [4, 0, 0, 0], + SessionResponse::Error => [5, 0, 0, 0], + } + } +} + +impl Decode for SessionResponse { + fn decode(words: [usize; 4]) -> Option { + match words[0] { + 0 => Some(SessionResponse::Launched { app: AppId::new(words[1] as u16) }), + 1 => Some(SessionResponse::Killed), + 2 => Some(SessionResponse::AppList { count: words[1] }), + 3 => Some(SessionResponse::Active { app: AppId::new(words[1] as u16) }), + 4 => Some(SessionResponse::NotFound), + 5 => Some(SessionResponse::Error), + _ => None, + } + } +} diff --git a/crates/servers/session-server/src/server.rs b/crates/servers/session-server/src/server.rs new file mode 100644 index 0000000..81a02e9 --- /dev/null +++ b/crates/servers/session-server/src/server.rs @@ -0,0 +1,115 @@ +use server_framework::Server; + +use crate::protocol::{AppId, SessionRequest, SessionResponse}; + +const MAX_APPS: usize = 16; + +#[derive(Clone, Copy)] +pub struct AppEntry { + pub name: &'static str, + pub path: &'static str, + pub registered: bool, +} + +#[derive(Clone, Copy)] +struct RunningApp { + app: AppId, + pid: u16, +} + +pub struct SessionServer { + apps: [Option; MAX_APPS], + app_count: usize, + running: [Option; MAX_APPS], + active: Option, + create_cap: usize, + display_cap: usize, + input_cap: usize, +} + +impl SessionServer { + pub fn new(create_cap: usize, display_cap: usize, input_cap: usize) -> Self { + Self { + apps: [const { None }; MAX_APPS], + app_count: 0, + running: [const { None }; MAX_APPS], + active: None, + create_cap, + display_cap, + input_cap, + } + } + + pub fn register_app(&mut self, name: &'static str, path: &'static str) -> AppId { + let id = AppId::new(self.app_count as u16); + self.apps[self.app_count] = Some(AppEntry { name, path, registered: true }); + self.app_count += 1; + id + } + + pub fn app_count(&self) -> usize { + self.app_count + } + + pub fn active_app(&self) -> Option { + self.active + } + + fn is_running(&self, app: AppId) -> bool { + self.running[app.raw() as usize].is_some() + } + + fn launch_app(&mut self, app: AppId) -> SessionResponse { + let idx = app.raw() as usize; + if idx >= self.app_count { + return SessionResponse::NotFound; + } + + if self.is_running(app) { + return SessionResponse::Error; + } + + let _app_entry = match &self.apps[idx] { + Some(entry) => entry, + None => return SessionResponse::NotFound, + }; + + let pid = userspace::syscall::create_process(self.create_cap); + + self.running[idx] = Some(RunningApp { app, pid: pid as u16 }); + self.active = Some(app); + + SessionResponse::Launched { app } + } + + fn kill_app(&mut self, app: AppId) -> SessionResponse { + let idx = app.raw() as usize; + match self.running[idx].take() { + Some(running) => { + let _ = running.pid; + if self.active == Some(app) { + self.active = None; + } + SessionResponse::Killed + } + None => SessionResponse::NotFound, + } + } +} + +impl Server for SessionServer { + type Request = SessionRequest; + type Response = SessionResponse; + + fn handle(&mut self, request: SessionRequest) -> SessionResponse { + match request { + SessionRequest::Launch { app } => self.launch_app(app), + SessionRequest::Kill { app } => self.kill_app(app), + SessionRequest::ListApps => SessionResponse::AppList { count: self.app_count }, + SessionRequest::ActiveApp => match self.active { + Some(app) => SessionResponse::Active { app }, + None => SessionResponse::NotFound, + }, + } + } +} diff --git a/crates/servers/session-server/src/tests/mod.rs b/crates/servers/session-server/src/tests/mod.rs new file mode 100644 index 0000000..aa77244 --- /dev/null +++ b/crates/servers/session-server/src/tests/mod.rs @@ -0,0 +1,2 @@ +mod protocol; +mod server; diff --git a/crates/servers/session-server/src/tests/protocol.rs b/crates/servers/session-server/src/tests/protocol.rs new file mode 100644 index 0000000..c8e410a --- /dev/null +++ b/crates/servers/session-server/src/tests/protocol.rs @@ -0,0 +1,46 @@ +use crate::protocol::*; +use server_framework::{Decode, Encode}; + +#[test] +fn launch_roundtrip() { + let req = SessionRequest::Launch { app: AppId::new(3) }; + assert_eq!(SessionRequest::decode(req.encode()), Some(req)); +} + +#[test] +fn kill_roundtrip() { + let req = SessionRequest::Kill { app: AppId::new(1) }; + assert_eq!(SessionRequest::decode(req.encode()), Some(req)); +} + +#[test] +fn list_apps_roundtrip() { + let req = SessionRequest::ListApps; + assert_eq!(SessionRequest::decode(req.encode()), Some(req)); +} + +#[test] +fn active_app_roundtrip() { + let req = SessionRequest::ActiveApp; + assert_eq!(SessionRequest::decode(req.encode()), Some(req)); +} + +#[test] +fn all_response_variants() { + let variants = [ + SessionResponse::Launched { app: AppId::new(0) }, + SessionResponse::Killed, + SessionResponse::AppList { count: 5 }, + SessionResponse::Active { app: AppId::new(2) }, + SessionResponse::NotFound, + SessionResponse::Error, + ]; + for resp in variants { + assert_eq!(SessionResponse::decode(resp.encode()), Some(resp)); + } +} + +#[test] +fn invalid_request_returns_none() { + assert!(SessionRequest::decode([99, 0, 0, 0]).is_none()); +} diff --git a/crates/servers/session-server/src/tests/server.rs b/crates/servers/session-server/src/tests/server.rs new file mode 100644 index 0000000..9e5033c --- /dev/null +++ b/crates/servers/session-server/src/tests/server.rs @@ -0,0 +1,78 @@ +use crate::protocol::*; +use crate::server::SessionServer; +use server_framework::Server; + +fn test_server() -> SessionServer { + let mut s = SessionServer::new(0, 0, 0); + s.register_app("snake", "/apps/snake.elf"); + s.register_app("clock", "/apps/clock.elf"); + s +} + +#[test] +fn list_apps_returns_count() { + let mut s = test_server(); + let resp = s.handle(SessionRequest::ListApps); + assert_eq!(resp, SessionResponse::AppList { count: 2 }); +} + +#[test] +fn launch_valid_app() { + let mut s = test_server(); + let resp = s.handle(SessionRequest::Launch { app: AppId::new(0) }); + assert!(matches!(resp, SessionResponse::Launched { .. })); +} + +#[test] +fn launch_sets_active() { + let mut s = test_server(); + s.handle(SessionRequest::Launch { app: AppId::new(0) }); + let resp = s.handle(SessionRequest::ActiveApp); + assert_eq!(resp, SessionResponse::Active { app: AppId::new(0) }); +} + +#[test] +fn launch_invalid_app_returns_not_found() { + let mut s = test_server(); + let resp = s.handle(SessionRequest::Launch { app: AppId::new(99) }); + assert_eq!(resp, SessionResponse::NotFound); +} + +#[test] +fn launch_already_running_returns_error() { + let mut s = test_server(); + s.handle(SessionRequest::Launch { app: AppId::new(0) }); + let resp = s.handle(SessionRequest::Launch { app: AppId::new(0) }); + assert_eq!(resp, SessionResponse::Error); +} + +#[test] +fn kill_running_app() { + let mut s = test_server(); + s.handle(SessionRequest::Launch { app: AppId::new(0) }); + let resp = s.handle(SessionRequest::Kill { app: AppId::new(0) }); + assert_eq!(resp, SessionResponse::Killed); +} + +#[test] +fn kill_clears_active() { + let mut s = test_server(); + s.handle(SessionRequest::Launch { app: AppId::new(0) }); + s.handle(SessionRequest::Kill { app: AppId::new(0) }); + let resp = s.handle(SessionRequest::ActiveApp); + assert_eq!(resp, SessionResponse::NotFound); +} + +#[test] +fn kill_not_running_returns_not_found() { + let mut s = test_server(); + let resp = s.handle(SessionRequest::Kill { app: AppId::new(0) }); + assert_eq!(resp, SessionResponse::NotFound); +} + +#[test] +fn no_active_app_initially() { + let mut s = test_server(); + let resp = s.handle(SessionRequest::ActiveApp); + assert_eq!(resp, SessionResponse::NotFound); +}