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

@@ -0,0 +1,8 @@
[package]
name = "log-server"
version = "0.1.0"
edition = "2024"
[dependencies]
types = { workspace = true }
server-framework = { workspace = true }

View File

@@ -0,0 +1,8 @@
#![no_std]
pub mod protocol;
pub mod server;
#[cfg(test)]
#[path = "tests/mod.rs"]
mod tests;

View 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,
}
}
}

View 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,
}
}
}
}
}

View File

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

View 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());
}

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