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.
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
9
crates/servers/session-server/Cargo.toml
Normal file
9
crates/servers/session-server/Cargo.toml
Normal file
@@ -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 }
|
||||
8
crates/servers/session-server/src/lib.rs
Normal file
8
crates/servers/session-server/src/lib.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
#![no_std]
|
||||
|
||||
pub mod protocol;
|
||||
pub mod server;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/mod.rs"]
|
||||
mod tests;
|
||||
77
crates/servers/session-server/src/protocol.rs
Normal file
77
crates/servers/session-server/src/protocol.rs
Normal file
@@ -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<Self> {
|
||||
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<Self> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
115
crates/servers/session-server/src/server.rs
Normal file
115
crates/servers/session-server/src/server.rs
Normal file
@@ -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<AppEntry>; MAX_APPS],
|
||||
app_count: usize,
|
||||
running: [Option<RunningApp>; MAX_APPS],
|
||||
active: Option<AppId>,
|
||||
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<AppId> {
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
2
crates/servers/session-server/src/tests/mod.rs
Normal file
2
crates/servers/session-server/src/tests/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod protocol;
|
||||
mod server;
|
||||
46
crates/servers/session-server/src/tests/protocol.rs
Normal file
46
crates/servers/session-server/src/tests/protocol.rs
Normal file
@@ -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());
|
||||
}
|
||||
78
crates/servers/session-server/src/tests/server.rs
Normal file
78
crates/servers/session-server/src/tests/server.rs
Normal file
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user