1mod abi;
20
21use std::cell::RefCell;
22use std::time::Duration;
23
24#[repr(u32)]
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum Kind {
28 Message = 0,
30 ConnEstablished = 1,
32 ConnData = 2,
34 ConnClosed = 3,
38}
39
40impl Kind {
41 fn from_u32(v: u32) -> Kind {
42 match v {
43 0 => Kind::Message,
44 1 => Kind::ConnEstablished,
45 2 => Kind::ConnData,
46 _ => Kind::ConnClosed,
47 }
48 }
49}
50
51#[derive(Debug, Clone)]
53pub struct Message {
54 pub kind: Kind,
56 pub conn_id: u32,
59 pub data: Vec<u8>,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub struct UnknownConnection(pub u32);
68
69impl std::fmt::Display for UnknownConnection {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 write!(f, "execsandbox: unknown connection id {}", self.0)
72 }
73}
74
75impl std::error::Error for UnknownConnection {}
76
77trait Host {
83 fn send(&self, dest: i32, ptr: *const u8, len: i32);
84 fn recv(&self, meta_ptr: *mut u8, buf_ptr: *mut u8, buf_cap: i32, timeout_ms: i32) -> i32;
85 fn conn_write(&self, conn_id: i32, ptr: *const u8, len: i32) -> i32;
86 fn max_frame(&self) -> i32;
87}
88
89struct RealHost;
90
91#[cfg(target_arch = "wasm32")]
92impl Host for RealHost {
93 fn send(&self, dest: i32, ptr: *const u8, len: i32) {
94 unsafe { abi::send(dest, ptr, len) }
95 }
96 fn recv(&self, meta_ptr: *mut u8, buf_ptr: *mut u8, buf_cap: i32, timeout_ms: i32) -> i32 {
97 unsafe { abi::recv(meta_ptr, buf_ptr, buf_cap, timeout_ms) }
98 }
99 fn conn_write(&self, conn_id: i32, ptr: *const u8, len: i32) -> i32 {
100 unsafe { abi::conn_write(conn_id, ptr, len) }
101 }
102 fn max_frame(&self) -> i32 {
103 unsafe { abi::max_frame() }
104 }
105}
106
107#[cfg(not(target_arch = "wasm32"))]
108impl Host for RealHost {
109 fn send(&self, _dest: i32, _ptr: *const u8, _len: i32) {
110 unreachable!("execsandbox: only usable when built for a wasm32 target")
111 }
112 fn recv(&self, _meta_ptr: *mut u8, _buf_ptr: *mut u8, _buf_cap: i32, _timeout_ms: i32) -> i32 {
113 unreachable!("execsandbox: only usable when built for a wasm32 target")
114 }
115 fn conn_write(&self, _conn_id: i32, _ptr: *const u8, _len: i32) -> i32 {
116 unreachable!("execsandbox: only usable when built for a wasm32 target")
117 }
118 fn max_frame(&self) -> i32 {
119 unreachable!("execsandbox: only usable when built for a wasm32 target")
120 }
121}
122
123thread_local! {
124 static RECV_BUF: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
128}
129
130pub fn send(dest: u32, data: &[u8]) {
142 send_with(&RealHost, dest, data)
143}
144
145fn send_with<H: Host>(host: &H, dest: u32, data: &[u8]) {
146 host.send(dest as i32, data.as_ptr(), data.len() as i32);
147}
148
149pub fn recv(timeout: Option<Duration>) -> Option<Message> {
167 let timeout_ms = duration_to_timeout_ms(timeout);
168 RECV_BUF.with(|buf| recv_with(&RealHost, &mut buf.borrow_mut(), timeout_ms))
169}
170
171fn duration_to_timeout_ms(timeout: Option<Duration>) -> i32 {
172 match timeout {
173 None => -1,
174 Some(d) => d.as_millis().min(i32::MAX as u128) as i32,
175 }
176}
177
178fn recv_with<H: Host>(host: &H, buf: &mut Vec<u8>, timeout_ms: i32) -> Option<Message> {
179 if buf.is_empty() {
180 let size = host.max_frame();
181 buf.resize(if size > 0 { size as usize } else { 4096 }, 0);
182 }
183
184 let mut meta = [0u8; 8];
185 loop {
186 let n = host.recv(
187 meta.as_mut_ptr(),
188 buf.as_mut_ptr(),
189 buf.len() as i32,
190 timeout_ms,
191 );
192 if n == -1 {
193 return None;
194 }
195 if n < -1 {
196 buf.resize((-(n + 1)) as usize, 0);
197 continue;
198 }
199
200 let kind_raw = u32::from_le_bytes(meta[0..4].try_into().unwrap());
201 if kind_raw > Kind::ConnClosed as u32 {
202 continue;
209 }
210 let conn_id = u32::from_le_bytes(meta[4..8].try_into().unwrap());
211 let data = buf[..n as usize].to_vec();
212 return Some(Message {
213 kind: Kind::from_u32(kind_raw),
214 conn_id,
215 data,
216 });
217 }
218}
219
220pub fn conn_write(conn_id: u32, data: &[u8]) -> Result<(), UnknownConnection> {
232 conn_write_with(&RealHost, conn_id, data)
233}
234
235fn conn_write_with<H: Host>(host: &H, conn_id: u32, data: &[u8]) -> Result<(), UnknownConnection> {
236 if host.conn_write(conn_id as i32, data.as_ptr(), data.len() as i32) == -1 {
237 return Err(UnknownConnection(conn_id));
238 }
239 Ok(())
240}
241
242pub fn max_frame() -> usize {
249 max_frame_with(&RealHost)
250}
251
252fn max_frame_with<H: Host>(host: &H) -> usize {
253 host.max_frame() as usize
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259 use std::rc::Rc;
260
261 type SendFn = Box<dyn FnMut(i32, *const u8, i32)>;
262 type RecvFn = Box<dyn FnMut(*mut u8, *mut u8, i32, i32) -> i32>;
263 type ConnWriteFn = Box<dyn FnMut(i32, *const u8, i32) -> i32>;
264 type MaxFrameFn = Box<dyn FnMut() -> i32>;
265
266 #[derive(Default)]
267 struct MockHost {
268 send: RefCell<Option<SendFn>>,
269 recv: RefCell<Option<RecvFn>>,
270 conn_write: RefCell<Option<ConnWriteFn>>,
271 max_frame: RefCell<Option<MaxFrameFn>>,
272 }
273
274 impl Host for MockHost {
275 fn send(&self, dest: i32, ptr: *const u8, len: i32) {
276 (self
277 .send
278 .borrow_mut()
279 .as_mut()
280 .expect("send not configured"))(dest, ptr, len)
281 }
282 fn recv(&self, meta_ptr: *mut u8, buf_ptr: *mut u8, buf_cap: i32, timeout_ms: i32) -> i32 {
283 (self
284 .recv
285 .borrow_mut()
286 .as_mut()
287 .expect("recv not configured"))(meta_ptr, buf_ptr, buf_cap, timeout_ms)
288 }
289 fn conn_write(&self, conn_id: i32, ptr: *const u8, len: i32) -> i32 {
290 (self
291 .conn_write
292 .borrow_mut()
293 .as_mut()
294 .expect("conn_write not configured"))(conn_id, ptr, len)
295 }
296 fn max_frame(&self) -> i32 {
297 (self
298 .max_frame
299 .borrow_mut()
300 .as_mut()
301 .expect("max_frame not configured"))()
302 }
303 }
304
305 fn fake_recv_once(
309 kind: Kind,
310 conn_id: u32,
311 payload: &'static [u8],
312 ) -> impl FnMut(*mut u8, *mut u8, i32, i32) -> i32 {
313 let mut served = false;
314 move |meta_ptr, buf_ptr, buf_cap, _timeout_ms| {
315 if served {
316 return -1;
317 }
318 served = true;
319 assert!(
320 payload.len() as i32 <= buf_cap,
321 "fake_recv_once: payload ({} bytes) does not fit in the buffer given by recv ({} bytes)",
322 payload.len(),
323 buf_cap
324 );
325 unsafe {
326 let meta = std::slice::from_raw_parts_mut(meta_ptr, 8);
327 meta[0..4].copy_from_slice(&(kind as u32).to_le_bytes());
328 meta[4..8].copy_from_slice(&conn_id.to_le_bytes());
329 let buf = std::slice::from_raw_parts_mut(buf_ptr, buf_cap as usize);
330 buf[..payload.len()].copy_from_slice(payload);
331 }
332 payload.len() as i32
333 }
334 }
335
336 #[test]
337 fn send_passes_dest_and_length() {
338 let host = MockHost::default();
339 let captured = Rc::new(RefCell::new((0i32, 0i32, false)));
340 let captured2 = captured.clone();
341 *host.send.borrow_mut() = Some(Box::new(move |dest, ptr, len| {
342 *captured2.borrow_mut() = (dest, len, !ptr.is_null());
343 }));
344
345 send_with(&host, 3, b"hi");
346
347 let (dest, len, ptr_nonnull) = *captured.borrow();
348 assert_eq!(dest, 3);
349 assert_eq!(len, 2);
350 assert!(
351 ptr_nonnull,
352 "send should be called with a non-null pointer for non-empty data"
353 );
354 }
355
356 #[test]
357 fn recv_success_decodes_kind_conn_id_and_data() {
358 let host = MockHost::default();
359 *host.max_frame.borrow_mut() = Some(Box::new(|| 4096));
360 *host.recv.borrow_mut() = Some(Box::new(fake_recv_once(Kind::ConnData, 7, b"payload")));
361
362 let msg = recv_with(&host, &mut Vec::new(), 1000).expect("expected a message");
363 assert_eq!(msg.kind, Kind::ConnData);
364 assert_eq!(msg.conn_id, 7);
365 assert_eq!(msg.data, b"payload");
366 }
367
368 #[test]
369 fn recv_reports_timeout_as_none() {
370 let host = MockHost::default();
371 *host.max_frame.borrow_mut() = Some(Box::new(|| 4096));
372 *host.recv.borrow_mut() = Some(Box::new(|_, _, _, _| -1));
373
374 assert!(recv_with(&host, &mut Vec::new(), 100).is_none());
375 }
376
377 #[test]
378 fn duration_to_timeout_ms_matches_abi_convention() {
379 assert_eq!(
380 duration_to_timeout_ms(None),
381 -1,
382 "no timeout should block forever (-1)"
383 );
384 assert_eq!(
385 duration_to_timeout_ms(Some(Duration::ZERO)),
386 0,
387 "a zero duration should return immediately (0)"
388 );
389 assert_eq!(
390 duration_to_timeout_ms(Some(Duration::from_millis(250))),
391 250,
392 "a positive duration should convert to milliseconds"
393 );
394 }
395
396 #[test]
397 fn recv_grows_buffer_on_undersized_reply() {
398 let host = MockHost::default();
399 *host.max_frame.borrow_mut() = Some(Box::new(|| 4)); let payload: &'static [u8] = b"this does not fit in 4 bytes";
401 let calls = Rc::new(RefCell::new(0));
402 let calls2 = calls.clone();
403 *host.recv.borrow_mut() = Some(Box::new(move |meta_ptr, buf_ptr, buf_cap, _timeout_ms| {
404 *calls2.borrow_mut() += 1;
405 if payload.len() as i32 > buf_cap {
406 return -(payload.len() as i32 + 1);
407 }
408 unsafe {
409 let meta = std::slice::from_raw_parts_mut(meta_ptr, 8);
410 meta[0..4].copy_from_slice(&(Kind::Message as u32).to_le_bytes());
411 meta[4..8].copy_from_slice(&0u32.to_le_bytes());
412 let buf = std::slice::from_raw_parts_mut(buf_ptr, buf_cap as usize);
413 buf[..payload.len()].copy_from_slice(payload);
414 }
415 payload.len() as i32
416 }));
417
418 let mut buf = Vec::new();
419 let msg = recv_with(&host, &mut buf, -1).expect("expected a message after the buffer grew");
420 assert_eq!(msg.data, payload);
421 assert_eq!(
422 *calls.borrow(),
423 2,
424 "recv should be called twice: undersized, then resized"
425 );
426 assert!(buf.len() >= payload.len());
427 }
428
429 #[test]
430 fn recv_skips_unknown_kind() {
431 let host = MockHost::default();
432 *host.max_frame.borrow_mut() = Some(Box::new(|| 4096));
433 let calls = Rc::new(RefCell::new(0));
434 let calls2 = calls.clone();
435 *host.recv.borrow_mut() = Some(Box::new(move |meta_ptr, buf_ptr, buf_cap, _timeout_ms| {
436 *calls2.borrow_mut() += 1;
437 let call = *calls2.borrow();
438 unsafe {
439 let meta = std::slice::from_raw_parts_mut(meta_ptr, 8);
440 let buf = std::slice::from_raw_parts_mut(buf_ptr, buf_cap as usize);
441 if call == 1 {
442 meta[0..4].copy_from_slice(&99u32.to_le_bytes());
445 meta[4..8].copy_from_slice(&0u32.to_le_bytes());
446 buf[..b"ignored".len()].copy_from_slice(b"ignored");
447 return b"ignored".len() as i32;
448 }
449 meta[0..4].copy_from_slice(&(Kind::Message as u32).to_le_bytes());
450 meta[4..8].copy_from_slice(&0u32.to_le_bytes());
451 buf[..b"real".len()].copy_from_slice(b"real");
452 b"real".len() as i32
453 }
454 }));
455
456 let msg =
457 recv_with(&host, &mut Vec::new(), -1).expect("expected the second, known-kind message");
458 assert_eq!(*calls.borrow(), 2);
459 assert_eq!(msg.kind, Kind::Message);
460 assert_eq!(msg.data, b"real");
461 }
462
463 #[test]
464 fn conn_write_success_returns_ok() {
465 let host = MockHost::default();
466 let captured = Rc::new(RefCell::new(0i32));
467 let captured2 = captured.clone();
468 *host.conn_write.borrow_mut() = Some(Box::new(move |conn_id, _, _| {
469 *captured2.borrow_mut() = conn_id;
470 0
471 }));
472
473 assert!(conn_write_with(&host, 42, b"data").is_ok());
474 assert_eq!(*captured.borrow(), 42);
475 }
476
477 #[test]
478 fn conn_write_unknown_conn_returns_error() {
479 let host = MockHost::default();
480 *host.conn_write.borrow_mut() = Some(Box::new(|_, _, _| -1));
481
482 assert_eq!(
483 conn_write_with(&host, 42, b"data"),
484 Err(UnknownConnection(42))
485 );
486 }
487
488 #[test]
489 fn max_frame_passes_through_host_value() {
490 let host = MockHost::default();
491 *host.max_frame.borrow_mut() = Some(Box::new(|| 1_048_576));
492
493 assert_eq!(max_frame_with(&host), 1_048_576);
494 }
495}