#![no_std]
#![deny(missing_docs)]
#![deny(clippy::all)]
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
extern crate alloc;
pub mod commands;
mod parser;
pub use parser::{Command, ParseError, Parser};
use alloc::string::String;
use alloc::vec::Vec;
use alloc::format;
#[derive(Debug, Clone)]
pub struct ShellConfig {
pub max_history: usize,
pub echo: bool,
pub prompt: &'static str,
}
impl Default for ShellConfig {
fn default() -> Self {
Self {
max_history: 32,
echo: true,
prompt: "ruvix> ",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct MemoryStats {
pub total_bytes: u64,
pub used_bytes: u64,
pub free_bytes: u64,
pub region_count: u32,
pub slab_count: u32,
pub peak_bytes: u64,
}
#[derive(Debug, Clone)]
pub struct TaskInfo {
pub id: u32,
pub name: [u8; 16],
pub state: TaskState,
pub priority: u8,
pub partition: u8,
pub cpu_affinity: u8,
pub cap_count: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum TaskState {
Ready = 0,
Running = 1,
Blocked = 2,
Sleeping = 3,
Exited = 4,
}
#[derive(Debug, Clone)]
pub struct CpuInfo {
pub id: u8,
pub online: bool,
pub is_primary: bool,
pub freq_mhz: u32,
pub load_percent: u8,
}
#[derive(Debug, Clone, Default)]
pub struct QueueStats {
pub queue_count: u32,
pub pending_messages: u64,
pub messages_sent: u64,
pub messages_received: u64,
pub zero_copy_count: u64,
}
#[derive(Debug, Clone, Default)]
pub struct VectorStats {
pub store_count: u32,
pub vector_count: u64,
pub total_dimensions: u32,
pub memory_bytes: u64,
pub reads: u64,
pub writes: u64,
}
#[derive(Debug, Clone, Default)]
pub struct ProofStats {
pub generated: u64,
pub verified: u64,
pub rejected: u64,
pub cache_entries: u32,
pub cache_hits: u64,
pub cache_misses: u64,
pub tier0_count: u64,
pub tier1_count: u64,
pub tier2_count: u64,
}
#[derive(Debug, Clone)]
pub struct CapEntry {
pub handle: u32,
pub object_id: u64,
pub object_type: u8,
pub rights: u32,
pub badge: u64,
pub depth: u8,
}
#[derive(Debug, Clone)]
pub struct WitnessEntry {
pub seq: u64,
pub timestamp_ns: u64,
pub operation: u8,
pub object_id: u64,
pub hash_prefix: [u8; 8],
}
#[derive(Debug, Clone, Default)]
pub struct PerfCounters {
pub syscalls: u64,
pub context_switches: u64,
pub interrupts: u64,
pub page_faults: u64,
pub ipi_sent: u64,
pub cpu_cycles: u64,
}
#[derive(Debug, Clone)]
pub struct KernelInfo {
pub version: &'static str,
pub build_time: &'static str,
pub boot_time_ns: u64,
pub current_time_ns: u64,
pub cpu_count: u8,
}
pub trait ShellBackend {
fn kernel_info(&self) -> KernelInfo;
fn memory_stats(&self) -> MemoryStats;
fn task_list(&self) -> Vec<TaskInfo>;
fn cpu_info(&self) -> Vec<CpuInfo>;
fn queue_stats(&self) -> QueueStats;
fn vector_stats(&self) -> VectorStats;
fn proof_stats(&self) -> ProofStats;
fn capability_entries(&self, task_id: Option<u32>) -> Vec<CapEntry>;
fn witness_entries(&self, count: usize) -> Vec<WitnessEntry>;
fn perf_counters(&self) -> PerfCounters;
fn trace_enabled(&self) -> bool;
fn set_trace(&mut self, enabled: bool);
fn reboot(&mut self);
}
#[derive(Debug)]
pub struct Shell {
config: ShellConfig,
parser: Parser,
history: Vec<String>,
}
impl Shell {
#[must_use]
pub fn new(config: ShellConfig) -> Self {
Self {
config,
parser: Parser::new(),
history: Vec::new(),
}
}
#[must_use]
pub fn default_shell() -> Self {
Self::new(ShellConfig::default())
}
#[must_use]
pub fn prompt(&self) -> &'static str {
self.config.prompt
}
pub fn execute_line<B: ShellBackend>(&mut self, line: &str, backend: &mut B) -> String {
let trimmed = line.trim();
if trimmed.is_empty() {
return String::new();
}
if self.history.len() >= self.config.max_history {
self.history.remove(0);
}
self.history.push(String::from(trimmed));
match self.parser.parse(trimmed) {
Ok(cmd) => self.dispatch_command(cmd, backend),
Err(e) => format!("Error: {}\nType 'help' for available commands.", e),
}
}
fn dispatch_command<B: ShellBackend>(&self, cmd: Command, backend: &mut B) -> String {
match cmd {
Command::Help => commands::help::execute(),
Command::Info => commands::info::execute(backend),
Command::Mem => commands::mem::execute(backend),
Command::Tasks => commands::tasks::execute(backend),
Command::Caps { task_id } => commands::caps::execute(backend, task_id),
Command::Queues => commands::queues::execute(backend),
Command::Vectors => commands::vectors::execute(backend),
Command::Proofs => commands::proofs::execute(backend),
Command::Cpu => commands::cpu::execute(backend),
Command::Witness { count } => commands::witness::execute(backend, count),
Command::Perf => commands::perf::execute(backend),
Command::Trace { enable } => {
if let Some(on) = enable {
backend.set_trace(on);
if on {
String::from("Syscall tracing enabled.")
} else {
String::from("Syscall tracing disabled.")
}
} else {
if backend.trace_enabled() {
String::from("Syscall tracing: ENABLED")
} else {
String::from("Syscall tracing: DISABLED")
}
}
}
Command::Reboot => {
backend.reboot();
String::from("Rebooting...")
}
}
}
#[must_use]
pub fn history(&self) -> &[String] {
&self.history
}
pub fn clear_history(&mut self) {
self.history.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
struct MockBackend {
trace_on: bool,
rebooted: bool,
}
impl MockBackend {
fn new() -> Self {
Self {
trace_on: false,
rebooted: false,
}
}
}
impl ShellBackend for MockBackend {
fn kernel_info(&self) -> KernelInfo {
KernelInfo {
version: "0.1.0-test",
build_time: "2024-01-01T00:00:00Z",
boot_time_ns: 1_000_000_000,
current_time_ns: 2_000_000_000,
cpu_count: 4,
}
}
fn memory_stats(&self) -> MemoryStats {
MemoryStats {
total_bytes: 1024 * 1024 * 1024, used_bytes: 256 * 1024 * 1024, free_bytes: 768 * 1024 * 1024, region_count: 42,
slab_count: 128,
peak_bytes: 512 * 1024 * 1024,
}
}
fn task_list(&self) -> Vec<TaskInfo> {
vec![
TaskInfo {
id: 0,
name: *b"idle\0\0\0\0\0\0\0\0\0\0\0\0",
state: TaskState::Running,
priority: 0,
partition: 0,
cpu_affinity: 0xFF,
cap_count: 4,
},
TaskInfo {
id: 1,
name: *b"init\0\0\0\0\0\0\0\0\0\0\0\0",
state: TaskState::Ready,
priority: 100,
partition: 0,
cpu_affinity: 0xFF,
cap_count: 16,
},
]
}
fn cpu_info(&self) -> Vec<CpuInfo> {
vec![
CpuInfo {
id: 0,
online: true,
is_primary: true,
freq_mhz: 1800,
load_percent: 25,
},
]
}
fn queue_stats(&self) -> QueueStats {
QueueStats {
queue_count: 8,
pending_messages: 12,
messages_sent: 1000,
messages_received: 988,
zero_copy_count: 500,
}
}
fn vector_stats(&self) -> VectorStats {
VectorStats {
store_count: 2,
vector_count: 10000,
total_dimensions: 768,
memory_bytes: 30 * 1024 * 1024,
reads: 50000,
writes: 10000,
}
}
fn proof_stats(&self) -> ProofStats {
ProofStats {
generated: 15000,
verified: 14950,
rejected: 50,
cache_entries: 64,
cache_hits: 12000,
cache_misses: 3000,
tier0_count: 10000,
tier1_count: 4500,
tier2_count: 500,
}
}
fn capability_entries(&self, _task_id: Option<u32>) -> Vec<CapEntry> {
vec![
CapEntry {
handle: 0,
object_id: 0x1000,
object_type: 1,
rights: 0x07,
badge: 0,
depth: 0,
},
]
}
fn witness_entries(&self, count: usize) -> Vec<WitnessEntry> {
(0..count.min(5))
.map(|i| WitnessEntry {
seq: i as u64,
timestamp_ns: 1_000_000_000 + i as u64 * 1000,
operation: 1,
object_id: 0x1000 + i as u64,
hash_prefix: [0xAB; 8],
})
.collect()
}
fn perf_counters(&self) -> PerfCounters {
PerfCounters {
syscalls: 100000,
context_switches: 5000,
interrupts: 250000,
page_faults: 100,
ipi_sent: 500,
cpu_cycles: 1_000_000_000_000,
}
}
fn trace_enabled(&self) -> bool {
self.trace_on
}
fn set_trace(&mut self, enabled: bool) {
self.trace_on = enabled;
}
fn reboot(&mut self) {
self.rebooted = true;
}
}
#[test]
fn test_shell_creation() {
let shell = Shell::default_shell();
assert_eq!(shell.prompt(), "ruvix> ");
}
#[test]
fn test_help_command() {
let mut shell = Shell::default_shell();
let mut backend = MockBackend::new();
let output = shell.execute_line("help", &mut backend);
assert!(output.contains("help"));
assert!(output.contains("info"));
assert!(output.contains("mem"));
}
#[test]
fn test_info_command() {
let mut shell = Shell::default_shell();
let mut backend = MockBackend::new();
let output = shell.execute_line("info", &mut backend);
assert!(output.contains("0.1.0-test"));
assert!(output.contains("CPU"));
}
#[test]
fn test_trace_toggle() {
let mut shell = Shell::default_shell();
let mut backend = MockBackend::new();
let output = shell.execute_line("trace", &mut backend);
assert!(output.contains("DISABLED"));
let output = shell.execute_line("trace on", &mut backend);
assert!(output.contains("enabled"));
assert!(backend.trace_enabled());
let output = shell.execute_line("trace off", &mut backend);
assert!(output.contains("disabled"));
assert!(!backend.trace_enabled());
}
#[test]
fn test_unknown_command() {
let mut shell = Shell::default_shell();
let mut backend = MockBackend::new();
let output = shell.execute_line("unknown_cmd", &mut backend);
assert!(output.contains("Error"));
}
#[test]
fn test_history() {
let mut shell = Shell::default_shell();
let mut backend = MockBackend::new();
shell.execute_line("help", &mut backend);
shell.execute_line("info", &mut backend);
let history = shell.history();
assert_eq!(history.len(), 2);
assert_eq!(history[0], "help");
assert_eq!(history[1], "info");
}
#[test]
fn test_empty_line() {
let mut shell = Shell::default_shell();
let mut backend = MockBackend::new();
let output = shell.execute_line("", &mut backend);
assert!(output.is_empty());
let output = shell.execute_line(" ", &mut backend);
assert!(output.is_empty());
}
}