1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4pub mod frame;
10
11#[derive(Serialize, Deserialize)]
14pub struct ExecRequest {
15 pub argv: Vec<String>,
16 #[serde(default)]
17 pub env: HashMap<String, String>,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
19 pub tty: Option<bool>,
20 #[serde(default, skip_serializing_if = "Option::is_none")]
21 pub rows: Option<u16>,
22 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub cols: Option<u16>,
24 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub cwd: Option<String>,
26}
27
28#[derive(Debug, Clone)]
32pub struct PortMapping {
33 pub host_port: u16,
34 pub guest_port: u16,
35}
36
37#[derive(Serialize, Deserialize)]
39pub struct ForwardRequest {
40 pub port: u16,
41}
42
43#[derive(Serialize, Deserialize)]
45pub struct ForwardResponse {
46 pub status: String,
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub message: Option<String>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct MountRequest {
56 pub tag: String,
57 pub guest_path: String,
58 #[serde(default = "default_true")]
61 pub read_only: bool,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct MountResponse {
67 pub tag: String,
68 pub ok: bool,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 pub error: Option<String>,
71}
72
73#[derive(Serialize, Deserialize)]
76pub struct ReadFileRequest {
77 pub path: String,
78}
79
80#[derive(Serialize, Deserialize)]
81pub struct WriteFileRequest {
82 pub path: String,
83 pub len: u64,
84}
85
86#[derive(Serialize, Deserialize)]
87pub struct WriteFileResponse {
88 pub ok: bool,
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub error: Option<String>,
91}
92
93#[derive(Serialize, Deserialize)]
96pub struct FsOkResponse {
97 pub ok: bool,
98 #[serde(skip_serializing_if = "Option::is_none")]
99 pub error: Option<String>,
100}
101
102#[derive(Serialize, Deserialize)]
103pub struct DownloadRequest {
104 pub url: String,
105 pub path: String,
106 #[serde(default)]
108 pub extract: bool,
109 #[serde(default)]
114 pub strip_components: u32,
115}
116
117#[derive(Serialize, Deserialize)]
118pub struct DownloadProgress {
119 pub bytes_downloaded: u64,
120 pub total_bytes: Option<u64>,
121}
122
123#[derive(Serialize, Deserialize)]
124pub struct MkdirRequest {
125 pub path: String,
126 #[serde(default = "default_true")]
127 pub recursive: bool,
128}
129
130#[derive(Serialize, Deserialize)]
131pub struct ReadDirRequest {
132 pub path: String,
133}
134
135#[derive(Serialize, Deserialize)]
136pub struct DirEntry {
137 pub name: String,
138 #[serde(rename = "type")]
139 pub entry_type: String,
140 pub size: u64,
141}
142
143#[derive(Serialize, Deserialize)]
144pub struct ReadDirResponse {
145 pub entries: Vec<DirEntry>,
146}
147
148#[derive(Serialize, Deserialize)]
149pub struct StatRequest {
150 pub path: String,
151}
152
153#[derive(Serialize, Deserialize)]
154pub struct StatResponse {
155 pub size: u64,
156 pub mode: u32,
157 pub mtime: u64,
158 pub is_dir: bool,
159 pub is_file: bool,
160 pub is_symlink: bool,
161}
162
163#[derive(Serialize, Deserialize)]
164pub struct RemoveRequest {
165 pub path: String,
166 #[serde(default)]
167 pub recursive: bool,
168}
169
170#[derive(Serialize, Deserialize)]
173pub struct DiscardRequest {
174 pub path: String,
176}
177
178#[derive(Serialize, Deserialize)]
179pub struct RenameRequest {
180 pub old_path: String,
181 pub new_path: String,
182}
183
184#[derive(Serialize, Deserialize)]
185pub struct CopyRequest {
186 pub src: String,
187 pub dst: String,
188 #[serde(default)]
189 pub recursive: bool,
190}
191
192#[derive(Serialize, Deserialize)]
193pub struct ChmodRequest {
194 pub path: String,
195 pub mode: u32,
196}
197
198#[derive(Serialize, Deserialize)]
201pub struct WatchRequest {
202 pub path: String,
203 #[serde(default = "default_true")]
204 pub recursive: bool,
205}
206
207fn default_true() -> bool {
208 true
209}
210
211pub mod watch_kind {
213 pub const CREATE: u8 = 0x01;
214 pub const MODIFY: u8 = 0x02;
215 pub const DELETE: u8 = 0x03;
216 pub const RENAME: u8 = 0x04;
217}
218
219#[derive(Debug, Clone)]
221pub struct WatchEvent {
222 pub kind: u8,
223 pub path: String,
224}
225
226impl WatchEvent {
227 pub fn encode(&self) -> Vec<u8> {
229 let mut buf = Vec::with_capacity(1 + self.path.len());
230 buf.push(self.kind);
231 buf.extend_from_slice(self.path.as_bytes());
232 buf
233 }
234
235 pub fn decode(payload: &[u8]) -> Option<Self> {
237 if payload.is_empty() {
238 return None;
239 }
240 let kind = payload[0];
241 let path = std::str::from_utf8(&payload[1..]).ok()?.to_string();
242 Some(Self { kind, path })
243 }
244}
245
246pub const VSOCK_PORT: u32 = 1024;
247pub const VSOCK_PORT_FORWARD: u32 = 1025;
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn mount_request_read_only_true_by_default() {
255 let json = r#"{"tag":"mount0","guest_path":"/workspace"}"#;
256 let req: MountRequest = serde_json::from_str(json).unwrap();
257 assert!(req.read_only);
258 }
259
260 #[test]
261 fn mount_request_read_only_false_roundtrips() {
262 let req = MountRequest {
263 tag: "mount0".into(),
264 guest_path: "/workspace".into(),
265 read_only: false,
266 };
267 let json = serde_json::to_string(&req).unwrap();
268 let req2: MountRequest = serde_json::from_str(&json).unwrap();
269 assert!(!req2.read_only);
270 }
271}