collum 0.1.0

A crate for cleanly describing and parsing bit-wide data structures
Documentation
//! # Collum
//!
//! Collum's goal is to make parsing packed bit-level protocols as easy as describing the data
//! structure, just like we would do in C; except in this case we get to use the beauty of Rust's
//! design and not an ambiguous and unsafe cast.
//!
//! To go over some of collum's bit-level parsing capabilities we'll go over an example of parsing
//! 2-bit integers from byte-slices. We'll simply define the two bit integer as a tuple struct with
//! two booleans (true or false representing the bit state).
//!
//! ```
//! # use collum::{BitSlice, Collum};
//! # use collum_derive::{Collum, Sized};
//!
//! #[derive(Collum, Sized, Debug, PartialEq)]
//! struct U2(bool, bool);
//!
//! # fn main() { example(); }
//! #
//! # fn example() -> Option<()> {
//! let bits = BitSlice::from(&[0b11000110]);
//!
//! let (first4bits, bits) = <[U2; 2]>::take_from_bit_slice(bits)?;
//! assert_eq!(first4bits, [U2(true, true), U2(false, false)]);
//!
//! let (last4bits, bits) = <[U2; 2]>::take_from_bit_slice(bits)?;
//! assert_eq!(last4bits, [U2(false, true), U2(true, false)]);
//!
//! // `bits` gives the remaining size of the bit slice.
//! assert_eq!(bits.bits(), 0);
//!
//! # Some(())
//! # }
//! ```

#![deny(missing_docs)]
#![no_std]

#[cfg(test)]
#[macro_use]
extern crate std;

mod impls;
mod sized;
mod slice;

/// A sub-module exposing type information for endianness. In most cases defining the type using
/// the type itself is not needed and using `#[collum(endian(..))]` should suffice.
pub mod endian;

pub use self::sized::*;
pub use self::slice::*;

#[cfg(feature = "derive")]
pub use collum_derive::*;

/// Trait for a type parsable from a [`BitSlice`] while returning the left of the slice.
pub trait Collum: crate::Sized + core::marker::Sized {
    /// Extracts the parsed `Self` from `slice`, respecting bit-offsets and returns the parsed
    /// value along with the adjusted offset of the [`BitSlice`].
    fn take_from_bit_slice(slice: BitSlice<'_>) -> Option<(Self, BitSlice<'_>)>;

    /// Helper function to extract the parsed `Self` from a byte slice and get back the remaining
    /// data as a [`BitSlice`].
    fn take_from_bytes(slice: &[u8]) -> Option<(Self, BitSlice<'_>)> {
        Self::take_from_bit_slice(slice.into())
    }

    /// Helper function for parsing `Self` without needing the remaining [`BitSlice`] back.
    fn from_bit_slice(slice: BitSlice<'_>) -> Option<Self> {
        Self::take_from_bit_slice(slice).map(|(s, ..)| s)
    }

    /// Helper function for parsing `Self` from a byte slice without needing the remaining
    /// [`BitSlice`] back.
    fn from_bytes(slice: &[u8]) -> Option<Self> {
        Self::take_from_bit_slice(slice.into()).map(|(s, ..)| s)
    }
}

#[cfg(test)]
mod tests {
    extern crate self as collum;

    use collum::{Collum, Sized};
    use collum_derive::{Collum, Sized};

    use crate::BitOffset;
    use crate::BitSlice;

    #[test]
    fn parse_udp_header() {
        #[derive(Collum, Sized, Debug, PartialEq)]
        #[collum(endian = "big")]
        struct UdpHeader {
            src: u16,
            dst: u16,
            len: u16,
            checksum: u16,
        }

        // This is pretty much what the implementation should look like.
        /*
        impl Collum for UdpHeader {
            fn take_from_bit_slice(mut slice: BitSlice) -> Option<(Self, BitSlice)> {
                let src;
                let dst;
                let len;
                let checksum;

                (Big(src), slice) = <Big<u16>>::take_from_bit_slice(slice)?;
                (Big(dst), slice) = <Big<u16>>::take_from_bit_slice(slice)?;
                (Big(len), slice) = <Big<u16>>::take_from_bit_slice(slice)?;
                (Big(checksum), slice) = <Big<u16>>::take_from_bit_slice(slice)?;

                Some((
                    UdpHeader {
                        src,
                        dst,
                        len,
                        checksum,
                    },
                    slice,
                ))
            }
        }
        */

        assert_eq!(UdpHeader::bits(), 8 * 8);

        assert_eq!(
            UdpHeader::from_bytes(&[0x01, 0xbb, 0xbf, 0xe5, 0x00, 0x21, 0x00, 0x00]),
            Some(UdpHeader {
                src: 443,
                dst: 49125,
                len: 33,
                checksum: 0,
            })
        );
    }

    #[test]
    fn parse_u2_manual() {
        #[derive(Debug, PartialEq)]
        struct U2(u8);

        impl Sized for U2 {
            fn bits() -> usize {
                2
            }
        }

        impl Collum for U2 {
            fn take_from_bit_slice(slice: BitSlice) -> Option<(Self, BitSlice)> {
                let [b] = slice.copy_bits::<1>(2)?;

                Some((U2((b & 0xc0) >> 6), slice.offset_by_sized::<U2>()?))
            }
        }

        assert_eq!(U2::from_bytes(&[1, 2, 3]), Some(U2(0)));
        assert_eq!(U2::from_bytes(&[0b11111111, 0]), Some(U2(0b11)));
        assert_eq!(U2::from_bytes(&[0b10000000, 0]), Some(U2(0b10)));

        assert_eq!(
            <[U2; 10]>::from_bytes(&[0b00000001, 0b00000010, 0b00000011]),
            Some([
                U2(0b00),
                U2(0b00),
                U2(0b00),
                U2(0b01),
                U2(0b00),
                U2(0b00),
                U2(0b00),
                U2(0b10),
                U2(0b00),
                U2(0b00)
            ])
        );
        assert_eq!(
            <[U2; 4]>::from_bytes(&[0b11111111]),
            Some([const { U2(0b11) }; 4])
        );
        assert_eq!(<[U2; 5]>::from_bytes(&[0b11000110]), None);
        assert_eq!(
            <[U2; 4]>::from_bytes(&[0b11000110]),
            Some([U2(0b11), U2(0b00), U2(0b01), U2(0b10)])
        );

        assert_eq!(
            <[U2; 5]>::take_from_bit_slice((&[0b11000110, 0b10011100]).into()),
            Some((
                [U2(0b11), U2(0b00), U2(0b01), U2(0b10), U2(0b10)],
                BitSlice {
                    data: &[0b0011100],
                    bit_offset: BitOffset::Bit2,
                }
            ))
        );
    }

    #[test]
    fn parse_u2_derived() {
        #[derive(Collum, Sized, Debug, PartialEq)]
        struct U2(bool, bool);

        assert_eq!(U2::from_bytes(&[1, 2, 3]), Some(U2(false, false)));
        assert_eq!(U2::from_bytes(&[0b11111111, 0]), Some(U2(true, true)));
        assert_eq!(U2::from_bytes(&[0b10000000, 0]), Some(U2(true, false)));

        assert_eq!(
            <[U2; 10]>::from_bytes(&[0b00000001, 0b00000010, 0b00000011]),
            Some([
                U2(false, false),
                U2(false, false),
                U2(false, false),
                U2(false, true),
                U2(false, false),
                U2(false, false),
                U2(false, false),
                U2(true, false),
                U2(false, false),
                U2(false, false)
            ])
        );
        assert_eq!(
            <[U2; 4]>::from_bytes(&[0b11111111]),
            Some([const { U2(true, true) }; 4])
        );
        assert_eq!(<[U2; 5]>::from_bytes(&[0b11000110]), None);
        assert_eq!(
            <[U2; 4]>::from_bytes(&[0b11000110]),
            Some([
                U2(true, true),
                U2(false, false),
                U2(false, true),
                U2(true, false)
            ])
        );

        assert_eq!(
            <[U2; 5]>::take_from_bit_slice((&[0b11000110, 0b10011100]).into()),
            Some((
                [
                    U2(true, true),
                    U2(false, false),
                    U2(false, true),
                    U2(true, false),
                    U2(true, false)
                ],
                BitSlice {
                    data: &[0b0011100],
                    bit_offset: BitOffset::Bit2,
                }
            ))
        );
    }

    #[test]
    fn parse_empty() {
        #[derive(Collum, Sized, Debug, PartialEq)]
        struct Unit;

        assert_eq!(Unit::bits(), 0);
        assert_eq!(Unit::from_bytes(&[1, 2, 3]), Some(Unit));
    }
}