flipdot_core/
frame.rs

1use std::borrow::Cow;
2use std::fmt::{self, Display, Formatter};
3use std::io::{BufRead, BufReader, Read, Write};
4use std::str;
5
6use derive_more::{Display, LowerHex, UpperHex};
7use lazy_static::lazy_static;
8use num_traits::Num;
9use regex::bytes::Regex;
10use thiserror::Error;
11
12/// Errors related to reading/writing [`Frame`]s of data.
13#[derive(Error, Debug)]
14#[non_exhaustive]
15pub enum FrameError {
16    /// [`Data`] length exceeded the maximum of 255 bytes.
17    #[error("Maximum data length is {} bytes, got {}", max, actual)]
18    DataTooLong {
19        /// The maximum data length.
20        max: u8,
21
22        /// The actual length of the data that was provided.
23        actual: usize,
24    },
25
26    /// Failed reading/writing a [`Frame`] of data.
27    #[error("Failed reading/writing a frame of data")]
28    Io {
29        /// The underlying I/O error.
30        #[from]
31        source: std::io::Error,
32    },
33
34    /// Failed to parse data into a [`Frame`].
35    #[error("Failed to parse invalid Intel HEX [{}] into a Frame", string_for_error(data))]
36    InvalidFrame {
37        /// The invalid frame data.
38        data: Vec<u8>,
39    },
40
41    /// [`Frame`] data didn't match declared length.
42    #[error(
43        "Frame data [{}] didn't match declared length: Expected {}, got {}",
44        string_for_error(data),
45        expected,
46        actual
47    )]
48    FrameDataMismatch {
49        /// The invalid frame data.
50        data: Vec<u8>,
51
52        /// The expected data length.
53        expected: usize,
54
55        /// The actual value of the data that was provided.
56        actual: usize,
57    },
58
59    /// [`Frame`] checksum didn't match declared checksum.
60    #[error(
61        "Frame checksum for [{}] didn't match declared checksum: Expected 0x{:X}, got 0x{:X}",
62        string_for_error(data),
63        expected,
64        actual
65    )]
66    BadChecksum {
67        /// The invalid frame data.
68        data: Vec<u8>,
69
70        /// The expected checksum.
71        expected: u8,
72
73        /// The actual checksum of the data.
74        actual: u8,
75    },
76}
77
78/// A low-level representation of an Intel HEX data frame.
79///
80/// The Luminator protocol uses the [Intel HEX] format but not its semantics.
81/// This struct handles parsing the raw bytes into a form we can reason about,
82/// dealing with checksums, and so forth. It makes no attempt to ascribe meaning
83/// to the address, message type, and data (that's [`Message`](crate::Message)'s job).
84///
85/// Both owned and borrowed data are supported.
86///
87/// # Examples
88///
89/// ```
90/// use flipdot_core::{Address, Data, Frame, MsgType};
91///
92/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
93/// #
94/// let frame = Frame::new(Address(2), MsgType(1), Data::try_new(vec![3, 31])?);
95/// println!("Parsed frame is {}", frame);
96///
97/// let bytes = frame.to_bytes();
98/// assert_eq!(b":02000201031FD9", bytes.as_slice());
99///
100/// let parsed = Frame::from_bytes(&bytes)?;
101/// assert_eq!(parsed, frame);
102/// #
103/// # Ok(()) }
104/// ```
105///
106/// # Format Details
107///
108/// The format consists of a leading colon, several numeric fields (two-character ASCII representations
109/// of hex bytes), and a final carriage return/linefeed terminator. Note that for convenience,
110/// `Frame` allows omitting the final CRLF sequence.
111///
112/// ```text
113/// ┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬ ┄ ┬────┬────┬────┬────┬────┬────┐
114/// │ :  │ DataLen │      Address      │ MsgType │  Data 0 │...│  Data N │  Chksum │ \r │ \n │
115/// └────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴ ┄ ┴────┴────┴────┴────┴────┴────┘
116///           └╌╌╌╌╌╌╌╌╌╌╌╌╌ # of ╌╌╌╌╌╌╌╌╌╌╌╌╌> ┆       Data bytes      ┆
117/// ```
118///
119/// The `DataLen` field describes how many two-character data byte sequences are present.
120/// Note that since it is represented as a single byte, the data length cannot exceed 255 (`0xFF`).
121/// If `DataLen` is 0, there are no data bytes, and `MsgType` is followed directly by `Chksum`.
122/// The checksum is a [longitudinal redundancy check] calculated on all numeric fields.
123///
124/// [Intel HEX]: https://en.wikipedia.org/wiki/Intel_HEX
125/// [longitudinal redundancy check]: https://en.wikipedia.org/wiki/Longitudinal_redundancy_check
126#[derive(Debug, Clone, PartialEq, Eq, Hash)]
127pub struct Frame<'a> {
128    address: Address,
129    message_type: MsgType,
130    data: Data<'a>,
131}
132
133/// A [`Frame`]'s message type.
134///
135/// Carries no implicit meaning, but is interpreted by [`Message`](crate::Message).
136///
137/// # Examples
138///
139/// ```
140/// use flipdot_core::{Address, Data, Frame, MsgType};
141///
142/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
143/// #
144/// // Create a frame with message type 1.
145/// let frame = Frame::new(Address(2), MsgType(1), Data::try_new(vec![1, 2])?);
146/// #
147/// # Ok(()) }
148/// ```
149#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Display, LowerHex, UpperHex)]
150pub struct MsgType(pub u8);
151
152/// The address of a sign, used to identify it on the bus.
153///
154/// # Examples
155///
156/// ```
157/// use flipdot_core::{Address, Data, Frame, MsgType};
158///
159/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
160/// #
161/// // Create a frame addressed to sign 2.
162/// let frame = Frame::new(Address(2), MsgType(1), Data::try_new(vec![1, 2])?);
163/// #
164/// # Ok(()) }
165/// ```
166#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Display, LowerHex, UpperHex)]
167pub struct Address(pub u16);
168
169impl<'a> Frame<'a> {
170    /// Constructs a new `Frame` with the specified address, message type, and data.
171    ///
172    /// # Examples
173    ///
174    /// ```
175    /// # use flipdot_core::{Address, Data, Frame, MsgType};
176    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
177    /// #
178    /// // some_data is moved into owning_frame.
179    /// let some_data = vec![1, 2, 3];
180    /// let owning_frame = Frame::new(Address(0xB), MsgType(0xA), Data::try_new(some_data)?);
181    ///
182    /// // other_data is borrowed.
183    /// let other_data = vec![1, 2, 3];
184    /// let borrowing_frame = Frame::new(Address(0xD), MsgType(0xC), Data::try_new(other_data.as_slice())?);
185    /// #
186    /// # Ok(()) }
187    /// ```
188    pub fn new(address: Address, message_type: MsgType, data: Data<'a>) -> Self {
189        Frame {
190            address,
191            message_type,
192            data,
193        }
194    }
195
196    /// Returns the message type of the frame.
197    ///
198    /// # Examples
199    ///
200    /// ```
201    /// # use flipdot_core::{Address, Data, Frame, MsgType};
202    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
203    /// #
204    /// let frame = Frame::new(Address(1), MsgType(1), Data::try_new(vec![])?);
205    /// match frame.message_type() {
206    ///    MsgType(1) => println!("Message 1"),
207    ///    _ => println!("Something else"),
208    /// }
209    /// #
210    /// # Ok(()) }
211    /// ```
212    pub fn message_type(&self) -> MsgType {
213        self.message_type
214    }
215
216    /// Returns the address of the frame.
217    ///
218    /// # Examples
219    ///
220    /// ```
221    /// # use flipdot_core::{Address, Data, Frame, MsgType};
222    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
223    /// #
224    /// let frame = Frame::new(Address(1), MsgType(1), Data::try_new(vec![])?);
225    /// if frame.address() == Address(3) {
226    ///     println!("This frame is addressed to me!");
227    /// }
228    /// #
229    /// # Ok(()) }
230    /// ```
231    pub fn address(&self) -> Address {
232        self.address
233    }
234
235    /// Returns a reference to the frame's data.
236    ///
237    /// # Examples
238    ///
239    /// ```
240    /// # use flipdot_core::{Address, Data, Frame, MsgType};
241    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
242    /// #
243    /// let frame = Frame::new(Address(1), MsgType(1), Data::try_new(vec![10, 20])?);
244    /// if (frame.data().as_ref() == &[10, 20]) {
245    ///     println!("Found the expected data!");
246    /// }
247    /// #
248    /// # Ok(()) }
249    /// ```
250    pub fn data(&self) -> &Cow<'a, [u8]> {
251        &self.data.0
252    }
253
254    /// Consumes the frame and returns ownership of its data.
255    ///
256    /// # Examples
257    ///
258    /// ```
259    /// # use flipdot_core::{Address, Data, Frame, MsgType};
260    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
261    /// #
262    /// let frame = Frame::new(Address(1), MsgType(1), Data::try_new(vec![6, 7])?);
263    /// let frame2 = Frame::new(Address(2), MsgType(2), frame.into_data());
264    /// #
265    /// # Ok(()) }
266    /// ```
267    pub fn into_data(self) -> Data<'a> {
268        self.data
269    }
270
271    /// Converts the frame to its wire format, *without* trailing carriage return/linefeed.
272    ///
273    /// # Examples
274    ///
275    /// ```
276    /// # use flipdot_core::{Address, Data, Frame, MsgType};
277    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
278    /// #
279    /// let frame = Frame::new(Address(2), MsgType(1), Data::try_new(vec![3, 31])?);
280    /// let bytes = frame.to_bytes();
281    /// assert_eq!(b":02000201031FD9", bytes.as_slice());
282    /// #
283    /// # Ok(()) }
284    /// ```
285    pub fn to_bytes(&self) -> Vec<u8> {
286        const HEX_DIGITS: &[u8] = b"0123456789ABCDEF";
287
288        let mut payload = self.payload();
289        let checksum = checksum(&payload);
290        payload.push(checksum);
291        let payload = payload;
292
293        // Colon, 2 ASCII digits for each byte, and 2 bytes for optional CRLF sequence
294        let mut output = Vec::<u8>::with_capacity(1 + 2 * payload.len() + 2);
295        output.push(b':');
296        for byte in &payload {
297            output.push(HEX_DIGITS[(byte >> 4) as usize]);
298            output.push(HEX_DIGITS[(byte & 0x0F) as usize]);
299        }
300        assert_eq!(output.len(), output.capacity() - 2);
301        output
302    }
303
304    /// Converts the frame to its wire format, including trailing carriage return/linefeed.
305    ///
306    /// # Examples
307    ///
308    /// ```
309    /// # use flipdot_core::{Address, Data, Frame, MsgType};
310    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
311    /// #
312    /// let frame = Frame::new(Address(2), MsgType(1), Data::try_new(vec![3, 31])?);
313    /// let bytes = frame.to_bytes_with_newline();
314    /// assert_eq!(b":02000201031FD9\r\n", bytes.as_slice());
315    /// #
316    /// # Ok(()) }
317    /// ```
318    pub fn to_bytes_with_newline(&self) -> Vec<u8> {
319        let mut output = self.to_bytes();
320        output.extend_from_slice(b"\r\n");
321        assert_eq!(output.len(), output.capacity());
322        output
323    }
324
325    /// Parses the Intel HEX wire format into a new `Frame`.
326    ///
327    /// # Errors
328    ///
329    /// Returns:
330    /// * [`FrameError::InvalidFrame`] if the data does not conform to the Intel HEX format.
331    /// * [`FrameError::FrameDataMismatch`] if the actual number of data bytes does not match the specified amount.
332    /// * [`FrameError::BadChecksum`] if the computed checksum on the data does not match the specified one.
333    ///
334    /// # Examples
335    ///
336    /// ```
337    /// # use flipdot_core::{Address, Data, Frame, MsgType};
338    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
339    /// #
340    /// let bytes = b":02000201031FD9\r\n";
341    /// let frame = Frame::from_bytes(&bytes[..])?;
342    /// assert_eq!(Frame::new(Address(2), MsgType(1), Data::try_new(vec![3, 31])?), frame);
343    /// #
344    /// # Ok(()) }
345    /// ```
346    pub fn from_bytes(bytes: &[u8]) -> Result<Self, FrameError> {
347        lazy_static! {
348            static ref RE: Regex = Regex::new(r"(?x)
349                ^:                                  # Colon marks beginning of frame
350                (?P<data_len>[[:xdigit:]]{2})       # 2 hex digits for data length
351                (?P<address>[[:xdigit:]]{4})        # 4 hex digits for address
352                (?P<message_type>[[:xdigit:]]{2})   # 2 hex digits for message type
353                (?P<data>(?:[[:xdigit:]]{2})*)      # Zero or more groups of 2 hex digits for data
354                (?P<checksum>[[:xdigit:]]{2})       # 2 hex digits for checksum
355                (?:\r\n)?$                          # Optional newline sequence
356            ").unwrap(); // Regex is valid so safe to unwrap.
357        }
358        let captures = RE
359            .captures(bytes)
360            .ok_or_else(|| FrameError::InvalidFrame { data: bytes.into() })?;
361
362        // Regex always matches all capture groups so safe to unwrap.
363        let data_len = parse_hex::<u8>(captures.name("data_len").unwrap().as_bytes());
364        let address = parse_hex::<u16>(captures.name("address").unwrap().as_bytes());
365        let message_type = parse_hex::<u8>(captures.name("message_type").unwrap().as_bytes());
366        let data_bytes = captures.name("data").unwrap().as_bytes();
367        let provided_checksum = parse_hex::<u8>(captures.name("checksum").unwrap().as_bytes());
368
369        let data = data_bytes.chunks(2).map(parse_hex::<u8>).collect::<Vec<_>>();
370        if data.len() != data_len as usize {
371            return Err(FrameError::FrameDataMismatch {
372                data: bytes.into(),
373                expected: data_len as usize,
374                actual: data.len(),
375            });
376        }
377
378        let frame = Frame::new(Address(address), MsgType(message_type), Data::try_new(data)?);
379        let payload = frame.payload();
380        let computed_checksum = checksum(&payload);
381        if computed_checksum != provided_checksum {
382            return Err(FrameError::BadChecksum {
383                data: bytes.into(),
384                expected: provided_checksum,
385                actual: computed_checksum,
386            });
387        }
388
389        Ok(frame)
390    }
391
392    /// Writes the byte representation (including CRLF) of the frame to a writer.
393    ///
394    /// # Errors
395    ///
396    /// Returns [`FrameError::Io`] if the write fails.
397    ///
398    /// # Examples
399    ///
400    /// ```no_run
401    /// # use flipdot_core::{Address, Data, Frame, MsgType};
402    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
403    /// #
404    /// let mut port = serial::open("COM3")?;
405    /// let frame = Frame::new(Address(2), MsgType(1), Data::try_new(vec![3, 31])?);
406    /// frame.write(&mut port)?;
407    /// #
408    /// # Ok(()) }
409    /// ```
410    pub fn write<W: Write>(&self, writer: &mut W) -> Result<(), FrameError> {
411        writer.write_all(&self.to_bytes_with_newline())?;
412        Ok(())
413    }
414
415    /// Reads the next line (up to `\n`) from the reader and converts the result
416    /// into a new `Frame`.
417    ///
418    /// # Errors
419    ///
420    /// Returns:
421    /// * [`FrameError::Io`] if the read fails.
422    /// * [`FrameError::InvalidFrame`] if the data does not conform to the Intel HEX format.
423    /// * [`FrameError::FrameDataMismatch`] if the actual number of data bytes does not match the specified amount.
424    /// * [`FrameError::BadChecksum`] if the computed checksum on the data does not match the specified one.
425    ///
426    /// # Examples
427    ///
428    /// ```no_run
429    /// # use flipdot_core::{Address, Data, Frame, MsgType};
430    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
431    /// #
432    /// let mut port = serial::open("COM3")?;
433    /// let frame = Frame::read(&mut port)?;
434    /// #
435    /// # Ok(()) }
436    /// ```
437    pub fn read<R: Read>(mut reader: &mut R) -> Result<Self, FrameError> {
438        // One-byte buffer seems to work best with such small payloads
439        let mut buf_reader = BufReader::with_capacity(1, &mut reader);
440        let mut data = Vec::<u8>::new();
441        let _ = buf_reader.read_until(b'\n', &mut data)?;
442        let frame = Frame::from_bytes(&data)?;
443        Ok(frame)
444    }
445
446    /// Returns the payload portion of the wire format.
447    ///
448    /// These are the numeric fields other than the checksum, upon which the checksum is computed.
449    fn payload(&self) -> Vec<u8> {
450        // Reserving an extra byte here so the checksum can be appended in to_bytes.
451        let mut payload = Vec::<u8>::with_capacity(5 + self.data.0.len());
452        payload.push(self.data.0.len() as u8);
453        payload.push((self.address.0 >> 8) as u8);
454        payload.push(self.address.0 as u8);
455        payload.push(self.message_type.0);
456        payload.extend_from_slice(&self.data.0);
457        assert_eq!(payload.len(), payload.capacity() - 1);
458        payload
459    }
460}
461
462impl Display for Frame<'_> {
463    /// Formats the frame in a human-readable way.
464    ///
465    /// Useful for viewing traffic on a bus. All numbers are in hex.
466    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
467        write!(f, "Type {:02X} | Addr {:04X}", self.message_type.0, self.address.0)?;
468        if self.data.0.len() > 0 {
469            write!(f, " | Data ")?;
470            for byte in self.data.0.iter() {
471                write!(f, "{:02X} ", byte)?;
472            }
473        }
474        Ok(())
475    }
476}
477
478/// Parses a byte slice representing ASCII text into a hex digit.
479///
480/// Assumes that the data has already been validated and panics if it is invalid.
481fn parse_hex<T: Num>(bytes: &[u8]) -> T
482where
483    <T as Num>::FromStrRadixErr: 'static + ::std::error::Error,
484{
485    // Regex already determined these are valid hex digits, so we can just unwrap.
486    let string = str::from_utf8(bytes).unwrap();
487    T::from_str_radix(string, 16).unwrap()
488}
489
490/// Formats a supposed Intel HEX byte string for display as part of an error message.
491///
492/// Does a lossy UTF-8 conversion (invalid characters represented as `?`) and removes whitespace.
493fn string_for_error(bytes: &[u8]) -> String {
494    String::from_utf8_lossy(bytes).trim().to_string()
495}
496
497/// Computes the LRC of the given byte slice.
498///
499/// The canonical implementation is a wrapping add followed by the two's
500/// complement (negation). Instead, we can just do a wrapping subtract
501/// from zero.
502fn checksum(bytes: &[u8]) -> u8 {
503    bytes.iter().fold(0, |acc, &b| acc.wrapping_sub(b))
504}
505
506/// Owned or borrowed data to be placed in a [`Frame`].
507///
508/// Since the data length in the [`Frame`] will be represented as a single byte,
509/// that length cannot exceed 255 (`0xFF`). `Data` is responsible for maintaining
510/// this invariant.
511///
512/// # Examples
513///
514/// ```
515/// use flipdot_core::{Address, Data, Frame, MsgType};
516/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
517/// #
518/// let data = Data::try_new(vec![1, 2, 3])?; // Ok since length under 255
519/// let frame = Frame::new(Address(2), MsgType(1), data);
520/// #
521/// # Ok(()) }
522/// ```
523#[derive(Debug, Clone, PartialEq, Eq, Hash)]
524pub struct Data<'a>(Cow<'a, [u8]>);
525
526impl<'a> Data<'a> {
527    /// Creates a new `Data` containing owned or borrowed data.
528    ///
529    /// Since the data length in the [`Frame`] will be represented as a single byte,
530    /// that length cannot exceed 255 (`0xFF`).
531    ///
532    /// # Errors
533    ///
534    /// Returns [`FrameError::DataTooLong`] if the data length is greater than 255 (`0xFF`).
535    ///
536    /// # Examples
537    ///
538    /// ```
539    /// use flipdot_core::Data;
540    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
541    /// #
542    /// let data = Data::try_new(vec![1, 2, 3])?;
543    /// assert_eq!(vec![1, 2, 3], data.get().as_ref());
544    /// #
545    /// # Ok(()) }
546    /// ```
547    ///
548    /// Borrowed data can also be used:
549    ///
550    /// ```
551    /// # use flipdot_core::Data;
552    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
553    /// #
554    /// let bytes = vec![1, 2, 3];
555    /// let data = Data::try_new(&bytes)?;
556    /// assert_eq!(vec![1, 2, 3], data.get().as_ref());
557    /// #
558    /// # Ok(()) }
559    /// ```
560    ///
561    /// This will fail since the passed-in vector is too large:
562    ///
563    /// ```
564    /// # use flipdot_core::Data;
565    /// let result = Data::try_new(vec![0; 1000]);
566    /// assert!(result.is_err());
567    /// ```
568    pub fn try_new<T: Into<Cow<'a, [u8]>>>(data: T) -> Result<Self, FrameError> {
569        let data: Cow<'a, [u8]> = data.into();
570        if data.len() > 0xFF {
571            return Err(FrameError::DataTooLong {
572                max: 0xFF,
573                actual: data.len(),
574            });
575        }
576        Ok(Data(data))
577    }
578
579    /// Returns a reference to the inner [`Cow`]`<[u8]>`.
580    ///
581    /// # Examples
582    ///
583    /// ```
584    /// # use flipdot_core::Data;
585    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
586    /// #
587    /// let data = Data::try_new(vec![])?;
588    /// assert!(data.get().is_empty());
589    /// #
590    /// # Ok(()) }
591    /// ```
592    pub fn get(&self) -> &Cow<'a, [u8]> {
593        &self.0
594    }
595}
596
597// Data is mostly used with small static arrays that obviously fit in the 255-byte limit,
598// so create some From impls that make that case simple. We unfortunately can't be generic
599// over integers yet, so use a macro to implement for common array lengths.
600macro_rules! impl_from_array_ref_with_length {
601    ($length:expr) => {
602        impl From<&'static [u8; $length]> for Data<'static> {
603            fn from(value: &'static [u8; $length]) -> Data<'static> {
604                Data::try_new(&value[..]).unwrap()
605            }
606        }
607    };
608}
609
610impl_from_array_ref_with_length!(0);
611impl_from_array_ref_with_length!(1);
612impl_from_array_ref_with_length!(2);
613impl_from_array_ref_with_length!(3);
614impl_from_array_ref_with_length!(4);
615
616#[cfg(test)]
617mod tests {
618    use super::*;
619    use std::error::Error;
620
621    #[test]
622    fn roundtrip_simple_frame() -> Result<(), Box<dyn Error>> {
623        let frame = Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF]));
624
625        let encoded = frame.to_bytes();
626        let decoded = Frame::from_bytes(&encoded)?;
627
628        assert_eq!(b":01007F02FF7F", encoded.as_slice());
629        assert_eq!(frame, decoded);
630
631        Ok(())
632    }
633
634    #[test]
635    fn roundtrip_complex_frame() -> Result<(), Box<dyn Error>> {
636        let data = Data::try_new(vec![
637            0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x06, 0x0C, 0x18, 0x7F, 0x7F, 0x00,
638        ])?;
639        let frame = Frame::new(Address(0x00), MsgType(0x00), data);
640
641        let encoded = frame.to_bytes();
642        let decoded = Frame::from_bytes(&encoded)?;
643
644        assert_eq!(&b":1000000001100000000000007F7F060C187F7F00B9"[..], encoded.as_slice());
645        assert_eq!(frame, decoded);
646
647        Ok(())
648    }
649
650    #[test]
651    fn roundtrip_complex_frame_newline() -> Result<(), Box<dyn Error>> {
652        let data = Data::try_new(vec![
653            0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x06, 0x0C, 0x18, 0x7F, 0x7F, 0x00,
654        ])?;
655        let frame = Frame::new(Address(0x00), MsgType(0x00), data);
656
657        let encoded = frame.to_bytes_with_newline();
658        let decoded = Frame::from_bytes(&encoded)?;
659
660        assert_eq!(&b":1000000001100000000000007F7F060C187F7F00B9\r\n"[..], encoded.as_slice());
661        assert_eq!(frame, decoded);
662
663        Ok(())
664    }
665
666    #[test]
667    fn roundtrip_empty_data() -> Result<(), Box<dyn Error>> {
668        let frame = Frame::new(Address(0x2B), MsgType(0xA9), Data::from(&[]));
669
670        let encoded = frame.to_bytes();
671        let decoded = Frame::from_bytes(&encoded)?;
672
673        assert_eq!(b":00002BA92C", encoded.as_slice());
674        assert_eq!(frame, decoded);
675
676        Ok(())
677    }
678
679    #[test]
680    fn data_length_over_255_rejected() {
681        let error = Data::try_new(vec![0; 256]).unwrap_err();
682        assert!(matches!(
683            error,
684            FrameError::DataTooLong {
685                max: 255,
686                actual: 256,
687                ..
688            }
689        ));
690    }
691
692    #[test]
693    fn newline_accepted() -> Result<(), Box<dyn Error>> {
694        let decoded = Frame::from_bytes(b":01007F02FF7F\r\n")?;
695        assert_eq!(Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF])), decoded);
696        Ok(())
697    }
698
699    #[test]
700    fn bad_checksum_detected() {
701        let error = Frame::from_bytes(b":01007F02FF7E").unwrap_err();
702        assert!(matches!(
703            error,
704            FrameError::BadChecksum {
705                expected: 0x7E,
706                actual: 0x7F,
707                ..
708            }
709        ));
710    }
711
712    #[test]
713    fn extra_data_detected() {
714        let error = Frame::from_bytes(b":00007F02007F").unwrap_err();
715        assert!(matches!(
716            error,
717            FrameError::FrameDataMismatch {
718                expected: 0,
719                actual: 1,
720                ..
721            }
722        ));
723    }
724
725    #[test]
726    fn missing_data_detected() {
727        let error = Frame::from_bytes(b":01007F027E").unwrap_err();
728        assert!(matches!(
729            error,
730            FrameError::FrameDataMismatch {
731                expected: 1,
732                actual: 0,
733                ..
734            }
735        ));
736    }
737
738    #[test]
739    fn invalid_format_detected() {
740        let error = Frame::from_bytes(b":01").unwrap_err();
741        assert!(matches!(error, FrameError::InvalidFrame { .. }));
742    }
743
744    #[test]
745    fn garbage_detected() {
746        let error = Frame::from_bytes(b"asdgdfg").unwrap_err();
747        assert!(matches!(error, FrameError::InvalidFrame { .. }));
748    }
749
750    #[test]
751    fn bad_char_detected() {
752        let error = Frame::from_bytes(b":01007F020z7E").unwrap_err();
753        assert!(matches!(error, FrameError::InvalidFrame { .. }));
754    }
755
756    #[test]
757    fn missing_char_detected() {
758        let error = Frame::from_bytes(b":01007F0207E").unwrap_err();
759        assert!(matches!(error, FrameError::InvalidFrame { .. }));
760    }
761
762    #[test]
763    fn leading_chars_detected() {
764        let error = Frame::from_bytes(b"abc:01007F02FF7Fa").unwrap_err();
765        assert!(matches!(error, FrameError::InvalidFrame { .. }));
766    }
767
768    #[test]
769    fn trailing_chars_detected() {
770        let error = Frame::from_bytes(b":01007F02FF7Fabc").unwrap_err();
771        assert!(matches!(error, FrameError::InvalidFrame { .. }));
772    }
773
774    #[test]
775    fn parsed_lifetime_independent() -> Result<(), Box<dyn Error>> {
776        let decoded = {
777            let string = b":01007F02FF7F".to_owned();
778            Frame::from_bytes(&string)?
779        };
780        assert_eq!(Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF])), decoded);
781        Ok(())
782    }
783
784    #[test]
785    fn getters() {
786        let frame = Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF]));
787        assert_eq!(frame.message_type(), MsgType(0x02));
788        assert_eq!(frame.address(), Address(0x7F));
789        assert_eq!(frame.data(), &vec![0xFFu8]);
790    }
791
792    #[test]
793    fn write() -> Result<(), Box<dyn Error>> {
794        let frame = Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF]));
795        let mut output = Vec::new();
796        frame.write(&mut output)?;
797        assert_eq!(b":01007F02FF7F\r\n", output.as_slice());
798        Ok(())
799    }
800
801    #[test]
802    fn read() -> Result<(), Box<dyn Error>> {
803        let mut buffer = &b":01007F02FF7F\r\n"[..];
804        let frame = Frame::read(&mut buffer)?;
805        assert_eq!(Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF])), frame);
806        Ok(())
807    }
808
809    #[test]
810    fn display() {
811        let frame = Frame::new(Address(0x7F), MsgType(0x02), Data::from(&[0xFF, 0xCB]));
812        let display = format!("{}", frame);
813        assert_eq!("Type 02 | Addr 007F | Data FF CB", display.trim());
814    }
815}