le-stream 10.1.4

De-/serialize objects from/to little endian byte streams
Documentation
//! Coverage for derived structs and represented enums.

#![cfg(all(test, feature = "derive"))]
#![allow(missing_docs)]

use le_stream::{FromLeStream, ToLeStream};

const COMPLEX_BYTES: [u8; 17] = [
    0xCD, 0xAB, 0x01, 0x01, 0x02, 0x03, 0xFE, 0xFF, 0x34, 0x12, 0x03, 0x00, 0x10, 0x02, 0x10, 0xFE,
    0xCA,
];

#[derive(Clone, Debug, Eq, PartialEq, FromLeStream, ToLeStream)]
struct Header {
    magic: u16,
    enabled: bool,
    modes: [u8; 3],
}

#[derive(Clone, Debug, Eq, PartialEq, FromLeStream, ToLeStream)]
struct Payload {
    sample_count: u8,
    window: core::ops::RangeInclusive<u16>,
}

#[derive(Clone, Debug, Eq, PartialEq, FromLeStream, ToLeStream)]
struct ComplexPacket {
    header: Header,
    samples: [i16; 2],
    payload: Payload,
    checksum: Option<u16>,
}

#[derive(Clone, Debug, Eq, PartialEq, FromLeStream, ToLeStream)]
struct OptionalTail {
    code: u8,
    tail: Option<u16>,
}

#[derive(Clone, Debug, Eq, PartialEq, FromLeStream, ToLeStream)]
#[repr(u8)]
enum UnitMessage {
    Ping = 0x01,
    Pong = 0x02,
}

#[derive(Clone, Debug, Eq, PartialEq, FromLeStream, ToLeStream)]
#[repr(u16)]
enum TupleMessage {
    Small(u8, u16) = 0x1001,
    Large(u32) = 0x1002,
}

#[derive(Clone, Debug, Eq, PartialEq, FromLeStream, ToLeStream)]
#[repr(u32)]
enum NamedMessage {
    Data { enabled: bool, sequence: u16 } = 0x0102_0304,
    Done = 0x0506_0708,
}

#[derive(Clone, Debug, Eq, PartialEq, FromLeStream, ToLeStream)]
#[repr(i16)]
enum SignedMessage {
    Negative(u8) = -2,
    Positive = 0x0102,
}

fn complex_packet() -> ComplexPacket {
    ComplexPacket {
        header: Header {
            magic: 0xABCD,
            enabled: true,
            modes: [0x01, 0x02, 0x03],
        },
        samples: [-2, 0x1234],
        payload: Payload {
            sample_count: 3,
            window: 0x1000..=0x1002,
        },
        checksum: Some(0xCAFE),
    }
}

#[test]
fn derived_complex_struct_serializes_in_field_order() {
    assert_eq!(
        complex_packet().to_le_stream().collect::<Vec<_>>(),
        COMPLEX_BYTES
    );
}

#[test]
fn derived_complex_struct_deserializes_in_field_order() {
    assert_eq!(
        ComplexPacket::from_le_stream_exact(COMPLEX_BYTES.into_iter()),
        Ok(complex_packet())
    );
}

#[test]
fn derived_struct_supports_absent_trailing_option() {
    let bytes = [0xA5];
    let value = OptionalTail {
        code: 0xA5,
        tail: None,
    };

    assert_eq!(
        OptionalTail::from_le_stream_exact(bytes.into_iter()),
        Ok(value)
    );
}

#[test]
fn repr_u8_unit_enum_round_trips() {
    assert_eq!(UnitMessage::Pong.to_le_stream().collect::<Vec<_>>(), [0x02]);
    assert_eq!(
        UnitMessage::from_le_stream_exact([0x01].into_iter()),
        Ok(UnitMessage::Ping)
    );
}

#[test]
fn repr_u16_tuple_enum_round_trips_with_associated_data() {
    let value = TupleMessage::Small(0xA5, 0x1234);
    let bytes = [0x01, 0x10, 0xA5, 0x34, 0x12];

    assert_eq!(value.clone().to_le_stream().collect::<Vec<_>>(), bytes);
    assert_eq!(
        TupleMessage::from_le_stream_exact(bytes.into_iter()),
        Ok(value)
    );
}

#[test]
fn repr_u32_named_enum_round_trips_with_associated_data() {
    let value = NamedMessage::Data {
        enabled: true,
        sequence: 0x1234,
    };
    let bytes = [0x04, 0x03, 0x02, 0x01, 0x01, 0x34, 0x12];

    assert_eq!(value.clone().to_le_stream().collect::<Vec<_>>(), bytes);
    assert_eq!(
        NamedMessage::from_le_stream_exact(bytes.into_iter()),
        Ok(value)
    );
}

#[test]
fn repr_i16_enum_round_trips_signed_discriminant() {
    let value = SignedMessage::Negative(0xA5);
    let bytes = [0xFE, 0xFF, 0xA5];

    assert_eq!(value.clone().to_le_stream().collect::<Vec<_>>(), bytes);
    assert_eq!(
        SignedMessage::from_le_stream_exact(bytes.into_iter()),
        Ok(value)
    );
}

#[test]
fn enum_deserialization_rejects_unknown_discriminants() {
    assert_eq!(UnitMessage::from_le_stream([0xFF].into_iter()), None);
}