2026-05-16 14:14:00 -06:00
|
|
|
extern crate alloc;
|
|
|
|
|
|
|
|
|
|
use alloc::vec::Vec;
|
|
|
|
|
|
2026-05-28 12:41:32 -06:00
|
|
|
use crate::{DeserializeError, SerializeError};
|
|
|
|
|
|
2026-05-28 12:56:59 -06:00
|
|
|
/// Fully decoded UnShell test packet.
|
|
|
|
|
///
|
|
|
|
|
/// The current protocol tests route only on hook id, hook end state, and absolute
|
|
|
|
|
/// path. `procedure_id` is therefore a compact numeric contract id instead of a
|
|
|
|
|
/// string label; application code can maintain its own id-to-name table outside the
|
|
|
|
|
/// hot packet path if it needs human-readable names.
|
2026-05-28 18:17:01 -06:00
|
|
|
#[derive(Debug, Clone)]
|
2026-05-16 14:14:00 -06:00
|
|
|
pub struct Packet {
|
|
|
|
|
pub hook_id: u16,
|
|
|
|
|
pub end_hook: bool,
|
2026-05-28 11:48:46 -06:00
|
|
|
pub path: Vec<u32>,
|
2026-05-28 12:56:59 -06:00
|
|
|
pub procedure_id: u32,
|
2026-05-16 14:14:00 -06:00
|
|
|
pub data: Vec<u8>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Packet {
|
2026-05-28 12:56:59 -06:00
|
|
|
/// Serializes the packet into the crate's current little-endian frame format.
|
|
|
|
|
///
|
|
|
|
|
/// Layout:
|
|
|
|
|
/// - fixed header: `hook_id: u16`, `flags: u8`, padding, `path_len: u32`
|
|
|
|
|
/// - path: `path_len` little-endian `u32` segments
|
|
|
|
|
/// - body: `body_len: u32`, `procedure_id: u32`, raw `data`
|
|
|
|
|
///
|
|
|
|
|
/// Keeping `procedure_id` fixed-width removes the old string length and UTF-8
|
|
|
|
|
/// validation path. That makes deserialization a single full-packet parse,
|
|
|
|
|
/// which matches how the endpoint mock transports actually consume packets.
|
2026-05-16 14:14:00 -06:00
|
|
|
pub fn serialize(&self) -> Result<Vec<u8>, SerializeError> {
|
2026-05-28 12:56:59 -06:00
|
|
|
let path_len = u32::try_from(self.path.len()).map_err(|_| SerializeError::PathTooLarge)?;
|
2026-05-16 14:14:00 -06:00
|
|
|
|
2026-05-28 12:56:59 -06:00
|
|
|
// body = fixed procedure_id field + data bytes
|
2026-05-16 14:14:00 -06:00
|
|
|
let body_payload_len = 4usize
|
2026-05-28 12:56:59 -06:00
|
|
|
.checked_add(self.data.len())
|
2026-05-16 14:14:00 -06:00
|
|
|
.ok_or(SerializeError::BodyTooLarge)?;
|
|
|
|
|
let body_len = u32::try_from(body_payload_len).map_err(|_| SerializeError::BodyTooLarge)?;
|
|
|
|
|
|
2026-05-28 12:56:59 -06:00
|
|
|
let path_bytes = self
|
|
|
|
|
.path
|
|
|
|
|
.len()
|
|
|
|
|
.checked_mul(4)
|
|
|
|
|
.ok_or(SerializeError::PathTooLarge)?;
|
|
|
|
|
let total = 8usize
|
|
|
|
|
.checked_add(path_bytes)
|
|
|
|
|
.and_then(|n| n.checked_add(4))
|
|
|
|
|
.and_then(|n| n.checked_add(body_payload_len))
|
|
|
|
|
.ok_or(SerializeError::BodyTooLarge)?;
|
2026-05-16 14:14:00 -06:00
|
|
|
let mut buf = Vec::with_capacity(total);
|
|
|
|
|
|
|
|
|
|
// ── header ────────────────────────────────────────────────────────────
|
2026-05-28 11:48:46 -06:00
|
|
|
let flags = self.end_hook as u8;
|
2026-05-16 14:14:00 -06:00
|
|
|
buf.extend_from_slice(&self.hook_id.to_le_bytes());
|
|
|
|
|
buf.push(flags);
|
|
|
|
|
buf.push(0u8); // padding
|
|
|
|
|
buf.extend_from_slice(&path_len.to_le_bytes());
|
2026-05-28 11:48:46 -06:00
|
|
|
for &segment in &self.path {
|
|
|
|
|
buf.extend_from_slice(&segment.to_le_bytes());
|
|
|
|
|
}
|
2026-05-16 14:14:00 -06:00
|
|
|
|
|
|
|
|
// ── body ──────────────────────────────────────────────────────────────
|
|
|
|
|
buf.extend_from_slice(&body_len.to_le_bytes());
|
2026-05-28 12:56:59 -06:00
|
|
|
buf.extend_from_slice(&self.procedure_id.to_le_bytes());
|
2026-05-16 14:14:00 -06:00
|
|
|
buf.extend_from_slice(&self.data);
|
|
|
|
|
|
|
|
|
|
Ok(buf)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 12:56:59 -06:00
|
|
|
/// Deserializes a full packet from untrusted transport bytes.
|
|
|
|
|
///
|
|
|
|
|
/// This parser intentionally consumes the complete packet shape. The old
|
|
|
|
|
/// partial parse path was removed because current routing tests and mock
|
|
|
|
|
/// transports always deserialize before calling endpoint routing, so keeping a
|
|
|
|
|
/// borrowed header API only preserved unused unsafe casting complexity.
|
|
|
|
|
pub fn deserialize(buf: &[u8]) -> Result<Self, DeserializeError> {
|
2026-05-16 14:14:00 -06:00
|
|
|
// fixed prefix: hook_id (2) + flags (1) + padding (1) + path_len (4)
|
|
|
|
|
if buf.len() < 8 {
|
|
|
|
|
return Err(DeserializeError::BufferTooShort);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let hook_id = u16::from_le_bytes([buf[0], buf[1]]);
|
|
|
|
|
let flags = buf[2];
|
2026-05-28 11:48:46 -06:00
|
|
|
let end_hook = flags & 0b0000_0001 != 0;
|
2026-05-16 14:14:00 -06:00
|
|
|
let path_len = u32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]) as usize;
|
|
|
|
|
|
|
|
|
|
let path_start = 8usize;
|
|
|
|
|
let path_end = path_start
|
2026-05-28 11:48:46 -06:00
|
|
|
.checked_add(path_len * 4)
|
2026-05-16 14:14:00 -06:00
|
|
|
.ok_or(DeserializeError::PathTooLong)?;
|
|
|
|
|
|
|
|
|
|
if buf.len() < path_end {
|
|
|
|
|
return Err(DeserializeError::BufferTooShort);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 12:56:59 -06:00
|
|
|
let mut path = Vec::with_capacity(path_len);
|
|
|
|
|
for chunk in buf[path_start..path_end].chunks_exact(4) {
|
|
|
|
|
path.push(u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
|
|
|
|
|
}
|
2026-05-16 14:14:00 -06:00
|
|
|
|
|
|
|
|
// body_len prefix
|
2026-05-28 12:56:59 -06:00
|
|
|
let body_buf = &buf[path_end..];
|
2026-05-16 14:14:00 -06:00
|
|
|
if body_buf.len() < 4 {
|
|
|
|
|
return Err(DeserializeError::BufferTooShort);
|
|
|
|
|
}
|
|
|
|
|
let body_len =
|
|
|
|
|
u32::from_le_bytes([body_buf[0], body_buf[1], body_buf[2], body_buf[3]]) as usize;
|
|
|
|
|
|
|
|
|
|
let body_end = 4usize
|
|
|
|
|
.checked_add(body_len)
|
|
|
|
|
.ok_or(DeserializeError::BodyLengthMismatch)?;
|
|
|
|
|
if body_buf.len() < body_end {
|
|
|
|
|
return Err(DeserializeError::BodyLengthMismatch);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-28 12:56:59 -06:00
|
|
|
// procedure_id + data
|
2026-05-16 14:14:00 -06:00
|
|
|
let inner = &body_buf[4..body_end];
|
|
|
|
|
if inner.len() < 4 {
|
|
|
|
|
return Err(DeserializeError::BufferTooShort);
|
|
|
|
|
}
|
2026-05-28 12:56:59 -06:00
|
|
|
let procedure_id = u32::from_le_bytes([inner[0], inner[1], inner[2], inner[3]]);
|
2026-05-16 14:14:00 -06:00
|
|
|
|
2026-05-28 12:56:59 -06:00
|
|
|
let data = inner[4..].to_vec();
|
2026-05-16 14:14:00 -06:00
|
|
|
|
|
|
|
|
Ok(Self {
|
2026-05-28 12:56:59 -06:00
|
|
|
hook_id,
|
|
|
|
|
end_hook,
|
|
|
|
|
path,
|
|
|
|
|
procedure_id,
|
2026-05-16 14:14:00 -06:00
|
|
|
data,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|