Skip to main content

st_protocol/
payload.rs

1//! Payload encoding/decoding with escape sequences
2//!
3//! ## Length Prefix Format
4//!
5//! First byte after verb determines payload format:
6//! - `0x20-0x7E` = ASCII string starts (printable chars)
7//! - `0x80-0xFE` = Length prefix (len = byte - 0x80, max 126)
8//! - `0xFF` = Extended length (next 2 bytes = u16 LE)
9//!
10//! ## Escape Sequences
11//!
12//! - `0x1B 0x1B` = literal `0x1B` in payload
13//! - `0x1B 0x00` = literal `0x00` in payload
14
15#[cfg(feature = "alloc")]
16use alloc::vec::Vec;
17
18use crate::{ESC, END, ProtocolError, ProtocolResult};
19
20/// Raw payload data with escape handling
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Payload {
23    /// Unescaped raw bytes
24    #[cfg(feature = "std")]
25    data: Vec<u8>,
26    #[cfg(all(feature = "alloc", not(feature = "std")))]
27    data: alloc::vec::Vec<u8>,
28    #[cfg(all(not(feature = "alloc"), not(feature = "std")))]
29    data: [u8; 256],
30    #[cfg(all(not(feature = "alloc"), not(feature = "std")))]
31    len: usize,
32}
33
34impl Default for Payload {
35    fn default() -> Self {
36        Self::new()
37    }
38}
39
40impl Payload {
41    /// Create empty payload
42    pub fn new() -> Self {
43        #[cfg(any(feature = "std", feature = "alloc"))]
44        {
45            Payload { data: Vec::new() }
46        }
47        #[cfg(all(not(feature = "alloc"), not(feature = "std")))]
48        {
49            Payload {
50                data: [0u8; 256],
51                len: 0,
52            }
53        }
54    }
55
56    /// Create empty payload
57    pub fn empty() -> Self {
58        Self::new()
59    }
60
61    /// Create from string slice
62    pub fn from_string(s: &str) -> Self {
63        let mut p = Self::new();
64        p.push_str(s);
65        p
66    }
67
68    /// Create from bytes
69    pub fn from_bytes(bytes: &[u8]) -> Self {
70        let mut p = Self::new();
71        for &b in bytes {
72            p.push_byte(b);
73        }
74        p
75    }
76
77    /// Push a single byte (unescaped)
78    pub fn push_byte(&mut self, b: u8) {
79        #[cfg(any(feature = "std", feature = "alloc"))]
80        {
81            self.data.push(b);
82        }
83        #[cfg(all(not(feature = "alloc"), not(feature = "std")))]
84        {
85            if self.len < 256 {
86                self.data[self.len] = b;
87                self.len += 1;
88            }
89        }
90    }
91
92    /// Push bytes from a string
93    pub fn push_str(&mut self, s: &str) {
94        for b in s.bytes() {
95            self.push_byte(b);
96        }
97    }
98
99    /// Push a u16 as little-endian
100    pub fn push_u16_le(&mut self, v: u16) {
101        self.push_byte((v & 0xFF) as u8);
102        self.push_byte((v >> 8) as u8);
103    }
104
105    /// Push a u32 as little-endian
106    pub fn push_u32_le(&mut self, v: u32) {
107        self.push_byte((v & 0xFF) as u8);
108        self.push_byte(((v >> 8) & 0xFF) as u8);
109        self.push_byte(((v >> 16) & 0xFF) as u8);
110        self.push_byte((v >> 24) as u8);
111    }
112
113    /// Get raw bytes (unescaped)
114    pub fn as_bytes(&self) -> &[u8] {
115        #[cfg(any(feature = "std", feature = "alloc"))]
116        {
117            &self.data
118        }
119        #[cfg(all(not(feature = "alloc"), not(feature = "std")))]
120        {
121            &self.data[..self.len]
122        }
123    }
124
125    /// Length of unescaped data
126    pub fn len(&self) -> usize {
127        #[cfg(any(feature = "std", feature = "alloc"))]
128        {
129            self.data.len()
130        }
131        #[cfg(all(not(feature = "alloc"), not(feature = "std")))]
132        {
133            self.len
134        }
135    }
136
137    /// Check if empty
138    pub fn is_empty(&self) -> bool {
139        self.len() == 0
140    }
141
142    /// Encode payload with escape sequences for wire format
143    #[cfg(any(feature = "std", feature = "alloc"))]
144    pub fn encode(&self) -> Vec<u8> {
145        let mut out = Vec::with_capacity(self.len() * 2); // worst case: all escapes
146
147        for &b in self.as_bytes() {
148            match b {
149                END => {
150                    out.push(ESC);
151                    out.push(END);
152                }
153                ESC => {
154                    out.push(ESC);
155                    out.push(ESC);
156                }
157                _ => out.push(b),
158            }
159        }
160
161        out
162    }
163
164    /// Decode payload from wire format (with escape sequences)
165    pub fn decode(data: &[u8]) -> ProtocolResult<Self> {
166        let mut payload = Self::new();
167        let mut i = 0;
168
169        while i < data.len() {
170            let b = data[i];
171
172            if b == ESC {
173                // Escape sequence
174                if i + 1 >= data.len() {
175                    return Err(ProtocolError::InvalidEscape);
176                }
177
178                let next = data[i + 1];
179                match next {
180                    END => payload.push_byte(END),  // 0x1B 0x00 = literal 0x00
181                    ESC => payload.push_byte(ESC),  // 0x1B 0x1B = literal 0x1B
182                    _ => return Err(ProtocolError::InvalidEscape),
183                }
184                i += 2;
185            } else {
186                payload.push_byte(b);
187                i += 1;
188            }
189        }
190
191        Ok(payload)
192    }
193
194    /// Try to interpret as UTF-8 string
195    pub fn as_str(&self) -> Option<&str> {
196        core::str::from_utf8(self.as_bytes()).ok()
197    }
198
199    /// Read a u16 LE at offset
200    pub fn read_u16_le(&self, offset: usize) -> Option<u16> {
201        let bytes = self.as_bytes();
202        if offset + 2 > bytes.len() {
203            return None;
204        }
205        Some(u16::from_le_bytes([bytes[offset], bytes[offset + 1]]))
206    }
207
208    /// Read a u32 LE at offset
209    pub fn read_u32_le(&self, offset: usize) -> Option<u32> {
210        let bytes = self.as_bytes();
211        if offset + 4 > bytes.len() {
212            return None;
213        }
214        Some(u32::from_le_bytes([
215            bytes[offset],
216            bytes[offset + 1],
217            bytes[offset + 2],
218            bytes[offset + 3],
219        ]))
220    }
221}
222
223/// Payload encoder for building complex payloads
224pub struct PayloadEncoder {
225    payload: Payload,
226}
227
228impl Default for PayloadEncoder {
229    fn default() -> Self {
230        Self::new()
231    }
232}
233
234impl PayloadEncoder {
235    pub fn new() -> Self {
236        PayloadEncoder {
237            payload: Payload::new(),
238        }
239    }
240
241    /// Add length-prefixed string (short form: 0x80-0xFE)
242    pub fn string(mut self, s: &str) -> Self {
243        let len = s.len();
244        if len <= 126 {
245            self.payload.push_byte((len as u8) + 0x80);
246        } else {
247            self.payload.push_byte(0xFF);
248            self.payload.push_u16_le(len as u16);
249        }
250        self.payload.push_str(s);
251        self
252    }
253
254    /// Add raw bytes with length prefix
255    pub fn bytes(mut self, data: &[u8]) -> Self {
256        let len = data.len();
257        if len <= 126 {
258            self.payload.push_byte((len as u8) + 0x80);
259        } else {
260            self.payload.push_byte(0xFF);
261            self.payload.push_u16_le(len as u16);
262        }
263        for &b in data {
264            self.payload.push_byte(b);
265        }
266        self
267    }
268
269    /// Add single byte
270    pub fn byte(mut self, b: u8) -> Self {
271        self.payload.push_byte(b);
272        self
273    }
274
275    /// Add u16 little-endian
276    pub fn u16_le(mut self, v: u16) -> Self {
277        self.payload.push_u16_le(v);
278        self
279    }
280
281    /// Add u32 little-endian
282    pub fn u32_le(mut self, v: u32) -> Self {
283        self.payload.push_u32_le(v);
284        self
285    }
286
287    /// Finish building and return payload
288    pub fn build(self) -> Payload {
289        self.payload
290    }
291}
292
293/// Payload decoder for parsing complex payloads
294pub struct PayloadDecoder<'a> {
295    data: &'a [u8],
296    pos: usize,
297}
298
299impl<'a> PayloadDecoder<'a> {
300    pub fn new(payload: &'a Payload) -> Self {
301        PayloadDecoder {
302            data: payload.as_bytes(),
303            pos: 0,
304        }
305    }
306
307    /// Remaining bytes
308    pub fn remaining(&self) -> usize {
309        self.data.len().saturating_sub(self.pos)
310    }
311
312    /// Read a single byte
313    pub fn byte(&mut self) -> Option<u8> {
314        if self.pos < self.data.len() {
315            let b = self.data[self.pos];
316            self.pos += 1;
317            Some(b)
318        } else {
319            None
320        }
321    }
322
323    /// Read u16 little-endian
324    pub fn u16_le(&mut self) -> Option<u16> {
325        if self.pos + 2 <= self.data.len() {
326            let v = u16::from_le_bytes([self.data[self.pos], self.data[self.pos + 1]]);
327            self.pos += 2;
328            Some(v)
329        } else {
330            None
331        }
332    }
333
334    /// Read u32 little-endian
335    pub fn u32_le(&mut self) -> Option<u32> {
336        if self.pos + 4 <= self.data.len() {
337            let v = u32::from_le_bytes([
338                self.data[self.pos],
339                self.data[self.pos + 1],
340                self.data[self.pos + 2],
341                self.data[self.pos + 3],
342            ]);
343            self.pos += 4;
344            Some(v)
345        } else {
346            None
347        }
348    }
349
350    /// Read length-prefixed string
351    pub fn string(&mut self) -> Option<&'a str> {
352        let len_byte = self.byte()?;
353
354        let len = if len_byte == 0xFF {
355            self.u16_le()? as usize
356        } else if len_byte >= 0x80 {
357            (len_byte - 0x80) as usize
358        } else {
359            // Printable ASCII - read until non-printable or end
360            self.pos -= 1;
361            let start = self.pos;
362            while self.pos < self.data.len() && self.data[self.pos] >= 0x20 && self.data[self.pos] <= 0x7E {
363                self.pos += 1;
364            }
365            let s = core::str::from_utf8(&self.data[start..self.pos]).ok()?;
366            return Some(s);
367        };
368
369        if self.pos + len > self.data.len() {
370            return None;
371        }
372
373        let s = core::str::from_utf8(&self.data[self.pos..self.pos + len]).ok()?;
374        self.pos += len;
375        Some(s)
376    }
377
378    /// Read length-prefixed bytes
379    pub fn bytes(&mut self) -> Option<&'a [u8]> {
380        let len_byte = self.byte()?;
381
382        let len = if len_byte == 0xFF {
383            self.u16_le()? as usize
384        } else if len_byte >= 0x80 {
385            (len_byte - 0x80) as usize
386        } else {
387            return None; // Raw bytes must have length prefix
388        };
389
390        if self.pos + len > self.data.len() {
391            return None;
392        }
393
394        let data = &self.data[self.pos..self.pos + len];
395        self.pos += len;
396        Some(data)
397    }
398}
399
400#[cfg(test)]
401mod tests {
402    use super::*;
403
404    #[test]
405    fn test_escape_roundtrip() {
406        let original = Payload::from_bytes(&[0x00, 0x1B, 0x42, 0x00, 0x1B]);
407        let encoded = original.encode();
408        let decoded = Payload::decode(&encoded).unwrap();
409        assert_eq!(decoded.as_bytes(), original.as_bytes());
410    }
411
412    #[test]
413    fn test_string_encoding() {
414        let payload = PayloadEncoder::new()
415            .string("/home/hue")
416            .byte(3) // depth
417            .build();
418
419        let mut decoder = PayloadDecoder::new(&payload);
420        assert_eq!(decoder.string(), Some("/home/hue"));
421        assert_eq!(decoder.byte(), Some(3));
422    }
423
424    #[test]
425    fn test_short_length_prefix() {
426        // String "abc" should encode as: 0x83 'a' 'b' 'c'
427        let payload = PayloadEncoder::new().string("abc").build();
428        let bytes = payload.as_bytes();
429        assert_eq!(bytes[0], 0x83); // 3 + 0x80
430        assert_eq!(&bytes[1..4], b"abc");
431    }
432
433    #[test]
434    fn test_extended_length() {
435        // 200-byte string needs extended length
436        let long_str = "x".repeat(200);
437        let payload = PayloadEncoder::new().string(&long_str).build();
438        let bytes = payload.as_bytes();
439        assert_eq!(bytes[0], 0xFF); // Extended marker
440        assert_eq!(bytes[1], 200); // Low byte
441        assert_eq!(bytes[2], 0);   // High byte
442    }
443}