feat: add log-server (ring buffer) and permission-manager (grant/revoke/check) system servers
This commit is contained in:
8
crates/servers/log-server/Cargo.toml
Normal file
8
crates/servers/log-server/Cargo.toml
Normal file
@@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "log-server"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
types = { workspace = true }
|
||||
server-framework = { workspace = true }
|
||||
8
crates/servers/log-server/src/lib.rs
Normal file
8
crates/servers/log-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;
|
||||
99
crates/servers/log-server/src/protocol.rs
Normal file
99
crates/servers/log-server/src/protocol.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use server_framework::{Decode, Encode};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum LogLevel {
|
||||
Error,
|
||||
Warn,
|
||||
Info,
|
||||
Debug,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum LogRequest {
|
||||
Write { level: LogLevel, tag: u16, payload: usize },
|
||||
GetCount,
|
||||
GetEntry { index: usize },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum LogResponse {
|
||||
Ok,
|
||||
Count(usize),
|
||||
Entry { level: LogLevel, tag: u16, payload: usize },
|
||||
NotFound,
|
||||
}
|
||||
|
||||
fn encode_level(level: LogLevel) -> usize {
|
||||
match level {
|
||||
LogLevel::Error => 0,
|
||||
LogLevel::Warn => 1,
|
||||
LogLevel::Info => 2,
|
||||
LogLevel::Debug => 3,
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_level(v: usize) -> Option<LogLevel> {
|
||||
match v {
|
||||
0 => Some(LogLevel::Error),
|
||||
1 => Some(LogLevel::Warn),
|
||||
2 => Some(LogLevel::Info),
|
||||
3 => Some(LogLevel::Debug),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for LogRequest {
|
||||
fn encode(&self) -> [usize; 4] {
|
||||
match self {
|
||||
LogRequest::Write { level, tag, payload } => {
|
||||
[0, encode_level(*level) | ((*tag as usize) << 8), *payload, 0]
|
||||
}
|
||||
LogRequest::GetCount => [1, 0, 0, 0],
|
||||
LogRequest::GetEntry { index } => [2, *index, 0, 0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for LogRequest {
|
||||
fn decode(words: [usize; 4]) -> Option<Self> {
|
||||
match words[0] {
|
||||
0 => {
|
||||
let level = decode_level(words[1] & 0xFF)?;
|
||||
let tag = ((words[1] >> 8) & 0xFFFF) as u16;
|
||||
Some(LogRequest::Write { level, tag, payload: words[2] })
|
||||
}
|
||||
1 => Some(LogRequest::GetCount),
|
||||
2 => Some(LogRequest::GetEntry { index: words[1] }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for LogResponse {
|
||||
fn encode(&self) -> [usize; 4] {
|
||||
match self {
|
||||
LogResponse::Ok => [0, 0, 0, 0],
|
||||
LogResponse::Count(n) => [1, *n, 0, 0],
|
||||
LogResponse::Entry { level, tag, payload } => {
|
||||
[2, encode_level(*level) | ((*tag as usize) << 8), *payload, 0]
|
||||
}
|
||||
LogResponse::NotFound => [3, 0, 0, 0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for LogResponse {
|
||||
fn decode(words: [usize; 4]) -> Option<Self> {
|
||||
match words[0] {
|
||||
0 => Some(LogResponse::Ok),
|
||||
1 => Some(LogResponse::Count(words[1])),
|
||||
2 => {
|
||||
let level = decode_level(words[1] & 0xFF)?;
|
||||
let tag = ((words[1] >> 8) & 0xFFFF) as u16;
|
||||
Some(LogResponse::Entry { level, tag, payload: words[2] })
|
||||
}
|
||||
3 => Some(LogResponse::NotFound),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
68
crates/servers/log-server/src/server.rs
Normal file
68
crates/servers/log-server/src/server.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use server_framework::Server;
|
||||
|
||||
use crate::protocol::{LogLevel, LogRequest, LogResponse};
|
||||
|
||||
const MAX_ENTRIES: usize = 128;
|
||||
|
||||
struct LogEntry {
|
||||
level: LogLevel,
|
||||
tag: u16,
|
||||
payload: usize,
|
||||
}
|
||||
|
||||
pub struct LogServer {
|
||||
entries: [Option<LogEntry>; MAX_ENTRIES],
|
||||
count: usize,
|
||||
write_pos: usize,
|
||||
}
|
||||
|
||||
impl LogServer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
entries: [const { None }; MAX_ENTRIES],
|
||||
count: 0,
|
||||
write_pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn count(&self) -> usize {
|
||||
self.count
|
||||
}
|
||||
}
|
||||
|
||||
impl Server for LogServer {
|
||||
type Request = LogRequest;
|
||||
type Response = LogResponse;
|
||||
|
||||
fn handle(&mut self, request: LogRequest) -> LogResponse {
|
||||
match request {
|
||||
LogRequest::Write { level, tag, payload } => {
|
||||
self.entries[self.write_pos] = Some(LogEntry { level, tag, payload });
|
||||
self.write_pos = (self.write_pos + 1) % MAX_ENTRIES;
|
||||
if self.count < MAX_ENTRIES {
|
||||
self.count += 1;
|
||||
}
|
||||
LogResponse::Ok
|
||||
}
|
||||
LogRequest::GetCount => LogResponse::Count(self.count),
|
||||
LogRequest::GetEntry { index } => {
|
||||
if index >= self.count {
|
||||
return LogResponse::NotFound;
|
||||
}
|
||||
let actual = if self.count < MAX_ENTRIES {
|
||||
index
|
||||
} else {
|
||||
(self.write_pos + index) % MAX_ENTRIES
|
||||
};
|
||||
match &self.entries[actual] {
|
||||
Some(entry) => LogResponse::Entry {
|
||||
level: entry.level,
|
||||
tag: entry.tag,
|
||||
payload: entry.payload,
|
||||
},
|
||||
None => LogResponse::NotFound,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
crates/servers/log-server/src/tests/mod.rs
Normal file
2
crates/servers/log-server/src/tests/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod protocol;
|
||||
mod server;
|
||||
39
crates/servers/log-server/src/tests/protocol.rs
Normal file
39
crates/servers/log-server/src/tests/protocol.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use crate::protocol::*;
|
||||
use server_framework::{Decode, Encode};
|
||||
|
||||
#[test]
|
||||
fn write_request_roundtrip() {
|
||||
let req = LogRequest::Write { level: LogLevel::Error, tag: 42, payload: 0xDEAD };
|
||||
assert_eq!(LogRequest::decode(req.encode()), Some(req));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_count_roundtrip() {
|
||||
let req = LogRequest::GetCount;
|
||||
assert_eq!(LogRequest::decode(req.encode()), Some(req));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_entry_roundtrip() {
|
||||
let req = LogRequest::GetEntry { index: 7 };
|
||||
assert_eq!(LogRequest::decode(req.encode()), Some(req));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entry_response_roundtrip() {
|
||||
let resp = LogResponse::Entry { level: LogLevel::Warn, tag: 10, payload: 123 };
|
||||
assert_eq!(LogResponse::decode(resp.encode()), Some(resp));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn all_levels_roundtrip() {
|
||||
for level in [LogLevel::Error, LogLevel::Warn, LogLevel::Info, LogLevel::Debug] {
|
||||
let req = LogRequest::Write { level, tag: 0, payload: 0 };
|
||||
assert_eq!(LogRequest::decode(req.encode()), Some(req));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_request_returns_none() {
|
||||
assert!(LogRequest::decode([99, 0, 0, 0]).is_none());
|
||||
}
|
||||
43
crates/servers/log-server/src/tests/server.rs
Normal file
43
crates/servers/log-server/src/tests/server.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use crate::protocol::*;
|
||||
use crate::server::LogServer;
|
||||
use server_framework::Server;
|
||||
|
||||
#[test]
|
||||
fn empty_server_has_zero_count() {
|
||||
let mut server = LogServer::new();
|
||||
let resp = server.handle(LogRequest::GetCount);
|
||||
assert_eq!(resp, LogResponse::Count(0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_increments_count() {
|
||||
let mut server = LogServer::new();
|
||||
server.handle(LogRequest::Write { level: LogLevel::Info, tag: 0, payload: 1 });
|
||||
server.handle(LogRequest::Write { level: LogLevel::Info, tag: 0, payload: 2 });
|
||||
let resp = server.handle(LogRequest::GetCount);
|
||||
assert_eq!(resp, LogResponse::Count(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_entry_returns_written_data() {
|
||||
let mut server = LogServer::new();
|
||||
server.handle(LogRequest::Write { level: LogLevel::Error, tag: 5, payload: 0xBEEF });
|
||||
let resp = server.handle(LogRequest::GetEntry { index: 0 });
|
||||
assert_eq!(resp, LogResponse::Entry { level: LogLevel::Error, tag: 5, payload: 0xBEEF });
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_entry_out_of_range_returns_not_found() {
|
||||
let mut server = LogServer::new();
|
||||
let resp = server.handle(LogRequest::GetEntry { index: 0 });
|
||||
assert_eq!(resp, LogResponse::NotFound);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ring_buffer_wraps() {
|
||||
let mut server = LogServer::new();
|
||||
for i in 0..200 {
|
||||
server.handle(LogRequest::Write { level: LogLevel::Debug, tag: 0, payload: i });
|
||||
}
|
||||
assert_eq!(server.count(), 128);
|
||||
}
|
||||
@@ -4,3 +4,5 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
types = { workspace = true }
|
||||
server-framework = { workspace = true }
|
||||
|
||||
@@ -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;
|
||||
|
||||
127
crates/servers/permission-manager/src/protocol.rs
Normal file
127
crates/servers/permission-manager/src/protocol.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
82
crates/servers/permission-manager/src/server.rs
Normal file
82
crates/servers/permission-manager/src/server.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
crates/servers/permission-manager/src/tests/mod.rs
Normal file
2
crates/servers/permission-manager/src/tests/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod protocol;
|
||||
mod server;
|
||||
59
crates/servers/permission-manager/src/tests/protocol.rs
Normal file
59
crates/servers/permission-manager/src/tests/protocol.rs
Normal 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());
|
||||
}
|
||||
85
crates/servers/permission-manager/src/tests/server.rs
Normal file
85
crates/servers/permission-manager/src/tests/server.rs
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user