wsprism_core/protocol/hot.rs
1//! Hot Lane binary frame parsing (panic-free).
2//!
3//! Parsing rules:
4//! - Never index (`buf[0]`); always use `Buf` and `remaining()` checks.
5//! - Never `unwrap()` / `expect()` / `panic!()` in production paths.
6//! - Validate header lengths before reading optional fields.
7
8use bytes::Buf;
9use bytes::Bytes;
10
11use crate::error::{Result, WsPrismError};
12
13/// Hot Lane flag: seq (u32) is present.
14pub const HOT_FLAG_SEQ_PRESENT: u8 = 0x01;
15
16/// Parsed Hot Lane frame.
17#[derive(Debug, Clone)]
18pub struct HotFrame {
19 /// Protocol version (must be 1).
20 pub v: u8,
21 /// Service id (routes to native BinaryService).
22 pub svc_id: u8,
23 /// Opcode within that service.
24 pub opcode: u8,
25 /// Feature flags (u8).
26 pub flags: u8,
27 /// Optional sequence number.
28 pub seq: Option<u32>,
29 /// Opaque payload (zero-copy).
30 pub payload: Bytes,
31}
32
33/// Decode a Hot Lane frame from bytes.
34///
35/// Defensive against malformed input; returns structured errors instead of
36/// panicking on short buffers or unsupported versions.
37pub fn decode_hot_frame(mut buf: Bytes) -> Result<HotFrame> {
38 // Minimum header: v, svc_id, opcode, flags
39 if buf.remaining() < 4 {
40 return Err(WsPrismError::BadRequest("hot frame too short".into()));
41 }
42
43 let v = buf.get_u8();
44 if v != 1 {
45 return Err(WsPrismError::UnsupportedVersion);
46 }
47
48 let svc_id = buf.get_u8();
49 let opcode = buf.get_u8();
50 let flags = buf.get_u8();
51
52 let seq = if (flags & HOT_FLAG_SEQ_PRESENT) != 0 {
53 if buf.remaining() < 4 {
54 return Err(WsPrismError::BadRequest(
55 "seq flag set but missing u32".into(),
56 ));
57 }
58 Some(buf.get_u32_le())
59 } else {
60 None
61 };
62
63 // Remaining bytes are payload.
64 let payload = buf.copy_to_bytes(buf.remaining());
65
66 Ok(HotFrame {
67 v,
68 svc_id,
69 opcode,
70 flags,
71 seq,
72 payload,
73 })
74}