Skip to main content

dcp/binary/
envelope.rs

1//! Binary Message Envelope (BME) - 8-byte fixed header for DCP messages.
2
3use crate::DCPError;
4
5/// DCP v1 magic number
6pub const DCP_MAGIC: u16 = 0xDC01;
7
8/// Binary Message Envelope - 8 bytes fixed header
9#[repr(C, packed)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct BinaryMessageEnvelope {
12    /// Magic number: 0xDC01 for DCP v1
13    pub magic: u16,
14    /// Message type (Tool=1, Resource=2, Prompt=3, Response=4, Error=5, Stream=6)
15    pub message_type: u8,
16    /// Flags: bit 0=streaming, bit 1=compressed, bit 2=signed
17    pub flags: u8,
18    /// Payload length in bytes
19    pub payload_len: u32,
20}
21
22/// Message types enumeration
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[repr(u8)]
25pub enum MessageType {
26    Tool = 1,
27    Resource = 2,
28    Prompt = 3,
29    Response = 4,
30    Error = 5,
31    Stream = 6,
32}
33
34impl MessageType {
35    /// Convert from u8 to MessageType
36    pub fn from_u8(value: u8) -> Option<Self> {
37        match value {
38            1 => Some(Self::Tool),
39            2 => Some(Self::Resource),
40            3 => Some(Self::Prompt),
41            4 => Some(Self::Response),
42            5 => Some(Self::Error),
43            6 => Some(Self::Stream),
44            _ => None,
45        }
46    }
47}
48
49/// Flag bits
50#[allow(non_snake_case)]
51pub mod Flags {
52    pub const STREAMING: u8 = 0b0000_0001;
53    pub const COMPRESSED: u8 = 0b0000_0010;
54    pub const SIGNED: u8 = 0b0000_0100;
55}
56
57impl BinaryMessageEnvelope {
58    /// Size of the envelope in bytes
59    pub const SIZE: usize = 8;
60
61    /// Create a new envelope
62    pub fn new(message_type: MessageType, flags: u8, payload_len: u32) -> Self {
63        Self {
64            magic: DCP_MAGIC,
65            message_type: message_type as u8,
66            flags,
67            payload_len,
68        }
69    }
70
71    /// Parse envelope from bytes - O(1) via pointer cast
72    #[inline(always)]
73    pub fn from_bytes(bytes: &[u8]) -> Result<&Self, DCPError> {
74        if bytes.len() < Self::SIZE {
75            return Err(DCPError::InsufficientData);
76        }
77        // SAFETY: We've verified the slice is at least 8 bytes
78        let envelope = unsafe { &*(bytes.as_ptr() as *const Self) };
79        if envelope.magic != DCP_MAGIC {
80            return Err(DCPError::InvalidMagic);
81        }
82        Ok(envelope)
83    }
84
85    /// Parse envelope from mutable bytes - O(1) via pointer cast
86    #[inline(always)]
87    pub fn from_bytes_mut(bytes: &mut [u8]) -> Result<&mut Self, DCPError> {
88        if bytes.len() < Self::SIZE {
89            return Err(DCPError::InsufficientData);
90        }
91        // SAFETY: We've verified the slice is at least 8 bytes
92        let envelope = unsafe { &mut *(bytes.as_mut_ptr() as *mut Self) };
93        if envelope.magic != DCP_MAGIC {
94            return Err(DCPError::InvalidMagic);
95        }
96        Ok(envelope)
97    }
98
99    /// Serialize envelope to bytes - zero allocation
100    #[inline(always)]
101    pub fn as_bytes(&self) -> &[u8] {
102        // SAFETY: BinaryMessageEnvelope is repr(C, packed) and has no padding
103        unsafe { std::slice::from_raw_parts(self as *const Self as *const u8, Self::SIZE) }
104    }
105
106    /// Get the message type as enum
107    pub fn get_message_type(&self) -> Option<MessageType> {
108        MessageType::from_u8(self.message_type)
109    }
110
111    /// Check if streaming flag is set
112    #[inline(always)]
113    pub fn is_streaming(&self) -> bool {
114        self.flags & Flags::STREAMING != 0
115    }
116
117    /// Check if compressed flag is set
118    #[inline(always)]
119    pub fn is_compressed(&self) -> bool {
120        self.flags & Flags::COMPRESSED != 0
121    }
122
123    /// Check if signed flag is set
124    #[inline(always)]
125    pub fn is_signed(&self) -> bool {
126        self.flags & Flags::SIGNED != 0
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn test_envelope_size() {
136        assert_eq!(std::mem::size_of::<BinaryMessageEnvelope>(), 8);
137    }
138
139    #[test]
140    fn test_envelope_round_trip() {
141        let envelope =
142            BinaryMessageEnvelope::new(MessageType::Tool, Flags::STREAMING | Flags::SIGNED, 1024);
143        let bytes = envelope.as_bytes();
144        let parsed = BinaryMessageEnvelope::from_bytes(bytes).unwrap();
145
146        // Copy values from packed struct before comparing
147        let magic = parsed.magic;
148        let message_type = parsed.message_type;
149        let flags = parsed.flags;
150        let payload_len = parsed.payload_len;
151
152        assert_eq!(magic, DCP_MAGIC);
153        assert_eq!(message_type, MessageType::Tool as u8);
154        assert_eq!(flags, Flags::STREAMING | Flags::SIGNED);
155        assert_eq!(payload_len, 1024);
156    }
157
158    #[test]
159    fn test_invalid_magic() {
160        let mut bytes = [0u8; 8];
161        bytes[0] = 0xFF;
162        bytes[1] = 0xFF;
163        assert_eq!(
164            BinaryMessageEnvelope::from_bytes(&bytes),
165            Err(DCPError::InvalidMagic)
166        );
167    }
168
169    #[test]
170    fn test_insufficient_data() {
171        let bytes = [0u8; 4];
172        assert_eq!(
173            BinaryMessageEnvelope::from_bytes(&bytes),
174            Err(DCPError::InsufficientData)
175        );
176    }
177
178    #[test]
179    fn test_flag_helpers() {
180        let envelope = BinaryMessageEnvelope::new(
181            MessageType::Stream,
182            Flags::STREAMING | Flags::COMPRESSED,
183            0,
184        );
185        assert!(envelope.is_streaming());
186        assert!(envelope.is_compressed());
187        assert!(!envelope.is_signed());
188    }
189}