1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
// DAP Server - Recording and Network Methods
// Split from server.rs for file health compliance
//
// Contains: Sprint 76 CAPTURE-002 recording capture + Sprint 74 DEBUG-002 TCP server
// NOTE: This file is included via include!() - no `use` imports allowed
impl DapServer {
// Sprint 76 - CAPTURE-002: Recording Capture Methods
/// Generate a unique recording file path with timestamp
///
/// Format: session-{timestamp}.pmat
fn generate_recording_path(&self) -> Option<PathBuf> {
use std::time::{SystemTime, UNIX_EPOCH};
let recording_dir = self.recording_dir.as_ref()?;
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).ok()?.as_secs();
Some(recording_dir.join(format!("session-{}.pmat", timestamp)))
}
/// Initialize recording on session start
///
/// Creates recording directory if needed and sets up ExecutionRecorder
fn start_recording(&self, program: &str, args: Vec<String>) -> anyhow::Result<()> {
// Only start recording if recording_dir is configured
let recording_dir = match &self.recording_dir {
Some(dir) => dir,
None => return Ok(()), // No recording configured
};
// Create recording directory if it doesn't exist
if !recording_dir.exists() {
std::fs::create_dir_all(recording_dir)
.map_err(|e| anyhow::anyhow!("Failed to create recording directory: {}", e))?;
}
// Generate recording file path
let recording_path = self
.generate_recording_path()
.ok_or_else(|| anyhow::anyhow!("Failed to generate recording path"))?;
// Create recording file
let file = File::create(&recording_path)
.map_err(|e| anyhow::anyhow!("Failed to create recording file: {}", e))?;
// Create ExecutionRecorder with writer
let dap_server_arc = Arc::new(Mutex::new(DapServer::new()));
let mut recorder =
ExecutionRecorder::with_writer(file, program.to_string(), args, dap_server_arc)?;
// Start recording (enables snapshot capture)
recorder.start_recording();
// Store recorder and path
*self
.execution_recorder
.lock()
.expect("Mutex should not be poisoned") = Some(recorder);
*self
.recording_path
.lock()
.expect("Mutex should not be poisoned") = Some(recording_path);
Ok(())
}
/// Finalize and save recording
///
/// Called on disconnect or terminate to complete the .pmat file
fn finalize_recording(&self) -> anyhow::Result<Option<PathBuf>> {
let mut recorder_guard = self
.execution_recorder
.lock()
.expect("Mutex should not be poisoned");
let recorder = recorder_guard.take();
if let Some(recorder) = recorder {
recorder.finalize()?;
let path = self
.recording_path
.lock()
.expect("Mutex should not be poisoned")
.clone();
return Ok(path);
}
Ok(None)
}
/// CAPTURE-002: Attempt to capture a snapshot if recording is active
///
/// This is called on debug events (breakpoint hits, step commands) to capture
/// execution state. Silently fails if recording is not active or capture fails.
fn capture_snapshot_if_recording(&self) {
let mut recorder_guard = match self.execution_recorder.lock() {
Ok(guard) => guard,
Err(_) => return, // Lock poisoned, skip capture
};
if let Some(ref mut recorder) = *recorder_guard {
// Only attempt capture if recorder is in recording state
if recorder.is_recording() {
if let Err(e) = recorder.capture_snapshot() {
eprintln!("Warning: Failed to capture snapshot: {}", e);
// Continue execution even if snapshot capture fails
}
}
}
}
// Sprint 74 - DEBUG-002: DAP Server CLI Handler
/// Run the DAP server on the specified port
///
/// This starts an async TCP server that listens for DAP protocol connections.
/// The server runs until the async task is aborted (e.g., via Ctrl+C).
///
/// # Arguments
/// * `port` - Port number to bind to (e.g., 5678)
/// * `host` - Host address to bind to (e.g., "127.0.0.1")
///
/// # Returns
/// * `Ok(())` if server starts and shuts down cleanly
/// * `Err` if port binding fails (e.g., "address already in use")
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub async fn run(&self, port: u16, host: String) -> anyhow::Result<()> {
use tokio::net::TcpListener;
// Bind to TCP port
let addr = format!("{}:{}", host, port);
let listener = TcpListener::bind(&addr)
.await
.map_err(|e| anyhow::anyhow!("Failed to bind to {}: {}", addr, e))?;
// Server is now listening - accept connections in a loop
loop {
// Accept incoming connection
let (_stream, _addr) = listener.accept().await?;
// Minimal implementation: just accept and drop connections
// Future enhancement: read DAP messages from stream and call handle_request()
}
}
}