⎇ b7d9d7abd94594597414cb59f27511b3ee01b789
crates/runtime/sandokan-daemon/src/protocol.rs
History
Who touched what
raw
feat(sandokan-daemon): B1.3 — DaemonEngine + protocolo wire
1//! Protocolo wire del daemon: requests/responses + framing.
2//!
3//! Encoding: postcard. Framing: prefijo de longitud `u32` little-endian
4//! seguido de los bytes postcard. Mismo patrón que el wire de shuma.
5
6use sandokan_core::{EngineError, ExecHandle, Intent, TelemetryFrame};
7use sandokan_lifecycle::LifecycleState;
8use serde::{Deserialize, Serialize};
9use tokio::io::{AsyncReadExt, AsyncWriteExt};
10use ulid::Ulid;
11
12/// Request del cliente al daemon. Espeja los métodos de `Engine`.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub enum DaemonRequest {
15 Run(Intent),
16 Stop { card_id: Ulid, grace_ms: u64 },
17 List,
18 Status { card_id: Ulid },
19 Telemetry { card_id: Ulid },
20}
21
22/// Response del daemon al cliente. Una variante por resultado posible.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub enum DaemonResponse {
25 Ran(ExecHandle),
26 Stopped,
27 Listed(Vec<ExecHandle>),
28 Status(LifecycleState),
29 Telemetry(TelemetryFrame),
30 Err(EngineError),
31}
32
33/// Límite defensivo de tamaño de frame (16 MiB). Un Intent con una Card
34/// grande sigue cabiendo; protege contra frames corruptos/maliciosos.
35pub const MAX_FRAME: u32 = 16 * 1024 * 1024;
36
37/// Escribe un valor serializable como frame length-prefixed.
38pub async fn write_frame<W, T>(w: &mut W, value: &T) -> std::io::Result<()>
39where
40 W: AsyncWriteExt + Unpin,
41 T: Serialize,
42{
43 let bytes = postcard::to_stdvec(value)
44 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
45 if bytes.len() as u64 > MAX_FRAME as u64 {
46 return Err(std::io::Error::new(
47 std::io::ErrorKind::InvalidData,
48 "frame excede MAX_FRAME",
49 ));
50 }
51 w.write_all(&(bytes.len() as u32).to_le_bytes()).await?;
52 w.write_all(&bytes).await?;
53 w.flush().await?;
54 Ok(())
55}
56
57/// Lee un frame length-prefixed y lo deserializa.
58pub async fn read_frame<R, T>(r: &mut R) -> std::io::Result<T>
59where
60 R: AsyncReadExt + Unpin,
61 T: for<'de> Deserialize<'de>,
62{
63 let mut len_buf = [0u8; 4];
64 r.read_exact(&mut len_buf).await?;
65 let len = u32::from_le_bytes(len_buf);
66 if len > MAX_FRAME {
67 return Err(std::io::Error::new(
68 std::io::ErrorKind::InvalidData,
69 "frame entrante excede MAX_FRAME",
70 ));
71 }
72 let mut buf = vec![0u8; len as usize];
73 r.read_exact(&mut buf).await?;
74 postcard::from_bytes(&buf)
75 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
76}