Skip to main content

ember_plus/s101/
frame.rs

1//! S101 frame structures.
2
3use crate::error::{S101Error, Result};
4use super::constants::*;
5use super::crc;
6
7/// S101 message types.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[repr(u8)]
10pub enum MessageType {
11    /// Ember message type (for both data and commands)
12    Ember = 0x0E,
13}
14
15impl TryFrom<u8> for MessageType {
16    type Error = S101Error;
17
18    fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
19        match value {
20            0x0E => Ok(MessageType::Ember),
21            _ => Err(S101Error::InvalidMessageType(value)),
22        }
23    }
24}
25
26/// S101 commands.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28#[repr(u8)]
29pub enum Command {
30    /// Ember data packet
31    Ember = 0x00,
32    /// Keep-alive request
33    KeepAliveRequest = 0x01,
34    /// Keep-alive response
35    KeepAliveResponse = 0x02,
36}
37
38impl TryFrom<u8> for Command {
39    type Error = S101Error;
40
41    fn try_from(value: u8) -> std::result::Result<Self, Self::Error> {
42        match value {
43            0x00 => Ok(Command::Ember),
44            0x01 => Ok(Command::KeepAliveRequest),
45            0x02 => Ok(Command::KeepAliveResponse),
46            _ => Err(S101Error::InvalidCommand(value)),
47        }
48    }
49}
50
51/// Flags for Ember messages.
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
53pub struct EmberFlags {
54    /// First packet of a multi-packet message
55    pub first_packet: bool,
56    /// Last packet of a multi-packet message
57    pub last_packet: bool,
58    /// Empty packet (used for keep-alive in some implementations)
59    pub empty_packet: bool,
60}
61
62impl EmberFlags {
63    /// Create flags for a single complete packet.
64    pub fn single_packet() -> Self {
65        EmberFlags {
66            first_packet: true,
67            last_packet: true,
68            empty_packet: false,
69        }
70    }
71
72    /// Encode flags to a byte.
73    pub fn encode(&self) -> u8 {
74        let mut flags = 0u8;
75        if self.first_packet {
76            flags |= 0x80;
77        }
78        if self.last_packet {
79            flags |= 0x40;
80        }
81        if self.empty_packet {
82            flags |= 0x20;
83        }
84        flags
85    }
86
87    /// Decode flags from a byte.
88    pub fn decode(byte: u8) -> Self {
89        EmberFlags {
90            first_packet: (byte & 0x80) != 0,
91            last_packet: (byte & 0x40) != 0,
92            empty_packet: (byte & 0x20) != 0,
93        }
94    }
95}
96
97/// An S101 message (high-level representation).
98#[derive(Debug, Clone, PartialEq, Eq)]
99pub enum S101Message {
100    /// Ember+ data packet
101    EmberData {
102        /// DTD type (usually 0x01 for Glow)
103        dtd_type: u8,
104        /// Application-specific bytes
105        app_bytes: Vec<u8>,
106        /// Packet flags
107        flags: EmberFlags,
108        /// Payload data
109        payload: Vec<u8>,
110    },
111    /// Keep-alive request
112    KeepAliveRequest,
113    /// Keep-alive response
114    KeepAliveResponse,
115}
116
117impl S101Message {
118    /// Create an Ember data message.
119    pub fn ember_data(payload: Vec<u8>) -> Self {
120        S101Message::EmberData {
121            dtd_type: 0x01, // Glow
122            app_bytes: vec![0x01, 0x31], // Version 1.49
123            flags: EmberFlags::single_packet(),
124            payload,
125        }
126    }
127
128    /// Check if this is a keep-alive request.
129    pub fn is_keepalive_request(&self) -> bool {
130        matches!(self, S101Message::KeepAliveRequest)
131    }
132
133    /// Check if this is a keep-alive response.
134    pub fn is_keepalive_response(&self) -> bool {
135        matches!(self, S101Message::KeepAliveResponse)
136    }
137
138    /// Get the payload if this is an Ember data message.
139    pub fn payload(&self) -> Option<&[u8]> {
140        match self {
141            S101Message::EmberData { payload, .. } => Some(payload),
142            _ => None,
143        }
144    }
145}
146
147/// An S101 frame (raw wire format).
148#[derive(Debug, Clone)]
149pub struct S101Frame {
150    /// Slot number (usually 0)
151    pub slot: u8,
152    /// Message type
153    pub message_type: MessageType,
154    /// Command
155    pub command: Command,
156    /// Payload (after header, before CRC)
157    pub payload: Vec<u8>,
158}
159
160impl S101Frame {
161    /// Create a new frame for Ember data.
162    pub fn ember_data(payload: Vec<u8>) -> Self {
163        S101Frame {
164            slot: 0,
165            message_type: MessageType::Ember,
166            command: Command::Ember,
167            payload,
168        }
169    }
170
171    /// Create a keep-alive request frame.
172    pub fn keepalive_request() -> Self {
173        S101Frame {
174            slot: 0,
175            message_type: MessageType::Ember,
176            command: Command::KeepAliveRequest,
177            payload: vec![],
178        }
179    }
180
181    /// Create a keep-alive response frame.
182    pub fn keepalive_response() -> Self {
183        S101Frame {
184            slot: 0,
185            message_type: MessageType::Ember,
186            command: Command::KeepAliveResponse,
187            payload: vec![],
188        }
189    }
190
191    /// Encode this frame to bytes (with framing and escaping).
192    pub fn encode(&self) -> Vec<u8> {
193        let mut output = Vec::new();
194        output.push(BOF);
195        
196        // Build content (will be used for CRC calculation)
197        let mut content = Vec::new();
198        
199        // Header: slot, message type, command
200        content.push(self.slot);
201        content.push(self.message_type as u8);
202        content.push(self.command as u8);
203        
204        // For keep-alive, just add version
205        // For Ember data, add version, flags, DTD, app bytes, then payload
206        match self.command {
207            Command::KeepAliveRequest | Command::KeepAliveResponse => {
208                content.push(0x01); // Version
209            }
210            Command::Ember => {
211                content.push(0x01); // Version
212                content.push(0xC0); // Flags: first + last packet
213                content.push(0x01); // DTD type: Glow
214                content.push(0x02); // App bytes count
215                content.push(0x1F); // Minor version: 31
216                content.push(0x02); // Major version: 2
217                content.extend(&self.payload);
218            }
219        }
220        
221        // Escape content and write to output
222        for &byte in &content {
223            if byte >= S101_INV {
224                output.push(ESCAPE_BYTE);
225                output.push(byte ^ ESCAPE_XOR);
226            } else {
227                output.push(byte);
228            }
229        }
230        
231        // Calculate CRC on the escaped content (excluding BOF)
232        let crc = crc::calculate_crc_escaped(&output[1..]);
233        let crc_lo = (crc & 0xFF) as u8;
234        let crc_hi = ((crc >> 8) & 0xFF) as u8;
235        
236        // Escape and append CRC bytes
237        if crc_lo >= S101_INV {
238            output.push(ESCAPE_BYTE);
239            output.push(crc_lo ^ ESCAPE_XOR);
240        } else {
241            output.push(crc_lo);
242        }
243        if crc_hi >= S101_INV {
244            output.push(ESCAPE_BYTE);
245            output.push(crc_hi ^ ESCAPE_XOR);
246        } else {
247            output.push(crc_hi);
248        }
249        
250        output.push(EOF);
251        output
252    }
253
254    /// Decode a frame from bytes (removing framing and escaping).
255    pub fn decode(data: &[u8]) -> Result<Self> {
256        if data.len() < 2 {
257            return Err(S101Error::IncompleteFrame.into());
258        }
259        
260        // Check frame markers
261        if data[0] != BOF {
262            return Err(S101Error::InvalidFrameMarker(data[0]).into());
263        }
264        if data[data.len() - 1] != EOF {
265            return Err(S101Error::InvalidFrameMarker(data[data.len() - 1]).into());
266        }
267        
268        // Remove escaping
269        let inner_data = &data[1..data.len() - 1];
270        let mut unescaped = Vec::with_capacity(inner_data.len());
271        let mut i = 0;
272        
273        while i < inner_data.len() {
274            if inner_data[i] == ESCAPE_BYTE {
275                if i + 1 >= inner_data.len() {
276                    return Err(S101Error::InvalidEscapeSequence.into());
277                }
278                unescaped.push(inner_data[i + 1] ^ ESCAPE_XOR);
279                i += 2;
280            } else {
281                unescaped.push(inner_data[i]);
282                i += 1;
283            }
284        }
285        
286        // Verify CRC
287        if !crc::verify_crc(&unescaped) {
288            let len = unescaped.len();
289            if len >= 2 {
290                let received = u16::from_be_bytes([unescaped[len - 2], unescaped[len - 1]]);
291                let calculated = crc::calculate_crc(&unescaped[..len - 2]);
292                return Err(S101Error::CrcMismatch {
293                    expected: calculated,
294                    actual: received,
295                }.into());
296            }
297            return Err(S101Error::IncompleteFrame.into());
298        }
299        
300        // Remove CRC from payload
301        let unescaped = &unescaped[..unescaped.len() - 2];
302        
303        if unescaped.len() < 3 {
304            return Err(S101Error::IncompleteFrame.into());
305        }
306        
307        let slot = unescaped[SLOT_OFFSET];
308        let message_type = MessageType::try_from(unescaped[MESSAGE_TYPE_OFFSET])?;
309        let command = Command::try_from(unescaped[COMMAND_OFFSET])?;
310        
311        // Extract payload based on command type
312        let payload = match command {
313            Command::KeepAliveRequest | Command::KeepAliveResponse => {
314                // Keep-alive: slot(1) + msg_type(1) + cmd(1) + version(1) = 4 bytes, no payload
315                vec![]
316            }
317            Command::Ember => {
318                // Ember data: slot(1) + msg_type(1) + cmd(1) + version(1) + flags(1) + dtd(1) + app_count(1) + app_bytes + payload
319                if unescaped.len() < 7 {
320                    return Err(S101Error::IncompleteFrame.into());
321                }
322                let app_bytes_count = unescaped[6] as usize;
323                let payload_start = 7 + app_bytes_count;
324                if payload_start > unescaped.len() {
325                    return Err(S101Error::IncompleteFrame.into());
326                }
327                unescaped[payload_start..].to_vec()
328            }
329        };
330        
331        Ok(S101Frame {
332            slot,
333            message_type,
334            command,
335            payload,
336        })
337    }
338
339    /// Convert to a high-level message.
340    pub fn to_message(&self) -> S101Message {
341        match self.command {
342            Command::Ember => S101Message::EmberData {
343                dtd_type: 0x01,
344                app_bytes: vec![0x1F, 0x02],
345                flags: EmberFlags::single_packet(),
346                payload: self.payload.clone(),
347            },
348            Command::KeepAliveRequest => S101Message::KeepAliveRequest,
349            Command::KeepAliveResponse => S101Message::KeepAliveResponse,
350        }
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357
358    #[test]
359    fn test_frame_roundtrip() {
360        let frame = S101Frame::ember_data(vec![0x01, 0x02, 0x03, 0x04]);
361        let encoded = frame.encode();
362        let decoded = S101Frame::decode(&encoded).unwrap();
363        
364        assert_eq!(frame.slot, decoded.slot);
365        assert_eq!(frame.message_type, decoded.message_type);
366        assert_eq!(frame.command, decoded.command);
367        assert_eq!(frame.payload, decoded.payload);
368    }
369
370    #[test]
371    fn test_keepalive_frame() {
372        let request = S101Frame::keepalive_request();
373        let encoded = request.encode();
374        let decoded = S101Frame::decode(&encoded).unwrap();
375        
376        assert_eq!(decoded.message_type, MessageType::Ember);
377        assert_eq!(decoded.command, Command::KeepAliveRequest);
378    }
379
380    #[test]
381    fn test_escape_sequences() {
382        // Test with bytes that need escaping
383        let frame = S101Frame::ember_data(vec![BOF, ESCAPE_BYTE, 0x00, 0x1F]);
384        let encoded = frame.encode();
385        let decoded = S101Frame::decode(&encoded).unwrap();
386        
387        assert_eq!(frame.payload, decoded.payload);
388    }
389}