bitsandbytes 0.1.0

An owned, bit-aware binary codec: fast bit/byte field types and the unified #[bin] macro.
Documentation
//! Phase 1 capstone (ROADMAP exit): a **complete DMR frame** — slot type +
//! a 264-bit burst (with a 48-bit sync `BitEnum`) + a CRC payload — composed from
//! the native bit codec and round-tripping with **no binrw**. Plus proptest
//! round-trips and a golden byte vector.
//!
//! Exercises, together: sub-byte leaf fields (`u4`/`u108`), a sub-byte
//! `#[derive(BitEnum)]` (the 48-bit sync, catch-all preserving unknown patterns),
//! a `#[nested]` sub-message (the burst), and a `[u8; N]` payload.

use bnb::{BitDecode, BitEncode, BitEnum, u4, u48, u108};
use proptest::prelude::*;

/// ETSI TS 102 361-1 Table 9.2 — 48-bit SYNC patterns; `Unknown` preserves any
/// other pattern losslessly (dual-use).
#[derive(BitEnum, Copy, Clone, Eq, PartialEq, Debug)]
#[bit_enum(u48)]
#[repr(u64)]
enum Sync {
    BaseStationVoice = 0x755F_D7DF_75F7,
    BaseStationData = 0xDFF5_7D75_DF5D,
    MobileStationVoice = 0x7F7D_5DD5_7DFD,
    #[catch_all]
    Unknown(u48),
}

/// A 264-bit DMR burst: `108 | 48 (sync) | 108`, none byte-aligned.
#[derive(BitDecode, BitEncode, Copy, Clone, Eq, PartialEq, Debug)]
struct Burst {
    payload_1: u108,
    sync: Sync,
    payload_2: u108,
}

/// A complete frame: a slot-type byte (color code + data type), the burst, and a
/// 2-byte CRC. 4 + 4 + 264 + 16 = 288 bits = 36 bytes.
#[derive(BitDecode, BitEncode, Copy, Clone, Eq, PartialEq, Debug)]
struct Frame {
    color_code: u4,
    data_type: u4,
    #[nested]
    burst: Burst,
    crc: [u8; 2],
}

fn frame_with(sync: Sync) -> Frame {
    Frame {
        color_code: u4::new(0x5),
        data_type: u4::new(0xA),
        burst: Burst {
            payload_1: u108::from_raw(0x0123_4567_89AB_CDEF_0123_4567),
            sync,
            payload_2: u108::from_raw(0x0FED_CBA9_8765_4321_0FED_CBA9),
        },
        crc: [0xBE, 0xEF],
    }
}

#[test]
fn frame_round_trips_with_no_binrw() {
    let frame = frame_with(Sync::BaseStationData);
    let bytes = frame.to_bytes().unwrap();
    assert_eq!(bytes.len(), 36, "288 bits");
    assert_eq!(Frame::decode_exact(&bytes).unwrap(), frame);
}

#[test]
fn recognizes_a_known_sync_and_preserves_unknown() {
    // A known Table 9.2 sync survives the round-trip as its named variant.
    let known = frame_with(Sync::MobileStationVoice);
    assert_eq!(
        Frame::decode_exact(&known.to_bytes().unwrap())
            .unwrap()
            .burst
            .sync,
        Sync::MobileStationVoice
    );

    // An off-table sync is preserved losslessly (dual-use), not rejected.
    let bogus = frame_with(Sync::Unknown(u48::from_raw(0x0000_0000_0001)));
    let decoded = Frame::decode_exact(&bogus.to_bytes().unwrap()).unwrap();
    assert_eq!(
        decoded.burst.sync,
        Sync::Unknown(u48::from_raw(0x0000_0000_0001))
    );
}

#[test]
fn golden_bytes() {
    // A fixed frame encodes to a fixed 36-byte vector (regression guard).
    let frame = frame_with(Sync::BaseStationVoice);
    let bytes = frame.to_bytes().unwrap();
    let golden: [u8; 36] = [
        0x5a, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x77,
        0x55, 0xfd, 0x7d, 0xf7, 0x5f, 0x70, 0x00, 0x0f, 0xed, 0xcb, 0xa9, 0x87, 0x65, 0x43, 0x21,
        0x0f, 0xed, 0xcb, 0xa9, 0xbe, 0xef,
    ];
    assert_eq!(bytes, golden, "got {bytes:02x?}");
    // ...and the golden bytes decode back to the frame.
    assert_eq!(Frame::decode_exact(&golden).unwrap(), frame);
}

proptest! {
    /// encode ∘ decode = id over random field values.
    #[test]
    fn frame_round_trips_prop(
        cc in any::<u8>(),
        dt in any::<u8>(),
        p1 in any::<u128>(),
        p2 in any::<u128>(),
        sync_raw in any::<u64>(),
        crc in any::<[u8; 2]>(),
    ) {
        let frame = Frame {
            color_code: u4::from_raw(cc),
            data_type: u4::from_raw(dt),
            burst: Burst {
                payload_1: u108::from_raw(p1),
                sync: <Sync as bnb::Bits>::from_bits(u128::from(sync_raw)),
                payload_2: u108::from_raw(p2),
            },
            crc,
        };
        let bytes = frame.to_bytes().unwrap();
        prop_assert_eq!(bytes.len(), 36);
        prop_assert_eq!(Frame::decode_exact(&bytes).unwrap(), frame);
    }
}