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); +}