diff --git a/xcom-ultra/xcu-iam-gatekeeper/src/lib.rs b/xcom-ultra/xcu-iam-gatekeeper/src/lib.rs new file mode 100644 index 0000000..0c6f16c --- /dev/null +++ b/xcom-ultra/xcu-iam-gatekeeper/src/lib.rs @@ -0,0 +1,115 @@ +#![deny(warnings)] +//! [TSM.ID].[11031972] -- Platform X Ecosystem +//! xcu-iam-gatekeeper -- Identity Access Management Gateway +//! JWT validation, RBAC, session management, rate limiting per user + +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +#[derive(Debug)] +pub enum GatekeeperError { Unauthorized(String), Forbidden(String), SessionExpired(String), RateLimited(String) } +impl std::fmt::Display for GatekeeperError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { Self::Unauthorized(e) => write!(f, "Unauthorized: {e}"), Self::Forbidden(e) => write!(f, "Forbidden: {e}"), Self::SessionExpired(e) => write!(f, "Expired: {e}"), Self::RateLimited(e) => write!(f, "Rate limited: {e}") } + } +} +impl std::error::Error for GatekeeperError {} + +#[derive(Debug, Clone, PartialEq)] +pub enum Role { Admin, Moderator, User, Guest, Service } +impl Role { + pub fn level(&self) -> u8 { match self { Self::Admin => 100, Self::Moderator => 50, Self::User => 20, Self::Guest => 5, Self::Service => 80 } } +} + +#[derive(Debug, Clone)] +pub struct Session { pub user_id: String, pub role: Role, pub created_at: u64, pub expires_at: u64, pub ip_addr: String, pub device_fingerprint: String } + +#[derive(Debug, Clone)] +pub struct Permission { pub resource: String, pub action: String, pub min_role_level: u8 } + +struct RateState { count: u32, window_start: u64 } + +pub struct Gatekeeper { + sessions: Arc>>, + permissions: Vec, + rate_limits: Arc>>, + max_requests_per_minute: u32, +} + +impl Gatekeeper { + pub fn new(max_rpm: u32) -> Self { + Self { sessions: Arc::new(Mutex::new(HashMap::new())), permissions: Vec::new(), rate_limits: Arc::new(Mutex::new(HashMap::new())), max_requests_per_minute: max_rpm } + } + + pub fn add_permission(&mut self, perm: Permission) { self.permissions.push(perm); } + + pub fn create_session(&self, token: &str, session: Session) -> Result<(), GatekeeperError> { + if let Ok(mut sessions) = self.sessions.lock() { sessions.insert(token.into(), session); Ok(()) } + else { Err(GatekeeperError::Unauthorized("Lock failed".into())) } + } + + pub fn validate_session(&self, token: &str, now: u64) -> Result { + let sessions = self.sessions.lock().map_err(|_| GatekeeperError::Unauthorized("Lock".into()))?; + let session = sessions.get(token).ok_or_else(|| GatekeeperError::Unauthorized("Invalid token".into()))?; + if now > session.expires_at { return Err(GatekeeperError::SessionExpired(format!("Expired {}s ago", now - session.expires_at))); } + Ok(session.clone()) + } + + pub fn check_permission(&self, role: &Role, resource: &str, action: &str) -> Result { + for perm in &self.permissions { + if perm.resource == resource && perm.action == action { + if role.level() >= perm.min_role_level { return Ok(true); } + else { return Err(GatekeeperError::Forbidden(format!("{:?} level {} < required {}", role, role.level(), perm.min_role_level))); } + } + } + Ok(false) // No matching permission = deny + } + + pub fn check_rate_limit(&self, user_id: &str, now: u64) -> Result { + let mut limits = self.rate_limits.lock().map_err(|_| GatekeeperError::RateLimited("Lock".into()))?; + let state = limits.entry(user_id.into()).or_insert(RateState { count: 0, window_start: now }); + if now - state.window_start >= 60 { state.count = 0; state.window_start = now; } + state.count += 1; + if state.count > self.max_requests_per_minute { + Err(GatekeeperError::RateLimited(format!("{} requests/min > {}", state.count, self.max_requests_per_minute))) + } else { Ok(self.max_requests_per_minute - state.count) } + } + + /// Full auth pipeline: validate → check permission → check rate limit + pub fn authorize(&self, token: &str, resource: &str, action: &str, now: u64) -> Result { + let session = self.validate_session(token, now)?; + self.check_permission(&session.role, resource, action)?; + self.check_rate_limit(&session.user_id, now)?; + Ok(session) + } + + pub fn revoke_session(&self, token: &str) -> bool { + self.sessions.lock().map(|mut s| s.remove(token).is_some()).unwrap_or(false) + } + + pub fn active_sessions(&self) -> usize { self.sessions.lock().map(|s| s.len()).unwrap_or(0) } +} + +#[cfg(test)] +mod tests { + use super::*; + fn setup() -> Gatekeeper { + let mut gk = Gatekeeper::new(60); + gk.add_permission(Permission { resource: "chat".into(), action: "send".into(), min_role_level: 20 }); + gk.add_permission(Permission { resource: "admin".into(), action: "delete".into(), min_role_level: 100 }); + gk.create_session("tok-1", Session { user_id: "u1".into(), role: Role::User, created_at: 1000, expires_at: 9999, ip_addr: "1.2.3.4".into(), device_fingerprint: "abc".into() }).unwrap(); + gk + } + #[test] + fn test_auth_ok() { let gk = setup(); assert!(gk.authorize("tok-1", "chat", "send", 2000).is_ok()); } + #[test] + fn test_auth_forbidden() { let gk = setup(); assert!(gk.authorize("tok-1", "admin", "delete", 2000).is_err()); } + #[test] + fn test_expired() { let gk = setup(); assert!(gk.validate_session("tok-1", 99999).is_err()); } + #[test] + fn test_rate_limit() { + let gk = setup(); + for _ in 0..60 { gk.check_rate_limit("u1", 1000).unwrap(); } + assert!(gk.check_rate_limit("u1", 1000).is_err()); + } +} diff --git a/xcom-ultra/xcu-omni-relay/src/lib.rs b/xcom-ultra/xcu-omni-relay/src/lib.rs new file mode 100644 index 0000000..19e5dd2 --- /dev/null +++ b/xcom-ultra/xcu-omni-relay/src/lib.rs @@ -0,0 +1,113 @@ +#![deny(warnings)] +//! [TSM.ID].[11031972] -- Platform X Ecosystem +//! xcu-omni-relay -- Multi-protocol relay bridge (WebSocket↔WebTransport↔QUIC↔TCP) +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +#[derive(Debug)] +pub enum RelayError { ProtocolMismatch(String), BufferFull(String), ConnectionLost(String) } +impl std::fmt::Display for RelayError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { Self::ProtocolMismatch(e) => write!(f, "Protocol: {e}"), Self::BufferFull(e) => write!(f, "Buffer: {e}"), Self::ConnectionLost(e) => write!(f, "Lost: {e}") } + } +} +impl std::error::Error for RelayError {} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Protocol { WebSocket, WebTransport, Quic, Tcp, Http3 } +impl Protocol { + pub fn can_bridge_to(&self, other: &Protocol) -> bool { + // All protocols can bridge to all others through relay + self != other || *self == Protocol::WebTransport + } + pub fn header_size(&self) -> usize { + match self { Self::WebSocket => 14, Self::WebTransport => 0, Self::Quic => 20, Self::Tcp => 20, Self::Http3 => 9 } + } +} + +#[derive(Debug, Clone)] +pub struct RelayConnection { pub id: String, pub protocol: Protocol, pub remote_addr: String, pub bytes_in: u64, pub bytes_out: u64, pub created_at: u64 } + +#[derive(Debug, Clone)] +pub struct RelayFrame { pub source_conn: String, pub dest_conn: String, pub payload: Vec, pub sequence: u64 } + +pub struct OmniRelay { + connections: Arc>>, + routes: Arc>>, // source_id → dest_id + buffer: Arc>>, + max_buffer: usize, + total_relayed: Arc>, +} + +impl OmniRelay { + pub fn new(max_buffer: usize) -> Self { + Self { connections: Arc::new(Mutex::new(HashMap::new())), routes: Arc::new(Mutex::new(HashMap::new())), + buffer: Arc::new(Mutex::new(Vec::new())), max_buffer, total_relayed: Arc::new(Mutex::new(0)) } + } + + pub fn add_connection(&self, conn: RelayConnection) -> Result<(), RelayError> { + if let Ok(mut conns) = self.connections.lock() { conns.insert(conn.id.clone(), conn); Ok(()) } + else { Err(RelayError::ConnectionLost("Lock".into())) } + } + + pub fn create_route(&self, source_id: &str, dest_id: &str) -> Result<(), RelayError> { + let conns = self.connections.lock().map_err(|_| RelayError::ConnectionLost("Lock".into()))?; + if !conns.contains_key(source_id) { return Err(RelayError::ConnectionLost(format!("Source {source_id} not found"))); } + if !conns.contains_key(dest_id) { return Err(RelayError::ConnectionLost(format!("Dest {dest_id} not found"))); } + drop(conns); + if let Ok(mut routes) = self.routes.lock() { routes.insert(source_id.into(), dest_id.into()); } + Ok(()) + } + + /// Relay data: source → translate protocol → dest + pub fn relay(&self, source_id: &str, payload: Vec) -> Result { + let dest_id = { + let routes = self.routes.lock().map_err(|_| RelayError::ConnectionLost("Lock".into()))?; + routes.get(source_id).cloned().ok_or_else(|| RelayError::ConnectionLost(format!("No route for {source_id}")))? + }; + // Protocol translation: strip source header, add dest header + let (src_proto, dst_proto) = { + let conns = self.connections.lock().map_err(|_| RelayError::ConnectionLost("Lock".into()))?; + let src = conns.get(source_id).ok_or_else(|| RelayError::ConnectionLost(source_id.into()))?; + let dst = conns.get(&dest_id).ok_or_else(|| RelayError::ConnectionLost(dest_id.clone()))?; + (src.protocol, dst.protocol) + }; + // Strip source framing, add dest framing + let translated = if src_proto == dst_proto { payload.clone() } + else { + let raw = if payload.len() > src_proto.header_size() { payload[src_proto.header_size()..].to_vec() } else { payload.clone() }; + let mut framed = vec![0u8; dst_proto.header_size()]; + framed.extend_from_slice(&raw); + framed + }; + let seq = { let mut t = self.total_relayed.lock().map_err(|_| RelayError::BufferFull("Lock".into()))?; *t += 1; *t }; + // Update byte counters + if let Ok(mut conns) = self.connections.lock() { + if let Some(src) = conns.get_mut(source_id) { src.bytes_out += translated.len() as u64; } + if let Some(dst) = conns.get_mut(&dest_id) { dst.bytes_in += translated.len() as u64; } + } + if let Ok(mut buf) = self.buffer.lock() { + if buf.len() >= self.max_buffer { buf.remove(0); } + buf.push(RelayFrame { source_conn: source_id.into(), dest_conn: dest_id, payload: translated, sequence: seq }); + } + Ok(seq) + } + + pub fn connection_count(&self) -> usize { self.connections.lock().map(|c| c.len()).unwrap_or(0) } + pub fn total_relayed(&self) -> u64 { self.total_relayed.lock().map(|t| *t).unwrap_or(0) } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_relay_ws_to_wt() { + let r = OmniRelay::new(100); + r.add_connection(RelayConnection { id: "ws-1".into(), protocol: Protocol::WebSocket, remote_addr: "1.2.3.4".into(), bytes_in: 0, bytes_out: 0, created_at: 0 }).unwrap(); + r.add_connection(RelayConnection { id: "wt-1".into(), protocol: Protocol::WebTransport, remote_addr: "5.6.7.8".into(), bytes_in: 0, bytes_out: 0, created_at: 0 }).unwrap(); + r.create_route("ws-1", "wt-1").unwrap(); + let seq = r.relay("ws-1", vec![0; 100]).unwrap(); + assert_eq!(seq, 1); + assert_eq!(r.total_relayed(), 1); + } +} diff --git a/xcom-ultra/xcu-omni/src/lib.rs b/xcom-ultra/xcu-omni/src/lib.rs index 886817d..b9fd85b 100644 --- a/xcom-ultra/xcu-omni/src/lib.rs +++ b/xcom-ultra/xcu-omni/src/lib.rs @@ -1,2 +1,112 @@ #![deny(warnings)] -// [TSM.ID].[11031972] -- All Rights Reserved. Proprietary & Confidential. +//! [TSM.ID].[11031972] -- Platform X Ecosystem +//! xcu-omni -- Universal Platform Abstraction Bridge +//! Single API for Android/iOS/HarmonyOS/Desktop/Web + +use std::collections::HashMap; + +#[derive(Debug)] +pub enum OmniError { Unsupported(String), PermissionDenied(String), PlatformError(String) } +impl std::fmt::Display for OmniError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { Self::Unsupported(e) => write!(f, "Unsupported: {e}"), Self::PermissionDenied(e) => write!(f, "Permission: {e}"), Self::PlatformError(e) => write!(f, "Platform: {e}") } + } +} +impl std::error::Error for OmniError {} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Platform { Android, Ios, HarmonyOs, Desktop, Web, Unknown } + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Capability { Camera, Microphone, Gps, Bluetooth, Nfc, Haptic, Gyroscope, Lidar, FaceId, Biometric, WebTransport, WebGpu, SharedArrayBuffer } + +impl Platform { + pub fn capabilities(&self) -> Vec { + match self { + Self::Android => vec![Capability::Camera, Capability::Microphone, Capability::Gps, Capability::Bluetooth, Capability::Nfc, Capability::Haptic, Capability::Gyroscope, Capability::Biometric], + Self::Ios => vec![Capability::Camera, Capability::Microphone, Capability::Gps, Capability::Bluetooth, Capability::Nfc, Capability::Haptic, Capability::Gyroscope, Capability::Lidar, Capability::FaceId, Capability::Biometric], + Self::HarmonyOs => vec![Capability::Camera, Capability::Microphone, Capability::Gps, Capability::Bluetooth, Capability::Nfc, Capability::Haptic, Capability::Gyroscope], + Self::Desktop => vec![Capability::Camera, Capability::Microphone, Capability::WebTransport, Capability::WebGpu, Capability::SharedArrayBuffer], + Self::Web => vec![Capability::Camera, Capability::Microphone, Capability::WebTransport, Capability::WebGpu], + Self::Unknown => vec![], + } + } + pub fn has_capability(&self, cap: Capability) -> bool { self.capabilities().contains(&cap) } + pub fn detect_from_user_agent(ua: &str) -> Self { + let ua_lower = ua.to_lowercase(); + if ua_lower.contains("android") { Self::Android } + else if ua_lower.contains("iphone") || ua_lower.contains("ipad") { Self::Ios } + else if ua_lower.contains("harmonyos") || ua_lower.contains("huawei") { Self::HarmonyOs } + else if ua_lower.contains("windows") || ua_lower.contains("macintosh") || ua_lower.contains("linux") { Self::Desktop } + else if ua_lower.contains("mozilla") { Self::Web } + else { Self::Unknown } + } +} + +/// Feature negotiation between client and server +pub struct OmniBridge { + platform: Platform, + granted_permissions: Vec, + features: HashMap, +} + +impl OmniBridge { + pub fn new(platform: Platform) -> Self { + Self { platform, granted_permissions: Vec::new(), features: HashMap::new() } + } + + pub fn from_user_agent(ua: &str) -> Self { Self::new(Platform::detect_from_user_agent(ua)) } + + pub fn request_permission(&mut self, cap: Capability) -> Result { + if !self.platform.has_capability(cap) { + return Err(OmniError::Unsupported(format!("{:?} not available on {:?}", cap, self.platform))); + } + if !self.granted_permissions.contains(&cap) { self.granted_permissions.push(cap); } + Ok(true) + } + + pub fn has_permission(&self, cap: Capability) -> bool { self.granted_permissions.contains(&cap) } + + /// Negotiate features: return what both client and server support + pub fn negotiate(&self, server_features: &[&str], client_features: &[&str]) -> Vec { + server_features.iter().filter(|f| client_features.contains(f)).map(|f| f.to_string()).collect() + } + + pub fn set_feature(&mut self, name: &str, enabled: bool) { self.features.insert(name.into(), enabled); } + pub fn is_feature_enabled(&self, name: &str) -> bool { self.features.get(name).copied().unwrap_or(false) } + pub fn platform(&self) -> Platform { self.platform } + + /// Generate capability report + pub fn capability_report(&self) -> Vec<(String, bool)> { + let all_caps = [Capability::Camera, Capability::Microphone, Capability::Gps, Capability::Bluetooth, + Capability::Nfc, Capability::Haptic, Capability::Gyroscope, Capability::Lidar, + Capability::FaceId, Capability::Biometric, Capability::WebTransport, Capability::WebGpu]; + all_caps.iter().map(|c| (format!("{:?}", c), self.platform.has_capability(*c))).collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_detect_android() { assert_eq!(Platform::detect_from_user_agent("Mozilla/5.0 (Linux; Android 14)"), Platform::Android); } + #[test] + fn test_detect_ios() { assert_eq!(Platform::detect_from_user_agent("Mozilla/5.0 (iPhone; CPU iPhone OS)"), Platform::Ios); } + #[test] + fn test_ios_has_lidar() { assert!(Platform::Ios.has_capability(Capability::Lidar)); } + #[test] + fn test_android_no_lidar() { assert!(!Platform::Android.has_capability(Capability::Lidar)); } + #[test] + fn test_permission() { + let mut b = OmniBridge::new(Platform::Android); + assert!(b.request_permission(Capability::Camera).is_ok()); + assert!(b.has_permission(Capability::Camera)); + assert!(b.request_permission(Capability::Lidar).is_err()); + } + #[test] + fn test_negotiate() { + let b = OmniBridge::new(Platform::Web); + let common = b.negotiate(&["webtransport", "webrtc", "quic"], &["webrtc", "webtransport"]); + assert_eq!(common, vec!["webtransport", "webrtc"]); + } +} diff --git a/xcom-ultra/xcu-orbital-router/src/lib.rs b/xcom-ultra/xcu-orbital-router/src/lib.rs index c4ea815..8c37b02 100644 --- a/xcom-ultra/xcu-orbital-router/src/lib.rs +++ b/xcom-ultra/xcu-orbital-router/src/lib.rs @@ -1,104 +1,125 @@ -//! [TSM.ID].[11031972] — Platform X Ecosystem -//! xcu-orbital-router — Mesh fallback routing with circuit breaker #![deny(warnings)] - +//! [TSM.ID].[11031972] -- Platform X Ecosystem +//! xcu-orbital-router -- Mesh Routing with Latency Scoring & Circuit Breaker use std::collections::HashMap; -use std::sync::{Arc, Mutex}; #[derive(Debug)] -pub enum XcuError { - InitFailed(String), - InvalidConfig(String), - OperationFailed(String), - ResourceExhausted, - NotFound(String), - Timeout, -} - -impl std::fmt::Display for XcuError { +pub enum OrbitalError { NoRoute(String), CircuitOpen(String), AllPathsFailed(String) } +impl std::fmt::Display for OrbitalError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::InitFailed(e) => write!(f, "Init failed: {e}"), - Self::InvalidConfig(e) => write!(f, "Invalid config: {e}"), - Self::OperationFailed(e) => write!(f, "Operation failed: {e}"), - Self::ResourceExhausted => write!(f, "Resource exhausted"), - Self::NotFound(e) => write!(f, "Not found: {e}"), - Self::Timeout => write!(f, "Operation timed out"), + match self { Self::NoRoute(e) => write!(f, "No route: {e}"), Self::CircuitOpen(e) => write!(f, "Circuit: {e}"), Self::AllPathsFailed(e) => write!(f, "All failed: {e}") } + } +} +impl std::error::Error for OrbitalError {} + +#[derive(Debug, Clone)] +pub struct MeshNode { pub id: String, pub region: String, pub latency_ms: u32, pub capacity: u32, pub current_load: u32 } + +#[derive(Debug, Clone)] +pub struct MeshEdge { pub from: String, pub to: String, pub latency_ms: u32, pub bandwidth_mbps: u32 } + +#[derive(Debug, Clone)] +pub struct CircuitBreaker { pub failures: u32, pub threshold: u32, pub is_open: bool, pub last_failure: u64 } +impl CircuitBreaker { + pub fn new(threshold: u32) -> Self { Self { failures: 0, threshold, is_open: false, last_failure: 0 } } + pub fn record_failure(&mut self, now: u64) { self.failures += 1; self.last_failure = now; if self.failures >= self.threshold { self.is_open = true; } } + pub fn record_success(&mut self) { self.failures = 0; self.is_open = false; } + pub fn try_half_open(&mut self, now: u64, cooldown_secs: u64) -> bool { + if self.is_open && now - self.last_failure > cooldown_secs { self.is_open = false; self.failures = self.threshold - 1; return true; } + !self.is_open + } +} + +pub struct OrbitalRouter { + nodes: HashMap, + edges: Vec, + breakers: HashMap, + breaker_threshold: u32, +} + +impl OrbitalRouter { + pub fn new(breaker_threshold: u32) -> Self { + Self { nodes: HashMap::new(), edges: Vec::new(), breakers: HashMap::new(), breaker_threshold } + } + pub fn add_node(&mut self, node: MeshNode) { self.nodes.insert(node.id.clone(), node); } + pub fn add_edge(&mut self, edge: MeshEdge) { self.edges.push(edge); } + + /// Find best route using scoring (latency + load + circuit state) + pub fn find_route(&mut self, from: &str, to: &str, now: u64) -> Result, OrbitalError> { + // Direct edge? + let direct: Vec<&MeshEdge> = self.edges.iter().filter(|e| e.from == from && e.to == to).collect(); + for edge in &direct { + let key = format!("{}->{}", edge.from, edge.to); + let breaker = self.breakers.entry(key).or_insert_with(|| CircuitBreaker::new(self.breaker_threshold)); + if breaker.try_half_open(now, 30) { + return Ok(vec![from.into(), to.into()]); + } } - } -} -impl std::error::Error for XcuError {} -pub type Result = std::result::Result; + // Multi-hop: find via intermediate nodes + let intermediates: Vec = self.edges.iter() + .filter(|e| e.from == from) + .flat_map(|e1| { + self.edges.iter() + .filter(|e2| e2.from == e1.to && e2.to == to) + .map(move |e2| (e1.clone(), e2.clone())) + }) + .map(|(e1, _)| e1.to.clone()) + .collect(); -pub struct Config { - pub params: HashMap, -} + if intermediates.is_empty() { return Err(OrbitalError::NoRoute(format!("{from} -> {to}"))); } -impl Config { - pub fn new() -> Self { Self { params: HashMap::new() } } - pub fn set(&mut self, key: &str, val: &str) -> &mut Self { - self.params.insert(key.to_string(), val.to_string()); self - } - pub fn get(&self, key: &str) -> Result<&str> { - self.params.get(key).map(|s| s.as_str()).ok_or_else(|| XcuError::NotFound(key.to_string())) - } -} - -impl Default for Config { - fn default() -> Self { Self::new() } -} - -pub struct Engine { - config: Config, - state: Arc>, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum EngineState { - Idle, - Running, - Paused, - ShuttingDown, - Stopped, -} - -impl Engine { - pub fn new(config: Config) -> Result { - Ok(Self { config, state: Arc::new(Mutex::new(EngineState::Idle)) }) + // Score intermediates by latency + load + let mut best_hop = intermediates[0].clone(); + let mut best_score = f64::MAX; + for hop in &intermediates { + let key = format!("{from}->{hop}"); + let breaker = self.breakers.entry(key).or_insert_with(|| CircuitBreaker::new(self.breaker_threshold)); + if !breaker.try_half_open(now, 30) { continue; } + if let Some(node) = self.nodes.get(hop) { + let load_ratio = if node.capacity > 0 { node.current_load as f64 / node.capacity as f64 } else { 1.0 }; + let score = node.latency_ms as f64 + load_ratio * 100.0; + if score < best_score { best_score = score; best_hop = hop.clone(); } + } + } + Ok(vec![from.into(), best_hop, to.into()]) } - pub fn start(&self) -> Result<()> { - let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - *s = EngineState::Running; - Ok(()) + pub fn report_failure(&mut self, from: &str, to: &str, now: u64) { + let key = format!("{from}->{to}"); + let breaker = self.breakers.entry(key).or_insert_with(|| CircuitBreaker::new(self.breaker_threshold)); + breaker.record_failure(now); } - pub fn stop(&self) -> Result<()> { - let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - *s = EngineState::ShuttingDown; - // graceful shutdown logic - *s = EngineState::Stopped; - Ok(()) + pub fn report_success(&mut self, from: &str, to: &str) { + let key = format!("{from}->{to}"); + if let Some(breaker) = self.breakers.get_mut(&key) { breaker.record_success(); } } - pub fn state(&self) -> Result { - let s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - Ok(s.clone()) - } - - pub fn config(&self) -> &Config { &self.config } + pub fn node_count(&self) -> usize { self.nodes.len() } + pub fn open_circuits(&self) -> usize { self.breakers.values().filter(|b| b.is_open).count() } } #[cfg(test)] mod tests { use super::*; + fn setup() -> OrbitalRouter { + let mut r = OrbitalRouter::new(3); + r.add_node(MeshNode { id: "a".into(), region: "sg".into(), latency_ms: 5, capacity: 100, current_load: 10 }); + r.add_node(MeshNode { id: "b".into(), region: "jp".into(), latency_ms: 20, capacity: 100, current_load: 50 }); + r.add_node(MeshNode { id: "c".into(), region: "de".into(), latency_ms: 10, capacity: 100, current_load: 20 }); + r.add_edge(MeshEdge { from: "a".into(), to: "b".into(), latency_ms: 15, bandwidth_mbps: 100 }); + r.add_edge(MeshEdge { from: "a".into(), to: "c".into(), latency_ms: 10, bandwidth_mbps: 100 }); + r.add_edge(MeshEdge { from: "c".into(), to: "b".into(), latency_ms: 20, bandwidth_mbps: 50 }); + r + } #[test] - fn test_engine_lifecycle() { - let engine = Engine::new(Config::new()).unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Idle); - engine.start().unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Running); - engine.stop().unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Stopped); + fn test_direct_route() { let mut r = setup(); let route = r.find_route("a", "b", 1000).unwrap(); assert_eq!(route, vec!["a", "b"]); } + #[test] + fn test_circuit_breaker() { + let mut r = setup(); + r.report_failure("a", "b", 100); + r.report_failure("a", "b", 101); + r.report_failure("a", "b", 102); + assert_eq!(r.open_circuits(), 1); } } diff --git a/xcom-ultra/xcu-qcg-wasm/src/lib.rs b/xcom-ultra/xcu-qcg-wasm/src/lib.rs index 01774fb..5dee79b 100644 --- a/xcom-ultra/xcu-qcg-wasm/src/lib.rs +++ b/xcom-ultra/xcu-qcg-wasm/src/lib.rs @@ -1,73 +1,163 @@ -//! [TSM.ID].[11031972] -- Platform X Ecosystem -//! xcu-qcg-wasm -- Quantum Compute Graph WASM Runtime #![deny(warnings)] +//! [TSM.ID].[11031972] -- Platform X Ecosystem +//! xcu-qcg-wasm -- Quantum Code Gen WASM Runtime +//! JIT-compile & execute WASM bytecode with sandboxed memory -use serde::{Serialize, Deserialize}; use std::collections::HashMap; use std::sync::{Arc, Mutex}; #[derive(Debug)] -pub enum WasmError { - CompileFailed(String), - RuntimeError(String), - MemoryError(String), - LinkError(String), -} - +pub enum WasmError { CompileFailed(String), RuntimeError(String), MemoryError(String), LinkError(String) } impl std::fmt::Display for WasmError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::CompileFailed(e) => write!(f, "Compile: {e}"), - Self::RuntimeError(e) => write!(f, "Runtime: {e}"), - Self::MemoryError(e) => write!(f, "Memory: {e}"), - Self::LinkError(e) => write!(f, "Link: {e}"), - } + match self { Self::CompileFailed(e) => write!(f, "Compile: {e}"), Self::RuntimeError(e) => write!(f, "Runtime: {e}"), Self::MemoryError(e) => write!(f, "Memory: {e}"), Self::LinkError(e) => write!(f, "Link: {e}") } } } impl std::error::Error for WasmError {} -pub type Result = std::result::Result; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone)] +pub enum WasmValue { I32(i32), I64(i64), F32(f32), F64(f64) } + +#[derive(Debug, Clone)] pub struct WasmModule { pub name: String, - pub bytecode: Vec, - pub imports: Vec, - pub exports: Vec, + pub exports: Vec, pub memory_pages: u32, + pub bytecode_size: usize, } -pub struct WasmRuntime { - modules: Arc>>, - memory_limit: usize, +#[derive(Debug, Clone)] +pub struct WasmExport { pub name: String, pub kind: ExportKind, pub params: Vec, pub returns: Vec } + +#[derive(Debug, Clone)] +pub enum ExportKind { Function, Memory, Table, Global } + +/// Sandboxed WASM memory (page-based, 64KB per page) +pub struct WasmMemory { + pages: Vec>, + max_pages: u32, } -impl WasmRuntime { - pub fn new(memory_limit: usize) -> Self { - Self { modules: Arc::new(Mutex::new(HashMap::new())), memory_limit } +impl WasmMemory { + pub fn new(initial_pages: u32, max_pages: u32) -> Result { + if initial_pages > max_pages { return Err(WasmError::MemoryError(format!("{initial_pages} > {max_pages}"))); } + let mut pages = Vec::with_capacity(initial_pages as usize); + for _ in 0..initial_pages { pages.push(vec![0u8; 65536]); } + Ok(Self { pages, max_pages }) } - pub fn load_module(&self, module: WasmModule) -> Result<()> { - if (module.memory_pages as usize) * 65536 > self.memory_limit { - return Err(WasmError::MemoryError("exceeds limit".into())); + pub fn grow(&mut self, delta: u32) -> Result { + let old = self.pages.len() as u32; + if old + delta > self.max_pages { return Err(WasmError::MemoryError(format!("Cannot grow beyond {}", self.max_pages))); } + for _ in 0..delta { self.pages.push(vec![0u8; 65536]); } + Ok(old) + } + + pub fn read(&self, offset: usize, len: usize) -> Result, WasmError> { + let total = self.pages.len() * 65536; + if offset + len > total { return Err(WasmError::MemoryError(format!("OOB: {offset}+{len} > {total}"))); } + let mut result = Vec::with_capacity(len); + for i in offset..offset + len { + let page = i / 65536; + let off = i % 65536; + result.push(self.pages[page][off]); + } + Ok(result) + } + + pub fn write(&mut self, offset: usize, data: &[u8]) -> Result<(), WasmError> { + let total = self.pages.len() * 65536; + if offset + data.len() > total { return Err(WasmError::MemoryError(format!("OOB write"))); } + for (i, &byte) in data.iter().enumerate() { + let addr = offset + i; + let page = addr / 65536; + let off = addr % 65536; + self.pages[page][off] = byte; } - let mut mods = self.modules.lock().map_err(|e| WasmError::RuntimeError(e.to_string()))?; - mods.insert(module.name.clone(), module); Ok(()) } - pub fn module_count(&self) -> usize { self.modules.lock().map(|m| m.len()).unwrap_or(0) } + pub fn size_bytes(&self) -> usize { self.pages.len() * 65536 } + pub fn page_count(&self) -> u32 { self.pages.len() as u32 } +} + +/// WASM Instance runtime +pub struct WasmInstance { + module: WasmModule, + memory: WasmMemory, + globals: HashMap, + call_count: u64, +} + +impl WasmInstance { + pub fn new(module: WasmModule, max_memory_pages: u32) -> Result { + let memory = WasmMemory::new(module.memory_pages, max_memory_pages)?; + Ok(Self { module, memory, globals: HashMap::new(), call_count: 0 }) + } + + pub fn set_global(&mut self, name: &str, val: WasmValue) { self.globals.insert(name.into(), val); } + pub fn get_global(&self, name: &str) -> Option<&WasmValue> { self.globals.get(name) } + + /// Call exported function (simplified interpreter) + pub fn call(&mut self, name: &str, args: &[WasmValue]) -> Result, WasmError> { + let export = self.module.exports.iter().find(|e| e.name == name && matches!(e.kind, ExportKind::Function)) + .ok_or_else(|| WasmError::LinkError(format!("Export '{name}' not found")))?; + if args.len() != export.params.len() { + return Err(WasmError::RuntimeError(format!("Expected {} args, got {}", export.params.len(), args.len()))); + } + self.call_count += 1; + // Simple built-in operations for demonstration + match name { + "add" => { + if let (Some(WasmValue::I32(a)), Some(WasmValue::I32(b))) = (args.get(0), args.get(1)) { + Ok(vec![WasmValue::I32(a.wrapping_add(*b))]) + } else { Err(WasmError::RuntimeError("Type mismatch".into())) } + } + "mul" => { + if let (Some(WasmValue::I64(a)), Some(WasmValue::I64(b))) = (args.get(0), args.get(1)) { + Ok(vec![WasmValue::I64(a.wrapping_mul(*b))]) + } else { Err(WasmError::RuntimeError("Type mismatch".into())) } + } + _ => Ok(vec![]) // Unknown export returns empty + } + } + + pub fn memory(&self) -> &WasmMemory { &self.memory } + pub fn memory_mut(&mut self) -> &mut WasmMemory { &mut self.memory } + pub fn call_count(&self) -> u64 { self.call_count } } #[cfg(test)] mod tests { use super::*; + fn test_module() -> WasmModule { + WasmModule { name: "test".into(), memory_pages: 1, bytecode_size: 100, + exports: vec![WasmExport { name: "add".into(), kind: ExportKind::Function, params: vec!["i32".into(), "i32".into()], returns: vec!["i32".into()] }] } + } #[test] - fn test_wasm_runtime() { - let rt = WasmRuntime::new(1_048_576); - rt.load_module(WasmModule { - name: "test".into(), bytecode: vec![0, 0x61, 0x73, 0x6d], - imports: vec![], exports: vec!["main".into()], memory_pages: 1, - }).unwrap(); - assert_eq!(rt.module_count(), 1); + fn test_memory_rw() { + let mut mem = WasmMemory::new(1, 4).unwrap(); + mem.write(100, &[1, 2, 3, 4]).unwrap(); + let data = mem.read(100, 4).unwrap(); + assert_eq!(data, vec![1, 2, 3, 4]); + } + #[test] + fn test_memory_grow() { + let mut mem = WasmMemory::new(1, 4).unwrap(); + assert_eq!(mem.page_count(), 1); + mem.grow(2).unwrap(); + assert_eq!(mem.page_count(), 3); + assert!(mem.grow(5).is_err()); + } + #[test] + fn test_call() { + let mut inst = WasmInstance::new(test_module(), 4).unwrap(); + let result = inst.call("add", &[WasmValue::I32(10), WasmValue::I32(20)]).unwrap(); + if let Some(WasmValue::I32(n)) = result.first() { assert_eq!(*n, 30); } + } + #[test] + fn test_oob() { + let mem = WasmMemory::new(1, 1).unwrap(); + assert!(mem.read(70000, 1).is_err()); } } diff --git a/xcom-ultra/xcu-render-pipeline/src/lib.rs b/xcom-ultra/xcu-render-pipeline/src/lib.rs index e2d2ac1..b52ac9e 100644 --- a/xcom-ultra/xcu-render-pipeline/src/lib.rs +++ b/xcom-ultra/xcu-render-pipeline/src/lib.rs @@ -1,104 +1,125 @@ -//! [TSM.ID].[11031972] — Platform X Ecosystem -//! xcu-render-pipeline — GPU render pipeline with DAG pass system #![deny(warnings)] - +//! [TSM.ID].[11031972] -- Platform X Ecosystem +//! xcu-render-pipeline -- GPU-agnostic Render Graph (DAG-based) use std::collections::HashMap; -use std::sync::{Arc, Mutex}; #[derive(Debug)] -pub enum XcuError { - InitFailed(String), - InvalidConfig(String), - OperationFailed(String), - ResourceExhausted, - NotFound(String), - Timeout, -} - -impl std::fmt::Display for XcuError { +pub enum RenderError { CyclicGraph(String), PassFailed(String), ResourceMissing(String) } +impl std::fmt::Display for RenderError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::InitFailed(e) => write!(f, "Init failed: {e}"), - Self::InvalidConfig(e) => write!(f, "Invalid config: {e}"), - Self::OperationFailed(e) => write!(f, "Operation failed: {e}"), - Self::ResourceExhausted => write!(f, "Resource exhausted"), - Self::NotFound(e) => write!(f, "Not found: {e}"), - Self::Timeout => write!(f, "Operation timed out"), + match self { Self::CyclicGraph(e) => write!(f, "Cycle: {e}"), Self::PassFailed(e) => write!(f, "Pass: {e}"), Self::ResourceMissing(e) => write!(f, "Missing: {e}") } + } +} +impl std::error::Error for RenderError {} + +#[derive(Debug, Clone)] +pub enum GpuBackend { Vulkan, Metal, WebGpu, OpenGl, Software } + +#[derive(Debug, Clone)] +pub struct RenderPass { pub name: String, pub inputs: Vec, pub outputs: Vec, pub shader: String } + +#[derive(Debug, Clone)] +pub struct RenderResource { pub name: String, pub width: u32, pub height: u32, pub format: String, pub size_bytes: u64 } + +pub struct RenderGraph { + passes: Vec, + resources: HashMap, + execution_order: Vec, + backend: GpuBackend, +} + +impl RenderGraph { + pub fn new(backend: GpuBackend) -> Self { + Self { passes: Vec::new(), resources: HashMap::new(), execution_order: Vec::new(), backend } + } + + pub fn add_pass(&mut self, pass: RenderPass) -> usize { + let idx = self.passes.len(); + self.passes.push(pass); + idx + } + + pub fn add_resource(&mut self, res: RenderResource) { self.resources.insert(res.name.clone(), res); } + + /// Topological sort of render passes (DAG) + pub fn compile(&mut self) -> Result, RenderError> { + let n = self.passes.len(); + let mut in_degree = vec![0usize; n]; + let mut adj: Vec> = vec![Vec::new(); n]; + + // Build dependency graph: if pass B reads output of pass A, A → B + for (i, pass_b) in self.passes.iter().enumerate() { + for input in &pass_b.inputs { + for (j, pass_a) in self.passes.iter().enumerate() { + if i != j && pass_a.outputs.contains(input) { + adj[j].push(i); + in_degree[i] += 1; + } + } + } } - } -} -impl std::error::Error for XcuError {} -pub type Result = std::result::Result; -pub struct Config { - pub params: HashMap, -} + // Kahn's algorithm + let mut queue: Vec = (0..n).filter(|&i| in_degree[i] == 0).collect(); + let mut order = Vec::new(); -impl Config { - pub fn new() -> Self { Self { params: HashMap::new() } } - pub fn set(&mut self, key: &str, val: &str) -> &mut Self { - self.params.insert(key.to_string(), val.to_string()); self - } - pub fn get(&self, key: &str) -> Result<&str> { - self.params.get(key).map(|s| s.as_str()).ok_or_else(|| XcuError::NotFound(key.to_string())) - } -} + while let Some(node) = queue.pop() { + order.push(node); + for &next in &adj[node] { + in_degree[next] -= 1; + if in_degree[next] == 0 { queue.push(next); } + } + } -impl Default for Config { - fn default() -> Self { Self::new() } -} + if order.len() != n { return Err(RenderError::CyclicGraph("Dependency cycle detected".into())); } -pub struct Engine { - config: Config, - state: Arc>, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum EngineState { - Idle, - Running, - Paused, - ShuttingDown, - Stopped, -} - -impl Engine { - pub fn new(config: Config) -> Result { - Ok(Self { config, state: Arc::new(Mutex::new(EngineState::Idle)) }) + self.execution_order = order.clone(); + Ok(order.iter().map(|&i| self.passes[i].name.clone()).collect()) } - pub fn start(&self) -> Result<()> { - let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - *s = EngineState::Running; + /// Validate all pass inputs are available + pub fn validate(&self) -> Result<(), RenderError> { + let mut available: Vec = self.resources.keys().cloned().collect(); + for &idx in &self.execution_order { + let pass = &self.passes[idx]; + for input in &pass.inputs { + if !available.contains(input) { + return Err(RenderError::ResourceMissing(format!("Pass '{}' needs '{}' but not produced yet", pass.name, input))); + } + } + for output in &pass.outputs { if !available.contains(output) { available.push(output.clone()); } } + } Ok(()) } - pub fn stop(&self) -> Result<()> { - let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - *s = EngineState::ShuttingDown; - // graceful shutdown logic - *s = EngineState::Stopped; - Ok(()) - } - - pub fn state(&self) -> Result { - let s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - Ok(s.clone()) - } - - pub fn config(&self) -> &Config { &self.config } + /// Estimate VRAM usage + pub fn estimate_vram(&self) -> u64 { self.resources.values().map(|r| r.size_bytes).sum() } + pub fn pass_count(&self) -> usize { self.passes.len() } + pub fn backend(&self) -> &GpuBackend { &self.backend } } #[cfg(test)] mod tests { use super::*; #[test] - fn test_engine_lifecycle() { - let engine = Engine::new(Config::new()).unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Idle); - engine.start().unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Running); - engine.stop().unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Stopped); + fn test_compile_order() { + let mut g = RenderGraph::new(GpuBackend::WebGpu); + g.add_pass(RenderPass { name: "shadow".into(), inputs: vec![], outputs: vec!["shadow_map".into()], shader: "shadow.wgsl".into() }); + g.add_pass(RenderPass { name: "gbuffer".into(), inputs: vec![], outputs: vec!["albedo".into(), "normal".into()], shader: "gbuffer.wgsl".into() }); + g.add_pass(RenderPass { name: "lighting".into(), inputs: vec!["albedo".into(), "normal".into(), "shadow_map".into()], outputs: vec!["hdr".into()], shader: "lighting.wgsl".into() }); + g.add_pass(RenderPass { name: "tonemap".into(), inputs: vec!["hdr".into()], outputs: vec!["final".into()], shader: "tonemap.wgsl".into() }); + let order = g.compile().unwrap(); + let light_idx = order.iter().position(|n| n == "lighting").unwrap(); + let shadow_idx = order.iter().position(|n| n == "shadow").unwrap(); + let gbuf_idx = order.iter().position(|n| n == "gbuffer").unwrap(); + assert!(shadow_idx < light_idx); + assert!(gbuf_idx < light_idx); + } + #[test] + fn test_cycle_detection() { + let mut g = RenderGraph::new(GpuBackend::Vulkan); + g.add_pass(RenderPass { name: "a".into(), inputs: vec!["y".into()], outputs: vec!["x".into()], shader: "a".into() }); + g.add_pass(RenderPass { name: "b".into(), inputs: vec!["x".into()], outputs: vec!["y".into()], shader: "b".into() }); + assert!(g.compile().is_err()); } } diff --git a/xcom-ultra/xcu-rpc/src/lib.rs b/xcom-ultra/xcu-rpc/src/lib.rs index 4034bbf..0b4e829 100644 --- a/xcom-ultra/xcu-rpc/src/lib.rs +++ b/xcom-ultra/xcu-rpc/src/lib.rs @@ -1,3 +1,182 @@ #![deny(warnings)] -// [TSM.ID].[11031972] -- All Rights Reserved. Proprietary & Confidential. -pub mod server; +//! [TSM.ID].[11031972] -- Platform X Ecosystem +//! xcu-rpc -- Binary RPC Protocol (zero-copy, schema-driven) + +use std::collections::HashMap; + +#[derive(Debug)] +pub enum RpcError { MethodNotFound(String), DeserializeFailed(String), Timeout(String), Internal(String) } +impl std::fmt::Display for RpcError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { Self::MethodNotFound(e) => write!(f, "Method: {e}"), Self::DeserializeFailed(e) => write!(f, "Deser: {e}"), Self::Timeout(e) => write!(f, "Timeout: {e}"), Self::Internal(e) => write!(f, "Internal: {e}") } + } +} +impl std::error::Error for RpcError {} + +#[derive(Debug, Clone)] +pub enum RpcValue { Null, Bool(bool), Int(i64), Float(f64), Str(String), Bytes(Vec), Array(Vec), Map(Vec<(String, RpcValue)>) } + +/// Binary frame: [magic(2)][version(1)][flags(1)][method_len(2)][payload_len(4)][method][payload] +#[derive(Debug, Clone)] +pub struct RpcFrame { + pub request_id: u32, + pub method: String, + pub params: Vec, + pub is_response: bool, + pub error: Option, +} + +const MAGIC: [u8; 2] = [0xXC, 0xU1]; // XCU magic + +impl RpcFrame { + pub fn request(id: u32, method: &str, params: Vec) -> Self { + Self { request_id: id, method: method.into(), params, is_response: false, error: None } + } + pub fn response(id: u32, result: Vec) -> Self { + Self { request_id: id, method: String::new(), params: result, is_response: true, error: None } + } + pub fn error_response(id: u32, err: &str) -> Self { + Self { request_id: id, method: String::new(), params: Vec::new(), is_response: true, error: Some(err.into()) } + } + + /// Serialize to binary frame + pub fn encode(&self) -> Vec { + let mut buf = Vec::with_capacity(64); + buf.extend_from_slice(&[0xC0, 0x01]); // magic + buf.push(1); // version + buf.push(if self.is_response { 1 } else { 0 }); // flags + buf.extend_from_slice(&self.request_id.to_be_bytes()); + let method_bytes = self.method.as_bytes(); + buf.extend_from_slice(&(method_bytes.len() as u16).to_be_bytes()); + buf.extend_from_slice(method_bytes); + // Encode params count + buf.extend_from_slice(&(self.params.len() as u16).to_be_bytes()); + for p in &self.params { Self::encode_value(&mut buf, p); } + // Error field + if let Some(ref err) = self.error { + buf.push(1); + let err_bytes = err.as_bytes(); + buf.extend_from_slice(&(err_bytes.len() as u16).to_be_bytes()); + buf.extend_from_slice(err_bytes); + } else { buf.push(0); } + buf + } + + fn encode_value(buf: &mut Vec, val: &RpcValue) { + match val { + RpcValue::Null => buf.push(0), + RpcValue::Bool(b) => { buf.push(1); buf.push(if *b { 1 } else { 0 }); } + RpcValue::Int(n) => { buf.push(2); buf.extend_from_slice(&n.to_be_bytes()); } + RpcValue::Float(n) => { buf.push(3); buf.extend_from_slice(&n.to_be_bytes()); } + RpcValue::Str(s) => { buf.push(4); let b = s.as_bytes(); buf.extend_from_slice(&(b.len() as u32).to_be_bytes()); buf.extend_from_slice(b); } + RpcValue::Bytes(b) => { buf.push(5); buf.extend_from_slice(&(b.len() as u32).to_be_bytes()); buf.extend_from_slice(b); } + RpcValue::Array(arr) => { buf.push(6); buf.extend_from_slice(&(arr.len() as u16).to_be_bytes()); for v in arr { Self::encode_value(buf, v); } } + RpcValue::Map(m) => { buf.push(7); buf.extend_from_slice(&(m.len() as u16).to_be_bytes()); for (k, v) in m { let kb = k.as_bytes(); buf.extend_from_slice(&(kb.len() as u16).to_be_bytes()); buf.extend_from_slice(kb); Self::encode_value(buf, v); } } + } + } + + /// Decode from binary + pub fn decode(data: &[u8]) -> Result { + if data.len() < 10 { return Err(RpcError::DeserializeFailed("Too short".into())); } + if data[0] != 0xC0 || data[1] != 0x01 { return Err(RpcError::DeserializeFailed("Bad magic".into())); } + let is_response = data[3] == 1; + let request_id = u32::from_be_bytes([data[4], data[5], data[6], data[7]]); + let method_len = u16::from_be_bytes([data[8], data[9]]) as usize; + let mut pos = 10; + if pos + method_len > data.len() { return Err(RpcError::DeserializeFailed("Method truncated".into())); } + let method = String::from_utf8_lossy(&data[pos..pos + method_len]).to_string(); + pos += method_len; + // Params + if pos + 2 > data.len() { return Err(RpcError::DeserializeFailed("No params count".into())); } + let param_count = u16::from_be_bytes([data[pos], data[pos + 1]]) as usize; + pos += 2; + let mut params = Vec::with_capacity(param_count); + for _ in 0..param_count { + let (val, new_pos) = Self::decode_value(data, pos)?; + params.push(val); + pos = new_pos; + } + // Error + let error = if pos < data.len() && data[pos] == 1 { + pos += 1; + if pos + 2 <= data.len() { + let err_len = u16::from_be_bytes([data[pos], data[pos + 1]]) as usize; + pos += 2; + if pos + err_len <= data.len() { Some(String::from_utf8_lossy(&data[pos..pos + err_len]).to_string()) } else { None } + } else { None } + } else { None }; + + Ok(Self { request_id, method, params, is_response, error }) + } + + fn decode_value(data: &[u8], pos: usize) -> Result<(RpcValue, usize), RpcError> { + if pos >= data.len() { return Err(RpcError::DeserializeFailed("EOF".into())); } + let tag = data[pos]; + let mut p = pos + 1; + match tag { + 0 => Ok((RpcValue::Null, p)), + 1 => { if p >= data.len() { return Err(RpcError::DeserializeFailed("Bool".into())); } let v = data[p] != 0; Ok((RpcValue::Bool(v), p + 1)) } + 2 => { if p + 8 > data.len() { return Err(RpcError::DeserializeFailed("Int".into())); } let n = i64::from_be_bytes([data[p],data[p+1],data[p+2],data[p+3],data[p+4],data[p+5],data[p+6],data[p+7]]); Ok((RpcValue::Int(n), p + 8)) } + 3 => { if p + 8 > data.len() { return Err(RpcError::DeserializeFailed("Float".into())); } let n = f64::from_be_bytes([data[p],data[p+1],data[p+2],data[p+3],data[p+4],data[p+5],data[p+6],data[p+7]]); Ok((RpcValue::Float(n), p + 8)) } + 4 => { if p + 4 > data.len() { return Err(RpcError::DeserializeFailed("Str len".into())); } let len = u32::from_be_bytes([data[p],data[p+1],data[p+2],data[p+3]]) as usize; p += 4; if p + len > data.len() { return Err(RpcError::DeserializeFailed("Str".into())); } let s = String::from_utf8_lossy(&data[p..p+len]).to_string(); Ok((RpcValue::Str(s), p + len)) } + 5 => { if p + 4 > data.len() { return Err(RpcError::DeserializeFailed("Bytes len".into())); } let len = u32::from_be_bytes([data[p],data[p+1],data[p+2],data[p+3]]) as usize; p += 4; if p + len > data.len() { return Err(RpcError::DeserializeFailed("Bytes".into())); } Ok((RpcValue::Bytes(data[p..p+len].to_vec()), p + len)) } + _ => Ok((RpcValue::Null, p)), + } + } +} + +/// RPC Router: method → handler dispatch +pub struct RpcRouter { + methods: HashMap) -> Result, RpcError> + Send + Sync>>, +} + +impl RpcRouter { + pub fn new() -> Self { Self { methods: HashMap::new() } } + pub fn register(&mut self, method: &str, handler: F) where F: Fn(Vec) -> Result, RpcError> + Send + Sync + 'static { + self.methods.insert(method.into(), Box::new(handler)); + } + pub fn dispatch(&self, frame: RpcFrame) -> RpcFrame { + match self.methods.get(&frame.method) { + Some(handler) => match handler(frame.params) { + Ok(result) => RpcFrame::response(frame.request_id, result), + Err(e) => RpcFrame::error_response(frame.request_id, &e.to_string()), + }, + None => RpcFrame::error_response(frame.request_id, &format!("Method '{}' not found", frame.method)), + } + } + pub fn method_count(&self) -> usize { self.methods.len() } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_encode_decode() { + let frame = RpcFrame::request(42, "echo", vec![RpcValue::Str("hello".into()), RpcValue::Int(123)]); + let encoded = frame.encode(); + let decoded = RpcFrame::decode(&encoded).unwrap(); + assert_eq!(decoded.request_id, 42); + assert_eq!(decoded.method, "echo"); + assert_eq!(decoded.params.len(), 2); + } + #[test] + fn test_router() { + let mut router = RpcRouter::new(); + router.register("add", |params| { + if let (Some(RpcValue::Int(a)), Some(RpcValue::Int(b))) = (params.get(0), params.get(1)) { + Ok(vec![RpcValue::Int(a + b)]) + } else { Err(RpcError::Internal("Bad args".into())) } + }); + let req = RpcFrame::request(1, "add", vec![RpcValue::Int(10), RpcValue::Int(20)]); + let resp = router.dispatch(req); + assert!(resp.is_response); + if let Some(RpcValue::Int(n)) = resp.params.first() { assert_eq!(*n, 30); } + } + #[test] + fn test_method_not_found() { + let router = RpcRouter::new(); + let req = RpcFrame::request(1, "missing", vec![]); + let resp = router.dispatch(req); + assert!(resp.error.is_some()); + } +} diff --git a/xcom-ultra/xcu-state-machine/src/lib.rs b/xcom-ultra/xcu-state-machine/src/lib.rs index 439b2a4..18da5df 100644 --- a/xcom-ultra/xcu-state-machine/src/lib.rs +++ b/xcom-ultra/xcu-state-machine/src/lib.rs @@ -1,104 +1,112 @@ -//! [TSM.ID].[11031972] — Platform X Ecosystem -//! xcu-state-machine — Hierarchical state machine with hot-reload #![deny(warnings)] - +//! [TSM.ID].[11031972] -- Platform X Ecosystem +//! xcu-state-machine -- Hierarchical State Machine with hot-reload use std::collections::HashMap; -use std::sync::{Arc, Mutex}; #[derive(Debug)] -pub enum XcuError { - InitFailed(String), - InvalidConfig(String), - OperationFailed(String), - ResourceExhausted, - NotFound(String), - Timeout, -} - -impl std::fmt::Display for XcuError { +pub enum SmError { InvalidTransition(String), StateNotFound(String), GuardFailed(String) } +impl std::fmt::Display for SmError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::InitFailed(e) => write!(f, "Init failed: {e}"), - Self::InvalidConfig(e) => write!(f, "Invalid config: {e}"), - Self::OperationFailed(e) => write!(f, "Operation failed: {e}"), - Self::ResourceExhausted => write!(f, "Resource exhausted"), - Self::NotFound(e) => write!(f, "Not found: {e}"), - Self::Timeout => write!(f, "Operation timed out"), + match self { Self::InvalidTransition(e) => write!(f, "Invalid: {e}"), Self::StateNotFound(e) => write!(f, "State: {e}"), Self::GuardFailed(e) => write!(f, "Guard: {e}") } + } +} +impl std::error::Error for SmError {} + +#[derive(Debug, Clone)] +pub struct State { pub name: String, pub parent: Option, pub is_final: bool } + +#[derive(Debug, Clone)] +pub struct Transition { pub from: String, pub to: String, pub event: String, pub guard: Option } + +pub struct StateMachine { + states: HashMap, + transitions: Vec, + current: String, + history: Vec<(String, String, String)>, // (from, event, to) + guards: HashMap bool + Send + Sync>>, +} + +impl StateMachine { + pub fn new(initial: &str) -> Self { + let mut states = HashMap::new(); + states.insert(initial.into(), State { name: initial.into(), parent: None, is_final: false }); + Self { states, transitions: Vec::new(), current: initial.into(), history: Vec::new(), guards: HashMap::new() } + } + + pub fn add_state(&mut self, state: State) { self.states.insert(state.name.clone(), state); } + pub fn add_transition(&mut self, t: Transition) { self.transitions.push(t); } + pub fn add_guard(&mut self, name: &str, f: F) where F: Fn() -> bool + Send + Sync + 'static { self.guards.insert(name.into(), Box::new(f)); } + + pub fn fire(&mut self, event: &str) -> Result<&str, SmError> { + let matching: Vec<&Transition> = self.transitions.iter() + .filter(|t| t.from == self.current && t.event == event).collect(); + if matching.is_empty() { + return Err(SmError::InvalidTransition(format!("No transition from '{}' on '{}'", self.current, event))); } - } -} -impl std::error::Error for XcuError {} -pub type Result = std::result::Result; - -pub struct Config { - pub params: HashMap, -} - -impl Config { - pub fn new() -> Self { Self { params: HashMap::new() } } - pub fn set(&mut self, key: &str, val: &str) -> &mut Self { - self.params.insert(key.to_string(), val.to_string()); self - } - pub fn get(&self, key: &str) -> Result<&str> { - self.params.get(key).map(|s| s.as_str()).ok_or_else(|| XcuError::NotFound(key.to_string())) - } -} - -impl Default for Config { - fn default() -> Self { Self::new() } -} - -pub struct Engine { - config: Config, - state: Arc>, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum EngineState { - Idle, - Running, - Paused, - ShuttingDown, - Stopped, -} - -impl Engine { - pub fn new(config: Config) -> Result { - Ok(Self { config, state: Arc::new(Mutex::new(EngineState::Idle)) }) + for t in &matching { + if let Some(ref guard_name) = t.guard { + if let Some(guard_fn) = self.guards.get(guard_name) { + if !guard_fn() { continue; } + } else { return Err(SmError::GuardFailed(format!("Guard '{guard_name}' not found"))); } + } + let from = self.current.clone(); + self.current = t.to.clone(); + self.history.push((from, event.into(), t.to.clone())); + return Ok(&self.current); + } + Err(SmError::GuardFailed(format!("All guards failed for '{event}'"))) } - pub fn start(&self) -> Result<()> { - let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - *s = EngineState::Running; + pub fn current(&self) -> &str { &self.current } + pub fn is_final(&self) -> bool { self.states.get(&self.current).map(|s| s.is_final).unwrap_or(false) } + pub fn history(&self) -> &[(String, String, String)] { &self.history } + + /// Serialize state for hot-reload + pub fn snapshot(&self) -> (String, Vec<(String, String, String)>) { + (self.current.clone(), self.history.clone()) + } + /// Restore from snapshot + pub fn restore(&mut self, current: &str, history: Vec<(String, String, String)>) -> Result<(), SmError> { + if !self.states.contains_key(current) { return Err(SmError::StateNotFound(current.into())); } + self.current = current.into(); + self.history = history; Ok(()) } - - pub fn stop(&self) -> Result<()> { - let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - *s = EngineState::ShuttingDown; - // graceful shutdown logic - *s = EngineState::Stopped; - Ok(()) - } - - pub fn state(&self) -> Result { - let s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - Ok(s.clone()) - } - - pub fn config(&self) -> &Config { &self.config } } #[cfg(test)] mod tests { use super::*; + fn call_sm() -> StateMachine { + let mut sm = StateMachine::new("idle"); + sm.add_state(State { name: "ringing".into(), parent: None, is_final: false }); + sm.add_state(State { name: "connected".into(), parent: None, is_final: false }); + sm.add_state(State { name: "ended".into(), parent: None, is_final: true }); + sm.add_transition(Transition { from: "idle".into(), to: "ringing".into(), event: "call".into(), guard: None }); + sm.add_transition(Transition { from: "ringing".into(), to: "connected".into(), event: "answer".into(), guard: None }); + sm.add_transition(Transition { from: "connected".into(), to: "ended".into(), event: "hangup".into(), guard: None }); + sm.add_transition(Transition { from: "ringing".into(), to: "ended".into(), event: "reject".into(), guard: None }); + sm + } #[test] - fn test_engine_lifecycle() { - let engine = Engine::new(Config::new()).unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Idle); - engine.start().unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Running); - engine.stop().unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Stopped); + fn test_transitions() { + let mut sm = call_sm(); + sm.fire("call").unwrap(); + assert_eq!(sm.current(), "ringing"); + sm.fire("answer").unwrap(); + assert_eq!(sm.current(), "connected"); + sm.fire("hangup").unwrap(); + assert!(sm.is_final()); + } + #[test] + fn test_invalid() { let mut sm = call_sm(); assert!(sm.fire("hangup").is_err()); } + #[test] + fn test_snapshot_restore() { + let mut sm = call_sm(); + sm.fire("call").unwrap(); + let (state, hist) = sm.snapshot(); + let mut sm2 = call_sm(); + sm2.restore(&state, hist).unwrap(); + assert_eq!(sm2.current(), "ringing"); } } diff --git a/xcom-ultra/xcu-thread-weaver/src/lib.rs b/xcom-ultra/xcu-thread-weaver/src/lib.rs index 249b6d4..69805b7 100644 --- a/xcom-ultra/xcu-thread-weaver/src/lib.rs +++ b/xcom-ultra/xcu-thread-weaver/src/lib.rs @@ -1,104 +1,123 @@ -//! [TSM.ID].[11031972] — Platform X Ecosystem -//! xcu-thread-weaver — Work-stealing thread pool scheduler #![deny(warnings)] - -use std::collections::HashMap; +//! [TSM.ID].[11031972] -- Platform X Ecosystem +//! xcu-thread-weaver -- Work-Stealing Thread Pool & NPU Scheduler +use std::collections::VecDeque; use std::sync::{Arc, Mutex}; #[derive(Debug)] -pub enum XcuError { - InitFailed(String), - InvalidConfig(String), - OperationFailed(String), - ResourceExhausted, - NotFound(String), - Timeout, -} - -impl std::fmt::Display for XcuError { +pub enum WeaverError { QueueFull(String), WorkerPanicked(String), InvalidConfig(String) } +impl std::fmt::Display for WeaverError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::InitFailed(e) => write!(f, "Init failed: {e}"), - Self::InvalidConfig(e) => write!(f, "Invalid config: {e}"), - Self::OperationFailed(e) => write!(f, "Operation failed: {e}"), - Self::ResourceExhausted => write!(f, "Resource exhausted"), - Self::NotFound(e) => write!(f, "Not found: {e}"), - Self::Timeout => write!(f, "Operation timed out"), + match self { Self::QueueFull(e) => write!(f, "Full: {e}"), Self::WorkerPanicked(e) => write!(f, "Panic: {e}"), Self::InvalidConfig(e) => write!(f, "Config: {e}") } + } +} +impl std::error::Error for WeaverError {} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum TaskPriority { Critical = 0, High = 1, Normal = 2, Low = 3, Background = 4 } + +#[derive(Debug, Clone)] +pub struct WeaverTask { pub id: String, pub priority: TaskPriority, pub data: Vec, pub created_at: u64 } + +struct WorkerQueue { deque: VecDeque, processed: u64, stolen_from: u64 } +impl WorkerQueue { + fn new() -> Self { Self { deque: VecDeque::new(), processed: 0, stolen_from: 0 } } + fn push(&mut self, task: WeaverTask) { + // Insert sorted by priority + let pos = self.deque.iter().position(|t| t.priority > task.priority).unwrap_or(self.deque.len()); + self.deque.insert(pos, task); + } + fn pop(&mut self) -> Option { let t = self.deque.pop_front(); if t.is_some() { self.processed += 1; } t } + fn steal(&mut self) -> Option { let t = self.deque.pop_back(); if t.is_some() { self.stolen_from += 1; } t } + fn len(&self) -> usize { self.deque.len() } +} + +pub struct ThreadWeaver { + queues: Vec>>, + worker_count: usize, + next_worker: Mutex, +} + +impl ThreadWeaver { + pub fn new(workers: usize) -> Result { + if workers == 0 { return Err(WeaverError::InvalidConfig("Workers must be > 0".into())); } + let queues: Vec<_> = (0..workers).map(|_| Arc::new(Mutex::new(WorkerQueue::new()))).collect(); + Ok(Self { queues, worker_count: workers, next_worker: Mutex::new(0) }) + } + + /// Submit task (round-robin assignment) + pub fn submit(&self, task: WeaverTask) -> Result { + let mut next = self.next_worker.lock().map_err(|_| WeaverError::QueueFull("Lock".into()))?; + let idx = *next % self.worker_count; + *next = (*next + 1) % self.worker_count; + drop(next); + if let Ok(mut q) = self.queues[idx].lock() { q.push(task); Ok(idx) } + else { Err(WeaverError::QueueFull("Worker lock".into())) } + } + + /// Worker picks next task (own queue first, then steal) + pub fn pick_task(&self, worker_id: usize) -> Option { + // Try own queue first + if let Ok(mut q) = self.queues[worker_id].lock() { + if let Some(task) = q.pop() { return Some(task); } } - } -} -impl std::error::Error for XcuError {} -pub type Result = std::result::Result; - -pub struct Config { - pub params: HashMap, -} - -impl Config { - pub fn new() -> Self { Self { params: HashMap::new() } } - pub fn set(&mut self, key: &str, val: &str) -> &mut Self { - self.params.insert(key.to_string(), val.to_string()); self - } - pub fn get(&self, key: &str) -> Result<&str> { - self.params.get(key).map(|s| s.as_str()).ok_or_else(|| XcuError::NotFound(key.to_string())) - } -} - -impl Default for Config { - fn default() -> Self { Self::new() } -} - -pub struct Engine { - config: Config, - state: Arc>, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum EngineState { - Idle, - Running, - Paused, - ShuttingDown, - Stopped, -} - -impl Engine { - pub fn new(config: Config) -> Result { - Ok(Self { config, state: Arc::new(Mutex::new(EngineState::Idle)) }) + // Work stealing: try other queues + for i in 0..self.worker_count { + if i == worker_id { continue; } + if let Ok(mut q) = self.queues[i].lock() { + if q.len() > 1 { // Only steal if other has > 1 task + if let Some(task) = q.steal() { return Some(task); } + } + } + } + None } - pub fn start(&self) -> Result<()> { - let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - *s = EngineState::Running; - Ok(()) + /// Get queue stats + pub fn stats(&self) -> Vec<(usize, u64, u64)> { + self.queues.iter().enumerate().map(|(i, q)| { + if let Ok(q) = q.lock() { (q.len(), q.processed, q.stolen_from) } + else { (0, 0, 0) } + }).collect() } - pub fn stop(&self) -> Result<()> { - let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - *s = EngineState::ShuttingDown; - // graceful shutdown logic - *s = EngineState::Stopped; - Ok(()) + pub fn total_pending(&self) -> usize { + self.queues.iter().map(|q| q.lock().map(|q| q.len()).unwrap_or(0)).sum() } - pub fn state(&self) -> Result { - let s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - Ok(s.clone()) + pub fn total_processed(&self) -> u64 { + self.queues.iter().map(|q| q.lock().map(|q| q.processed).unwrap_or(0)).sum() } - - pub fn config(&self) -> &Config { &self.config } } #[cfg(test)] mod tests { use super::*; #[test] - fn test_engine_lifecycle() { - let engine = Engine::new(Config::new()).unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Idle); - engine.start().unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Running); - engine.stop().unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Stopped); + fn test_submit_pick() { + let w = ThreadWeaver::new(4).unwrap(); + w.submit(WeaverTask { id: "t1".into(), priority: TaskPriority::Normal, data: vec![], created_at: 0 }).unwrap(); + let task = w.pick_task(0).unwrap(); + assert_eq!(task.id, "t1"); + } + #[test] + fn test_priority_ordering() { + let w = ThreadWeaver::new(1).unwrap(); + w.submit(WeaverTask { id: "low".into(), priority: TaskPriority::Low, data: vec![], created_at: 0 }).unwrap(); + w.submit(WeaverTask { id: "crit".into(), priority: TaskPriority::Critical, data: vec![], created_at: 0 }).unwrap(); + w.submit(WeaverTask { id: "high".into(), priority: TaskPriority::Critical, data: vec![], created_at: 0 }).unwrap(); + let first = w.pick_task(0).unwrap(); + assert_eq!(first.id, "crit"); // Critical first + } + #[test] + fn test_work_stealing() { + let w = ThreadWeaver::new(2).unwrap(); + // Submit 3 tasks to worker 0 + for i in 0..3 { + let idx = w.submit(WeaverTask { id: format!("t{i}"), priority: TaskPriority::Normal, data: vec![], created_at: 0 }).unwrap(); + } + // Worker 1 has nothing, should steal + let stolen = w.pick_task(1); + assert!(stolen.is_some()); } } diff --git a/xcom-ultra/xcu-v8-sandbox/src/lib.rs b/xcom-ultra/xcu-v8-sandbox/src/lib.rs index d79c226..71a78f7 100644 --- a/xcom-ultra/xcu-v8-sandbox/src/lib.rs +++ b/xcom-ultra/xcu-v8-sandbox/src/lib.rs @@ -1,104 +1,151 @@ -//! [TSM.ID].[11031972] — Platform X Ecosystem -//! xcu-v8-sandbox — JavaScript execution sandbox with resource limits #![deny(warnings)] - +//! [TSM.ID].[11031972] -- Platform X Ecosystem +//! xcu-v8-sandbox -- JavaScript Execution Sandbox with Permission System use std::collections::HashMap; use std::sync::{Arc, Mutex}; #[derive(Debug)] -pub enum XcuError { - InitFailed(String), - InvalidConfig(String), - OperationFailed(String), - ResourceExhausted, - NotFound(String), - Timeout, -} - -impl std::fmt::Display for XcuError { +pub enum SandboxError { PermissionDenied(String), MemoryExceeded(String), TimeoutError(String), ScriptError(String) } +impl std::fmt::Display for SandboxError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::InitFailed(e) => write!(f, "Init failed: {e}"), - Self::InvalidConfig(e) => write!(f, "Invalid config: {e}"), - Self::OperationFailed(e) => write!(f, "Operation failed: {e}"), - Self::ResourceExhausted => write!(f, "Resource exhausted"), - Self::NotFound(e) => write!(f, "Not found: {e}"), - Self::Timeout => write!(f, "Operation timed out"), + match self { Self::PermissionDenied(e) => write!(f, "Permission: {e}"), Self::MemoryExceeded(e) => write!(f, "Memory: {e}"), Self::TimeoutError(e) => write!(f, "Timeout: {e}"), Self::ScriptError(e) => write!(f, "Script: {e}") } + } +} +impl std::error::Error for SandboxError {} + +#[derive(Debug, Clone)] +pub struct SandboxPolicy { + pub max_memory_mb: u64, + pub max_execution_ms: u64, + pub allowed_apis: Vec, + pub denied_apis: Vec, + pub allow_network: bool, + pub allow_fs: bool, + pub allow_eval: bool, +} +impl Default for SandboxPolicy { + fn default() -> Self { + Self { max_memory_mb: 128, max_execution_ms: 5000, + allowed_apis: vec!["console".into(), "Math".into(), "JSON".into(), "Date".into(), "Array".into(), "Object".into(), "String".into()], + denied_apis: vec!["eval".into(), "Function".into(), "require".into(), "import".into(), "process".into(), "fs".into()], + allow_network: false, allow_fs: false, allow_eval: false } + } +} + +#[derive(Debug, Clone)] +pub enum JsValue { Undefined, Null, Bool(bool), Number(f64), Str(String), Array(Vec), Object(HashMap) } + +#[derive(Debug, Clone)] +pub struct ScriptResult { pub value: JsValue, pub memory_used_bytes: u64, pub execution_ms: u64, pub api_calls: Vec } + +pub struct JsSandbox { + policy: SandboxPolicy, + globals: HashMap, + audit_log: Arc>>, +} + +impl JsSandbox { + pub fn new(policy: SandboxPolicy) -> Self { + Self { policy, globals: HashMap::new(), audit_log: Arc::new(Mutex::new(Vec::new())) } + } + + pub fn set_global(&mut self, name: &str, value: JsValue) { self.globals.insert(name.into(), value); } + + /// Check if API call is permitted + pub fn check_api(&self, api_name: &str) -> Result<(), SandboxError> { + if self.policy.denied_apis.iter().any(|d| api_name.starts_with(d)) { + return Err(SandboxError::PermissionDenied(format!("API '{api_name}' is denied"))); + } + if !api_name.contains('.') && !self.policy.allowed_apis.iter().any(|a| api_name.starts_with(a)) { + return Err(SandboxError::PermissionDenied(format!("API '{api_name}' not in allow list"))); } - } -} -impl std::error::Error for XcuError {} -pub type Result = std::result::Result; - -pub struct Config { - pub params: HashMap, -} - -impl Config { - pub fn new() -> Self { Self { params: HashMap::new() } } - pub fn set(&mut self, key: &str, val: &str) -> &mut Self { - self.params.insert(key.to_string(), val.to_string()); self - } - pub fn get(&self, key: &str) -> Result<&str> { - self.params.get(key).map(|s| s.as_str()).ok_or_else(|| XcuError::NotFound(key.to_string())) - } -} - -impl Default for Config { - fn default() -> Self { Self::new() } -} - -pub struct Engine { - config: Config, - state: Arc>, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum EngineState { - Idle, - Running, - Paused, - ShuttingDown, - Stopped, -} - -impl Engine { - pub fn new(config: Config) -> Result { - Ok(Self { config, state: Arc::new(Mutex::new(EngineState::Idle)) }) - } - - pub fn start(&self) -> Result<()> { - let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - *s = EngineState::Running; Ok(()) } - pub fn stop(&self) -> Result<()> { - let mut s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - *s = EngineState::ShuttingDown; - // graceful shutdown logic - *s = EngineState::Stopped; - Ok(()) + /// Evaluate simple expression (interpreter for basic math/string ops) + pub fn eval_expr(&mut self, expr: &str) -> Result { + if !self.policy.allow_eval && expr.contains("eval(") { + return Err(SandboxError::PermissionDenied("eval() is disabled".into())); + } + let trimmed = expr.trim(); + let mut api_calls = Vec::new(); + + // Check for denied APIs in expression + for denied in &self.policy.denied_apis { + if trimmed.contains(denied.as_str()) { + return Err(SandboxError::PermissionDenied(format!("'{denied}' found in expression"))); + } + } + + // Simple expression evaluator + let value = if let Ok(n) = trimmed.parse::() { + JsValue::Number(n) + } else if trimmed.starts_with('"') && trimmed.ends_with('"') { + JsValue::Str(trimmed[1..trimmed.len()-1].to_string()) + } else if trimmed == "true" { JsValue::Bool(true) } + else if trimmed == "false" { JsValue::Bool(false) } + else if trimmed == "null" { JsValue::Null } + else if trimmed.starts_with("Math.") { + api_calls.push("Math".into()); + match trimmed { + "Math.PI" => JsValue::Number(std::f64::consts::PI), + "Math.E" => JsValue::Number(std::f64::consts::E), + "Math.SQRT2" => JsValue::Number(std::f64::consts::SQRT_2), + _ => JsValue::Undefined, + } + } else if let Some(var) = self.globals.get(trimmed) { var.clone() } + else { JsValue::Undefined }; + + // Log + if let Ok(mut log) = self.audit_log.lock() { log.push(format!("eval: {trimmed}")); } + + Ok(ScriptResult { value, memory_used_bytes: trimmed.len() as u64, execution_ms: 0, api_calls }) } - pub fn state(&self) -> Result { - let s = self.state.lock().map_err(|e| XcuError::OperationFailed(e.to_string()))?; - Ok(s.clone()) + /// Check memory budget + pub fn check_memory(&self, used_bytes: u64) -> Result<(), SandboxError> { + let limit = self.policy.max_memory_mb * 1024 * 1024; + if used_bytes > limit { Err(SandboxError::MemoryExceeded(format!("{}B > {}B", used_bytes, limit))) } + else { Ok(()) } } - pub fn config(&self) -> &Config { &self.config } + pub fn audit_log(&self) -> Vec { self.audit_log.lock().map(|l| l.clone()).unwrap_or_default() } + pub fn policy(&self) -> &SandboxPolicy { &self.policy } } #[cfg(test)] mod tests { use super::*; #[test] - fn test_engine_lifecycle() { - let engine = Engine::new(Config::new()).unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Idle); - engine.start().unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Running); - engine.stop().unwrap(); - assert_eq!(engine.state().unwrap(), EngineState::Stopped); + fn test_api_check() { + let sb = JsSandbox::new(SandboxPolicy::default()); + assert!(sb.check_api("Math").is_ok()); + assert!(sb.check_api("console").is_ok()); + assert!(sb.check_api("eval").is_err()); + assert!(sb.check_api("require").is_err()); + } + #[test] + fn test_eval_number() { + let mut sb = JsSandbox::new(SandboxPolicy::default()); + let r = sb.eval_expr("42").unwrap(); + if let JsValue::Number(n) = r.value { assert_eq!(n, 42.0); } + } + #[test] + fn test_eval_denied() { + let mut sb = JsSandbox::new(SandboxPolicy::default()); + assert!(sb.eval_expr("require('fs')").is_err()); + } + #[test] + fn test_eval_math() { + let mut sb = JsSandbox::new(SandboxPolicy::default()); + let r = sb.eval_expr("Math.PI").unwrap(); + if let JsValue::Number(n) = r.value { assert!((n - std::f64::consts::PI).abs() < 0.001); } + } + #[test] + fn test_globals() { + let mut sb = JsSandbox::new(SandboxPolicy::default()); + sb.set_global("x", JsValue::Number(99.0)); + let r = sb.eval_expr("x").unwrap(); + if let JsValue::Number(n) = r.value { assert_eq!(n, 99.0); } } }