Skip to main content

st_protocol/
frame.rs

1//! Frame encoding/decoding
2//!
3//! ## Frame Format
4//!
5//! ```text
6//! ┌──────┬─────────────────┬──────┐
7//! │ verb │     payload     │ 0x00 │
8//! │ 1B   │   N bytes       │ END  │
9//! └──────┴─────────────────┴──────┘
10//! ```
11
12#[cfg(feature = "alloc")]
13use alloc::vec::Vec;
14
15use crate::{Verb, Payload, END, ESC, ProtocolError, ProtocolResult, MAX_FRAME_SIZE};
16
17/// A complete protocol frame
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct Frame {
20    verb: Verb,
21    payload: Payload,
22}
23
24impl Frame {
25    /// Create a new frame
26    pub fn new(verb: Verb, payload: Payload) -> Self {
27        Frame { verb, payload }
28    }
29
30    /// Create a simple frame with no payload
31    pub fn simple(verb: Verb) -> Self {
32        Frame {
33            verb,
34            payload: Payload::empty(),
35        }
36    }
37
38    /// Get the verb
39    pub fn verb(&self) -> Verb {
40        self.verb
41    }
42
43    /// Get the payload
44    pub fn payload(&self) -> &Payload {
45        &self.payload
46    }
47
48    /// Consume and return the payload
49    pub fn into_payload(self) -> Payload {
50        self.payload
51    }
52
53    /// Encode frame for wire transmission
54    #[cfg(any(feature = "std", feature = "alloc"))]
55    pub fn encode(&self) -> Vec<u8> {
56        let encoded_payload = self.payload.encode();
57        let mut out = Vec::with_capacity(encoded_payload.len() + 2);
58
59        out.push(self.verb.as_byte());
60        out.extend_from_slice(&encoded_payload);
61        out.push(END);
62
63        out
64    }
65
66    /// Decode frame from wire format
67    pub fn decode(data: &[u8]) -> ProtocolResult<Self> {
68        // Minimum frame: verb + END = 2 bytes
69        if data.len() < 2 {
70            return Err(ProtocolError::FrameTooShort);
71        }
72
73        if data.len() > MAX_FRAME_SIZE {
74            return Err(ProtocolError::FrameTooLarge);
75        }
76
77        // Last byte must be END
78        if data[data.len() - 1] != END {
79            return Err(ProtocolError::MissingEndMarker);
80        }
81
82        // First byte is verb
83        let verb = Verb::from_byte(data[0]).ok_or(ProtocolError::InvalidVerb(data[0]))?;
84
85        // Payload is everything between verb and END
86        let payload_data = &data[1..data.len() - 1];
87        let payload = Payload::decode(payload_data)?;
88
89        Ok(Frame { verb, payload })
90    }
91
92    /// Check if frame is valid (verb + END marker present)
93    pub fn is_valid(data: &[u8]) -> bool {
94        if data.len() < 2 {
95            return false;
96        }
97        if data[data.len() - 1] != END {
98            return false;
99        }
100        Verb::from_byte(data[0]).is_some()
101    }
102
103    /// Find the end of a frame in a buffer (returns length including END)
104    pub fn find_end(data: &[u8]) -> Option<usize> {
105        let mut i = 1; // Skip verb byte
106
107        while i < data.len() {
108            match data[i] {
109                END => return Some(i + 1),
110                ESC if i + 1 < data.len() => i += 2, // Skip escape sequence
111                ESC => return None, // Incomplete escape
112                _ => i += 1,
113            }
114        }
115
116        None // No END found
117    }
118}
119
120/// Builder for constructing frames
121pub struct FrameBuilder {
122    verb: Verb,
123    payload: Payload,
124}
125
126impl FrameBuilder {
127    pub fn new(verb: Verb) -> Self {
128        FrameBuilder {
129            verb,
130            payload: Payload::new(),
131        }
132    }
133
134    /// Add a byte to payload
135    pub fn byte(mut self, b: u8) -> Self {
136        self.payload.push_byte(b);
137        self
138    }
139
140    /// Add a string to payload
141    pub fn string(mut self, s: &str) -> Self {
142        self.payload.push_str(s);
143        self
144    }
145
146    /// Add bytes to payload
147    pub fn bytes(mut self, data: &[u8]) -> Self {
148        for &b in data {
149            self.payload.push_byte(b);
150        }
151        self
152    }
153
154    /// Add u16 LE to payload
155    pub fn u16_le(mut self, v: u16) -> Self {
156        self.payload.push_u16_le(v);
157        self
158    }
159
160    /// Add u32 LE to payload
161    pub fn u32_le(mut self, v: u32) -> Self {
162        self.payload.push_u32_le(v);
163        self
164    }
165
166    /// Build the frame
167    pub fn build(self) -> Frame {
168        Frame {
169            verb: self.verb,
170            payload: self.payload,
171        }
172    }
173}
174
175// Convenience constructors for common frames
176impl Frame {
177    /// Create a PING frame
178    pub fn ping() -> Self {
179        Frame::simple(Verb::Ping)
180    }
181
182    /// Create a STATS frame
183    pub fn stats() -> Self {
184        Frame::simple(Verb::Stats)
185    }
186
187    /// Create an OK/ACK frame
188    pub fn ok() -> Self {
189        Frame::simple(Verb::Ok)
190    }
191
192    /// Create an ERROR frame with message
193    pub fn error(message: &str) -> Self {
194        Frame::new(Verb::Error, Payload::from_string(message))
195    }
196
197    /// Create a SCAN frame
198    pub fn scan(path: &str, depth: u8) -> Self {
199        let mut payload = Payload::new();
200        // Length-prefixed path
201        let len = path.len();
202        if len <= 126 {
203            payload.push_byte((len as u8) + 0x80);
204        } else {
205            payload.push_byte(0xFF);
206            payload.push_u16_le(len as u16);
207        }
208        payload.push_str(path);
209        payload.push_byte(depth);
210
211        Frame::new(Verb::Scan, payload)
212    }
213
214    /// Create a SEARCH frame (simple - pattern only)
215    pub fn search(pattern: &str) -> Self {
216        Frame::new(Verb::Search, Payload::from_string(pattern))
217    }
218
219    /// Create a SEARCH frame with path, pattern, and max results
220    pub fn search_path(path: &str, pattern: &str, max_results: u8) -> Self {
221        let mut payload = Payload::new();
222
223        // Length-prefixed path
224        let path_len = path.len();
225        if path_len <= 126 {
226            payload.push_byte((path_len as u8) + 0x80);
227        } else {
228            payload.push_byte(0xFF);
229            payload.push_u16_le(path_len as u16);
230        }
231        payload.push_str(path);
232
233        // Length-prefixed pattern
234        let pattern_len = pattern.len();
235        if pattern_len <= 126 {
236            payload.push_byte((pattern_len as u8) + 0x80);
237        } else {
238            payload.push_byte(0xFF);
239            payload.push_u16_le(pattern_len as u16);
240        }
241        payload.push_str(pattern);
242
243        // Max results
244        payload.push_byte(max_results);
245
246        Frame::new(Verb::Search, payload)
247    }
248
249    /// Create a FORMAT frame with just mode (lists formats)
250    pub fn format(mode: &str) -> Self {
251        Frame::new(Verb::Format, Payload::from_string(mode))
252    }
253
254    /// Create a FORMAT frame with mode, path, and depth (scans and formats)
255    pub fn format_path(mode: &str, path: &str, depth: u8) -> Self {
256        let mut payload = Payload::new();
257
258        // Length-prefixed mode
259        let mode_len = mode.len();
260        if mode_len <= 126 {
261            payload.push_byte((mode_len as u8) + 0x80);
262        } else {
263            payload.push_byte(0xFF);
264            payload.push_u16_le(mode_len as u16);
265        }
266        payload.push_str(mode);
267
268        // Length-prefixed path
269        let path_len = path.len();
270        if path_len <= 126 {
271            payload.push_byte((path_len as u8) + 0x80);
272        } else {
273            payload.push_byte(0xFF);
274            payload.push_u16_le(path_len as u16);
275        }
276        payload.push_str(path);
277
278        // Depth
279        payload.push_byte(depth);
280
281        Frame::new(Verb::Format, payload)
282    }
283
284    /// Create a REMEMBER frame
285    pub fn remember(content: &str, keywords: &str, memory_type: &str) -> Self {
286        let mut payload = Payload::new();
287
288        // Length-prefixed content
289        let content_len = content.len();
290        if content_len <= 126 {
291            payload.push_byte((content_len as u8) + 0x80);
292        } else {
293            payload.push_byte(0xFF);
294            payload.push_u16_le(content_len as u16);
295        }
296        payload.push_str(content);
297
298        // Length-prefixed keywords
299        let keywords_len = keywords.len();
300        if keywords_len <= 126 {
301            payload.push_byte((keywords_len as u8) + 0x80);
302        } else {
303            payload.push_byte(0xFF);
304            payload.push_u16_le(keywords_len as u16);
305        }
306        payload.push_str(keywords);
307
308        // Length-prefixed type
309        let type_len = memory_type.len();
310        if type_len <= 126 {
311            payload.push_byte((type_len as u8) + 0x80);
312        } else {
313            payload.push_byte(0xFF);
314            payload.push_u16_le(type_len as u16);
315        }
316        payload.push_str(memory_type);
317
318        // Default emotional state (neutral)
319        payload.push_byte(128); // valence = 0.0
320        payload.push_byte(128); // arousal = 0.5
321
322        Frame::new(Verb::Remember, payload)
323    }
324
325    /// Create a RECALL frame
326    pub fn recall(keywords: &str, max_results: u8) -> Self {
327        let mut payload = Payload::new();
328
329        // Length-prefixed keywords
330        let keywords_len = keywords.len();
331        if keywords_len <= 126 {
332            payload.push_byte((keywords_len as u8) + 0x80);
333        } else {
334            payload.push_byte(0xFF);
335            payload.push_u16_le(keywords_len as u16);
336        }
337        payload.push_str(keywords);
338
339        payload.push_byte(max_results);
340
341        Frame::new(Verb::Recall, payload)
342    }
343
344    /// Create a FORGET frame
345    pub fn forget(memory_id: &str) -> Self {
346        Frame::new(Verb::Forget, Payload::from_string(memory_id))
347    }
348
349    /// Create an M8_WAVE frame (get memory stats)
350    pub fn m8_wave() -> Self {
351        Frame::simple(Verb::M8Wave)
352    }
353
354    /// Create an AUDIO frame with AcousticMemory data
355    ///
356    /// Payload format: [AYE8 magic + serialized AcousticMemory bytes]
357    /// The bytes should be from liquid-rust's AcousticMemory::to_bytes()
358    pub fn audio(acoustic_bytes: &[u8]) -> Self {
359        let mut payload = Payload::new();
360        for &b in acoustic_bytes {
361            payload.push_byte(b);
362        }
363        Frame::new(Verb::Audio, payload)
364    }
365
366    /// Create an AUDIO frame with text and emotion (simplified)
367    ///
368    /// For when you don't have full AcousticMemory, just text + emotion
369    pub fn audio_simple(text: &str, valence: f32, arousal: f32) -> Self {
370        let mut payload = Payload::new();
371
372        // Length-prefixed text
373        let text_len = text.len();
374        if text_len <= 126 {
375            payload.push_byte((text_len as u8) + 0x80);
376        } else {
377            payload.push_byte(0xFF);
378            payload.push_u16_le(text_len as u16);
379        }
380        payload.push_str(text);
381
382        // Emotion as two bytes (0-255 range)
383        // valence: -1.0 to 1.0 → 0 to 255
384        // arousal: 0.0 to 1.0 → 0 to 255
385        payload.push_byte(((valence + 1.0) * 127.5) as u8);
386        payload.push_byte((arousal * 255.0) as u8);
387
388        Frame::new(Verb::Audio, payload)
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    use super::*;
395
396    #[test]
397    fn test_ping_frame() {
398        let frame = Frame::ping();
399        let encoded = frame.encode();
400        assert_eq!(encoded, vec![0x05, 0x00]); // ENQ + END
401    }
402
403    #[test]
404    fn test_error_frame() {
405        let frame = Frame::error("not found");
406        let encoded = frame.encode();
407        assert_eq!(encoded[0], 0x15); // NAK
408        assert_eq!(encoded[encoded.len() - 1], 0x00); // END
409    }
410
411    #[test]
412    fn test_frame_roundtrip() {
413        let original = Frame::scan("/home/hue", 3);
414        let encoded = original.encode();
415        let decoded = Frame::decode(&encoded).unwrap();
416
417        assert_eq!(decoded.verb(), Verb::Scan);
418    }
419
420    #[test]
421    fn test_find_end() {
422        let data = vec![0x01, 0x41, 0x42, 0x00]; // SCAN "AB" END
423        assert_eq!(Frame::find_end(&data), Some(4));
424    }
425
426    #[test]
427    fn test_find_end_with_escape() {
428        let data = vec![0x01, 0x1B, 0x00, 0x00]; // SCAN ESC-NULL END
429        assert_eq!(Frame::find_end(&data), Some(4));
430    }
431
432    #[test]
433    fn test_builder() {
434        let frame = FrameBuilder::new(Verb::Search)
435            .string("*.rs")
436            .build();
437
438        assert_eq!(frame.verb(), Verb::Search);
439        assert_eq!(frame.payload().as_str(), Some("*.rs"));
440    }
441}