feat: add log-server (ring buffer) and permission-manager (grant/revoke/check) system servers

This commit is contained in:
2026-10-18 03:14:48 +02:00
parent 919e918c0a
commit 36cab28506
15 changed files with 631 additions and 12 deletions

View File

@@ -4,3 +4,5 @@ version = "0.1.0"
edition = "2024"
[dependencies]
types = { workspace = true }
server-framework = { workspace = true }

View File

@@ -1,14 +1,8 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#![no_std]
pub mod protocol;
pub mod server;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}
#[path = "tests/mod.rs"]
mod tests;

View File

@@ -0,0 +1,127 @@
use server_framework::{Decode, Encode};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CapabilityKind {
Display,
Input,
AudioPlayback,
AudioCapture,
Camera,
Storage,
Network,
Gps,
Bluetooth,
}
#[derive(Debug, PartialEq)]
pub enum PermissionRequest {
RequestCapability { process: u16, kind: CapabilityKind },
RevokeCapability { process: u16, kind: CapabilityKind },
CheckCapability { process: u16, kind: CapabilityKind },
ListGranted { process: u16 },
}
#[derive(Debug, PartialEq)]
pub enum PermissionResponse {
Granted,
Denied,
Revoked,
HasCapability(bool),
GrantedList(u16),
}
fn encode_kind(kind: CapabilityKind) -> usize {
match kind {
CapabilityKind::Display => 0,
CapabilityKind::Input => 1,
CapabilityKind::AudioPlayback => 2,
CapabilityKind::AudioCapture => 3,
CapabilityKind::Camera => 4,
CapabilityKind::Storage => 5,
CapabilityKind::Network => 6,
CapabilityKind::Gps => 7,
CapabilityKind::Bluetooth => 8,
}
}
fn decode_kind(v: usize) -> Option<CapabilityKind> {
match v {
0 => Some(CapabilityKind::Display),
1 => Some(CapabilityKind::Input),
2 => Some(CapabilityKind::AudioPlayback),
3 => Some(CapabilityKind::AudioCapture),
4 => Some(CapabilityKind::Camera),
5 => Some(CapabilityKind::Storage),
6 => Some(CapabilityKind::Network),
7 => Some(CapabilityKind::Gps),
8 => Some(CapabilityKind::Bluetooth),
_ => None,
}
}
impl Encode for PermissionRequest {
fn encode(&self) -> [usize; 4] {
match self {
PermissionRequest::RequestCapability { process, kind } => {
[0, *process as usize, encode_kind(*kind), 0]
}
PermissionRequest::RevokeCapability { process, kind } => {
[1, *process as usize, encode_kind(*kind), 0]
}
PermissionRequest::CheckCapability { process, kind } => {
[2, *process as usize, encode_kind(*kind), 0]
}
PermissionRequest::ListGranted { process } => {
[3, *process as usize, 0, 0]
}
}
}
}
impl Decode for PermissionRequest {
fn decode(words: [usize; 4]) -> Option<Self> {
match words[0] {
0 => Some(PermissionRequest::RequestCapability {
process: words[1] as u16,
kind: decode_kind(words[2])?,
}),
1 => Some(PermissionRequest::RevokeCapability {
process: words[1] as u16,
kind: decode_kind(words[2])?,
}),
2 => Some(PermissionRequest::CheckCapability {
process: words[1] as u16,
kind: decode_kind(words[2])?,
}),
3 => Some(PermissionRequest::ListGranted {
process: words[1] as u16,
}),
_ => None,
}
}
}
impl Encode for PermissionResponse {
fn encode(&self) -> [usize; 4] {
match self {
PermissionResponse::Granted => [0, 0, 0, 0],
PermissionResponse::Denied => [1, 0, 0, 0],
PermissionResponse::Revoked => [2, 0, 0, 0],
PermissionResponse::HasCapability(has) => [3, *has as usize, 0, 0],
PermissionResponse::GrantedList(bits) => [4, *bits as usize, 0, 0],
}
}
}
impl Decode for PermissionResponse {
fn decode(words: [usize; 4]) -> Option<Self> {
match words[0] {
0 => Some(PermissionResponse::Granted),
1 => Some(PermissionResponse::Denied),
2 => Some(PermissionResponse::Revoked),
3 => Some(PermissionResponse::HasCapability(words[1] != 0)),
4 => Some(PermissionResponse::GrantedList(words[1] as u16)),
_ => None,
}
}
}

View File

@@ -0,0 +1,82 @@
use server_framework::Server;
use crate::protocol::{CapabilityKind, PermissionRequest, PermissionResponse};
const MAX_PROCESSES: usize = 64;
const NUM_KINDS: usize = 9;
pub struct PermissionManager {
grants: [[bool; NUM_KINDS]; MAX_PROCESSES],
}
impl PermissionManager {
pub fn new() -> Self {
Self {
grants: [[false; NUM_KINDS]; MAX_PROCESSES],
}
}
fn kind_index(kind: CapabilityKind) -> usize {
match kind {
CapabilityKind::Display => 0,
CapabilityKind::Input => 1,
CapabilityKind::AudioPlayback => 2,
CapabilityKind::AudioCapture => 3,
CapabilityKind::Camera => 4,
CapabilityKind::Storage => 5,
CapabilityKind::Network => 6,
CapabilityKind::Gps => 7,
CapabilityKind::Bluetooth => 8,
}
}
fn granted_bits(&self, process: u16) -> u16 {
let idx = process as usize;
if idx >= MAX_PROCESSES {
return 0;
}
let mut bits = 0u16;
for i in 0..NUM_KINDS {
if self.grants[idx][i] {
bits |= 1 << i;
}
}
bits
}
}
impl Server for PermissionManager {
type Request = PermissionRequest;
type Response = PermissionResponse;
fn handle(&mut self, request: PermissionRequest) -> PermissionResponse {
match request {
PermissionRequest::RequestCapability { process, kind } => {
let idx = process as usize;
if idx >= MAX_PROCESSES {
return PermissionResponse::Denied;
}
self.grants[idx][Self::kind_index(kind)] = true;
PermissionResponse::Granted
}
PermissionRequest::RevokeCapability { process, kind } => {
let idx = process as usize;
if idx >= MAX_PROCESSES {
return PermissionResponse::Denied;
}
self.grants[idx][Self::kind_index(kind)] = false;
PermissionResponse::Revoked
}
PermissionRequest::CheckCapability { process, kind } => {
let idx = process as usize;
if idx >= MAX_PROCESSES {
return PermissionResponse::HasCapability(false);
}
PermissionResponse::HasCapability(self.grants[idx][Self::kind_index(kind)])
}
PermissionRequest::ListGranted { process } => {
PermissionResponse::GrantedList(self.granted_bits(process))
}
}
}
}

View File

@@ -0,0 +1,2 @@
mod protocol;
mod server;

View File

@@ -0,0 +1,59 @@
use crate::protocol::*;
use server_framework::{Decode, Encode};
#[test]
fn request_capability_roundtrip() {
let req = PermissionRequest::RequestCapability { process: 5, kind: CapabilityKind::Camera };
assert_eq!(PermissionRequest::decode(req.encode()), Some(req));
}
#[test]
fn revoke_capability_roundtrip() {
let req = PermissionRequest::RevokeCapability { process: 3, kind: CapabilityKind::Network };
assert_eq!(PermissionRequest::decode(req.encode()), Some(req));
}
#[test]
fn check_capability_roundtrip() {
let req = PermissionRequest::CheckCapability { process: 1, kind: CapabilityKind::Storage };
assert_eq!(PermissionRequest::decode(req.encode()), Some(req));
}
#[test]
fn list_granted_roundtrip() {
let req = PermissionRequest::ListGranted { process: 7 };
assert_eq!(PermissionRequest::decode(req.encode()), Some(req));
}
#[test]
fn all_kinds_roundtrip() {
let kinds = [
CapabilityKind::Display, CapabilityKind::Input, CapabilityKind::AudioPlayback,
CapabilityKind::AudioCapture, CapabilityKind::Camera, CapabilityKind::Storage,
CapabilityKind::Network, CapabilityKind::Gps, CapabilityKind::Bluetooth,
];
for kind in kinds {
let req = PermissionRequest::RequestCapability { process: 0, kind };
assert_eq!(PermissionRequest::decode(req.encode()), Some(req));
}
}
#[test]
fn response_roundtrips() {
let responses = [
PermissionResponse::Granted,
PermissionResponse::Denied,
PermissionResponse::Revoked,
PermissionResponse::HasCapability(true),
PermissionResponse::HasCapability(false),
PermissionResponse::GrantedList(0b101),
];
for resp in responses {
assert_eq!(PermissionResponse::decode(resp.encode()), Some(resp));
}
}
#[test]
fn invalid_request_returns_none() {
assert!(PermissionRequest::decode([99, 0, 0, 0]).is_none());
}

View File

@@ -0,0 +1,85 @@
use crate::protocol::*;
use crate::server::PermissionManager;
use server_framework::Server;
#[test]
fn new_manager_has_no_grants() {
let mut mgr = PermissionManager::new();
let resp = mgr.handle(PermissionRequest::CheckCapability {
process: 0,
kind: CapabilityKind::Camera,
});
assert_eq!(resp, PermissionResponse::HasCapability(false));
}
#[test]
fn grant_then_check() {
let mut mgr = PermissionManager::new();
mgr.handle(PermissionRequest::RequestCapability {
process: 1,
kind: CapabilityKind::Network,
});
let resp = mgr.handle(PermissionRequest::CheckCapability {
process: 1,
kind: CapabilityKind::Network,
});
assert_eq!(resp, PermissionResponse::HasCapability(true));
}
#[test]
fn grant_does_not_affect_other_process() {
let mut mgr = PermissionManager::new();
mgr.handle(PermissionRequest::RequestCapability {
process: 1,
kind: CapabilityKind::Camera,
});
let resp = mgr.handle(PermissionRequest::CheckCapability {
process: 2,
kind: CapabilityKind::Camera,
});
assert_eq!(resp, PermissionResponse::HasCapability(false));
}
#[test]
fn revoke_removes_grant() {
let mut mgr = PermissionManager::new();
mgr.handle(PermissionRequest::RequestCapability {
process: 0,
kind: CapabilityKind::Storage,
});
mgr.handle(PermissionRequest::RevokeCapability {
process: 0,
kind: CapabilityKind::Storage,
});
let resp = mgr.handle(PermissionRequest::CheckCapability {
process: 0,
kind: CapabilityKind::Storage,
});
assert_eq!(resp, PermissionResponse::HasCapability(false));
}
#[test]
fn list_granted_returns_bitfield() {
let mut mgr = PermissionManager::new();
mgr.handle(PermissionRequest::RequestCapability {
process: 0,
kind: CapabilityKind::Display,
});
mgr.handle(PermissionRequest::RequestCapability {
process: 0,
kind: CapabilityKind::Network,
});
let resp = mgr.handle(PermissionRequest::ListGranted { process: 0 });
// Display=bit 0, Network=bit 6 → 0b1000001 = 65
assert_eq!(resp, PermissionResponse::GrantedList(0b1000001));
}
#[test]
fn out_of_range_process_denied() {
let mut mgr = PermissionManager::new();
let resp = mgr.handle(PermissionRequest::RequestCapability {
process: 999,
kind: CapabilityKind::Camera,
});
assert_eq!(resp, PermissionResponse::Denied);
}