use prometeu_core::debugger_protocol::*; use prometeu_core::firmware::{BootTarget, Firmware}; use prometeu_core::model::CartridgeLoader; use prometeu_core::Hardware; use std::io::{Read, Write}; use std::net::{TcpListener, TcpStream}; /// Host-side implementation of the PROMETEU DevTools Protocol. /// /// This component acts as a TCP server that allows external tools (like the /// Prometeu Debugger) to observe and control the execution of the virtual machine. /// /// Communication is based on JSONL (JSON lines) over TCP. pub struct HostDebugger { /// If true, the VM will not start execution until a 'start' command is received. pub waiting_for_start: bool, /// The TCP listener for incoming debugger connections. pub(crate) listener: Option, /// The currently active connection to a debugger client. pub(crate) stream: Option, /// Sequence tracker to ensure logs are sent only once. last_log_seq: u64, /// Frame tracker to send telemetry snapshots periodically. last_telemetry_frame: u64, } impl HostDebugger { /// Creates a new debugger interface in an idle state. pub fn new() -> Self { Self { waiting_for_start: false, listener: None, stream: None, last_log_seq: 0, last_telemetry_frame: 0, } } /// Configures the debugger based on the boot target. /// If debug mode is enabled, it binds to the specified TCP port. pub fn setup_boot_target(&mut self, boot_target: &BootTarget, firmware: &mut Firmware) { if let BootTarget::Cartridge { path, debug: true, debug_port } = boot_target { self.waiting_for_start = true; // Pre-load cartridge metadata so the Handshake message can contain // valid information about the App being debugged. if let Ok(cartridge) = CartridgeLoader::load(path) { firmware.os.initialize_vm(&mut firmware.vm, &cartridge); } match TcpListener::bind(format!("127.0.0.1:{}", debug_port)) { Ok(listener) => { // Set listener to non-blocking so it doesn't halt the main loop. listener.set_nonblocking(true).expect("Cannot set non-blocking"); self.listener = Some(listener); println!("[Debugger] Listening for start command on port {}...", debug_port); } Err(e) => { eprintln!("[Debugger] Failed to bind to port {}: {}", debug_port, e); } } println!("[Debugger] (Or press D to start execution)"); } } /// Sends a structured response to the connected debugger client. fn send_response(&mut self, resp: DebugResponse) { if let Some(stream) = &mut self.stream { if let Ok(json) = serde_json::to_string(&resp) { let _ = stream.write_all(json.as_bytes()); let _ = stream.write_all(b"\n"); } } } /// Sends an asynchronous event to the connected debugger client. fn send_event(&mut self, event: DebugEvent) { if let Some(stream) = &mut self.stream { if let Ok(json) = serde_json::to_string(&event) { let _ = stream.write_all(json.as_bytes()); let _ = stream.write_all(b"\n"); } } } /// Main maintenance method called by the HostRunner every iteration. /// It handles new connections and processes incoming commands. pub fn check_commands(&mut self, firmware: &mut Firmware, hardware: &mut Hardware) { // 1. Accept new client connections. if let Some(listener) = &self.listener { if let Ok((stream, _addr)) = listener.accept() { // Currently, only one debugger client is supported at a time. if self.stream.is_none() { println!("[Debugger] Connection received!"); stream.set_nonblocking(true).expect("Cannot set non-blocking on stream"); self.stream = Some(stream); // Immediately send the Handshake message to identify the Runtime and App. let handshake = DebugResponse::Handshake { protocol_version: DEVTOOLS_PROTOCOL_VERSION, runtime_version: "0.1".to_string(), cartridge: HandshakeCartridge { app_id: firmware.os.current_app_id, title: firmware.os.current_cartridge_title.clone(), app_version: firmware.os.current_cartridge_app_version.clone(), app_mode: firmware.os.current_cartridge_app_mode, }, }; self.send_response(handshake); } else { println!("[Debugger] Connection refused: already connected."); } } } // 2. Read and process pending commands from the network stream. if let Some(mut stream) = self.stream.take() { let mut buf = [0u8; 4096]; match stream.read(&mut buf) { Ok(0) => { // TCP socket closed by the client. println!("[Debugger] Connection closed by remote."); self.stream = None; // Resume VM execution if it was paused waiting for the debugger. firmware.os.paused = false; self.waiting_for_start = false; } Ok(n) => { let data = &buf[..n]; let msg = String::from_utf8_lossy(data); self.stream = Some(stream); // Support multiple JSON messages in a single TCP packet. for line in msg.lines() { let trimmed = line.trim(); if trimmed.is_empty() { continue; } if let Ok(cmd) = serde_json::from_str::(trimmed) { self.handle_command(cmd, firmware, hardware); } } } Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { // No data available right now, continue. self.stream = Some(stream); } Err(e) => { eprintln!("[Debugger] Connection error: {}", e); self.stream = None; firmware.os.paused = false; self.waiting_for_start = false; } } } // 3. Push events (logs, telemetry) to the client. if self.stream.is_some() { self.stream_events(firmware); } } /// Dispatches a specific DebugCommand to the system components. fn handle_command(&mut self, cmd: DebugCommand, firmware: &mut Firmware, hardware: &mut Hardware) { match cmd { DebugCommand::Ok | DebugCommand::Start => { if self.waiting_for_start { println!("[Debugger] Starting execution..."); self.waiting_for_start = false; } firmware.os.paused = false; } DebugCommand::Pause => { firmware.os.paused = true; } DebugCommand::Resume => { firmware.os.paused = false; } DebugCommand::Step => { // Execute exactly one instruction and keep paused. firmware.os.paused = true; let _ = firmware.os.debug_step_instruction(&mut firmware.vm, hardware); } DebugCommand::StepFrame => { // Execute until the end of the current logical frame. firmware.os.paused = false; firmware.os.debug_step_request = true; } DebugCommand::GetState => { // Return detailed VM register and stack state. let stack_top = firmware.vm.operand_stack.iter() .rev().take(10).cloned().collect(); let resp = DebugResponse::GetState { pc: firmware.vm.pc, stack_top, frame_index: firmware.os.logical_frame_index, app_id: firmware.os.current_app_id, }; self.send_response(resp); } DebugCommand::SetBreakpoint { pc } => { firmware.vm.breakpoints.insert(pc); } DebugCommand::ListBreakpoints => { let pcs = firmware.vm.breakpoints.iter().cloned().collect(); self.send_response(DebugResponse::Breakpoints { pcs }); } DebugCommand::ClearBreakpoint { pc } => { firmware.vm.breakpoints.remove(&pc); } } } /// Scans the system for new information to push to the debugger client. fn stream_events(&mut self, firmware: &mut Firmware) { // 1. Process and send new log entries. let new_events = firmware.os.log_service.get_after(self.last_log_seq); for event in new_events { self.last_log_seq = event.seq; // Map specific internal log tags to protocol events. if event.tag == 0xDEB1 { self.send_event(DebugEvent::BreakpointHit { pc: firmware.vm.pc, frame_index: firmware.os.logical_frame_index, }); } // Map Certification tags (0xCA01-0xCA03) to 'Cert' protocol events. if event.tag >= 0xCA01 && event.tag <= 0xCA03 { let tel = &firmware.os.telemetry_last; let cert_config = &firmware.os.certifier.config; let (rule, used, limit) = match event.tag { 0xCA01 => ( "cycles_budget".to_string(), tel.cycles_used, cert_config.cycles_budget_per_frame.unwrap_or(0), ), 0xCA02 => ( "max_syscalls".to_string(), tel.syscalls as u64, cert_config.max_syscalls_per_frame.unwrap_or(0) as u64, ), 0xCA03 => ( "max_host_cpu_us".to_string(), tel.host_cpu_time_us, cert_config.max_host_cpu_us_per_frame.unwrap_or(0), ), _ => ("unknown".to_string(), 0, 0), }; self.send_event(DebugEvent::Cert { rule, used, limit, frame_index: firmware.os.logical_frame_index, }); } self.send_event(DebugEvent::Log { level: format!("{:?}", event.level), source: format!("{:?}", event.source), msg: event.msg.clone(), }); } // 2. Send telemetry snapshots at the completion of every frame. let current_frame = firmware.os.logical_frame_index; if current_frame > self.last_telemetry_frame { let tel = &firmware.os.telemetry_last; self.send_event(DebugEvent::Telemetry { frame_index: tel.frame_index, vm_steps: tel.vm_steps, syscalls: tel.syscalls, cycles: tel.cycles_used, cycles_budget: tel.cycles_budget, host_cpu_time_us: tel.host_cpu_time_us, violations: tel.violations, gfx_used_bytes: tel.gfx_used_bytes, gfx_inflight_bytes: tel.gfx_inflight_bytes, gfx_slots_occupied: tel.gfx_slots_occupied, audio_used_bytes: tel.audio_used_bytes, audio_inflight_bytes: tel.audio_inflight_bytes, audio_slots_occupied: tel.audio_slots_occupied, }); self.last_telemetry_frame = current_frame; } } }