oxidef_compact1 0.1.0-alpha.1

Oxidef is an experimental interface definition language and serialisation scheme for efficient and strongly-typed payloads.
Documentation
use bytes::{Buf, BufMut};

use crate::{decoder, encoder};

/// Maximum value that fits in one byte in VU29
/// (This is 7 one bits: 8 bits - 1 bit of overhead)
pub const VU29_MAX_1BYTE: u32 = (1 << 7) - 1;

pub const VU29_MIN_2BYTE: u32 = VU29_MAX_1BYTE + 1;

/// Maximum value that fits in two bytes in VU29
/// (This is 14 one bits: 16 bits - 2 bits of overhead)
pub const VU29_MAX_2BYTE: u32 = (1 << 14) - 1;

pub const VU29_MIN_3BYTE: u32 = VU29_MAX_2BYTE + 1;

/// Maximum value that fits in three bytes in VU29
/// (This is 21 one bits: 24 bits - 3 bits of overhead)
pub const VU29_MAX_3BYTE: u32 = (1 << 21) - 1;

pub const VU29_MIN_4BYTE: u32 = VU29_MAX_3BYTE + 1;

/// Maximum value that fits in four bytes in VU29
/// (This is 29 one bits: 32 bits - 3 bits of overhead)
pub const VU29_MAX_4BYTE: u32 = (1 << 29) - 1;

/// Encode a VU29 integer.
///
/// The VU29 integer will be emitted in canonical form.
///
/// If the integer is too large, returns Err.
pub fn encode_vu29(buf: &mut impl BufMut, val: u32) -> Result<(), encoder::EncError> {
    match val {
        0..=VU29_MAX_1BYTE => {
            buf.put_u8(val as u8);
            Ok(())
        }
        VU29_MIN_2BYTE..=VU29_MAX_2BYTE => {
            let bytes = [
                (val & 0b0011_1111) as u8 | 0b1000_0000,
                ((val >> 6) & 0xFF) as u8,
            ];
            buf.put_slice(&bytes[..]);
            Ok(())
        }
        VU29_MIN_3BYTE..=VU29_MAX_3BYTE => {
            let bytes = [
                (val & 0b0001_1111) as u8 | 0b1100_0000,
                ((val >> 5) & 0xFF) as u8,
                ((val >> 13) & 0xFF) as u8,
            ];
            buf.put_slice(&bytes[..]);
            Ok(())
        }
        VU29_MIN_4BYTE..=VU29_MAX_4BYTE => {
            let bytes = [
                (val & 0b0001_1111) as u8 | 0b1110_0000,
                ((val >> 5) & 0xFF) as u8,
                ((val >> 13) & 0xFF) as u8,
                ((val >> 21) & 0xFF) as u8,
            ];
            buf.put_slice(&bytes[..]);
            Ok(())
        }
        _ => Err(encoder::EncError::VarUintOverflow),
    }
}

pub fn encode_vu29_with_backing(
    backing: &mut [u8; 4],
    val: u32,
) -> Result<&[u8], encoder::EncError> {
    let mut cursor = &mut backing[..];
    encode_vu29(&mut cursor, val)?;
    // any left over bytes in the buffer are not part of the vu29 tag
    let vu29_length = 4 - cursor.len();
    Ok(&backing[0..vu29_length])
}

/// Decode a VU29 integer.
///
/// The VU29 integer must be in canonical form, otherwise Err is returned.
pub fn decode_vu29(buf: &mut impl Buf) -> Result<u32, decoder::DecError> {
    let first_byte = buf.try_get_u8()?;
    Ok(match first_byte {
        0b0000_0000..=0b0111_1111 => first_byte as u32,
        0b1000_0000..=0b1011_1111 => {
            let supplemental = buf.try_get_u8()?;
            let out = ((supplemental as u32) << 6) | (first_byte as u32 & 0b0011_1111);
            if out <= VU29_MAX_1BYTE {
                return Err(decoder::DecError::VarUintNotCanonical);
            }
            out
        }
        0b1100_0000..=0b1101_1111 => {
            let supplemental = buf.try_get_u16_le()?;
            let out = ((supplemental as u32) << 5) | (first_byte as u32 & 0b0001_1111);
            if out <= VU29_MAX_2BYTE {
                return Err(decoder::DecError::VarUintNotCanonical);
            }
            out
        }
        0b1110_0000..=0b1111_1111 => {
            let supplemental1 = buf.try_get_u16_le()?;
            let supplemental2 = buf.try_get_u8()?;
            let out = ((supplemental1 as u32) << 5)
                | ((supplemental2 as u32) << 21)
                | (first_byte as u32 & 0b0001_1111);
            if out <= VU29_MAX_3BYTE {
                return Err(decoder::DecError::VarUintNotCanonical);
            }
            out
        }
    })
}

#[cfg(test)]
mod test {
    use alloc::vec::Vec;

    use crate::vu29::{decode_vu29, encode_vu29, VU29_MAX_3BYTE, VU29_MAX_4BYTE};

    #[test]
    fn test_encode_vu29() {
        #[track_caller]
        fn expect(bytes: &[u8], decoded: u32) {
            let mut target: Vec<u8> = Vec::new();
            encode_vu29(&mut target, decoded).unwrap();
            assert_eq!(bytes, &target[..]);
        }

        #[track_caller]
        fn expect_error(decoded: u32) {
            let mut target: Vec<u8> = Vec::new();
            encode_vu29(&mut target, decoded).unwrap_err();
        }

        expect(&[0], 0);
        expect(&[42], 42);
        expect(&[0b0100_0000], 64);
        expect(&[0b0111_1111], 127);

        expect(&[0b1000_0000, 0b0000_0010], 128);
        expect(&[0b1000_0000, 0b1000_0000], 8192);

        expect(&[0b1101_1111, 0b1111_1111, 0b1111_1111], VU29_MAX_3BYTE);

        expect(
            &[0b1110_0000, 0b0000_0000, 0b0000_0000, 0b0000_0001],
            VU29_MAX_3BYTE + 1,
        );
        expect(
            &[0b1111_1111, 0b1111_1111, 0b1111_1111, 0b1111_1111],
            VU29_MAX_4BYTE,
        );

        // out of range
        expect_error(1 << 29);
    }

    #[test]
    fn test_decode_vu29() {
        #[track_caller]
        fn expect(mut bytes: &[u8], decoded: u32) {
            assert_eq!(decode_vu29(&mut bytes).unwrap(), decoded);
            assert!(bytes.is_empty());
        }

        #[track_caller]
        fn expect_error(mut bytes: &[u8]) {
            decode_vu29(&mut bytes).unwrap_err();
        }

        expect(&[0], 0);
        expect(&[42], 42);
        expect(&[0b0100_0000], 64);
        expect(&[0b0111_1111], 127);
        // too short
        expect_error(&[0b1000_0000]);

        // not canonical
        expect_error(&[0b1000_0000, 0b0000_0000]);
        expect_error(&[0b1000_0000, 0b0000_0001]); // non-canonical 64

        expect(&[0b1000_0000, 0b0000_0010], 128);
        expect(&[0b1000_0000, 0b1000_0000], 8192);

        // too short
        expect_error(&[0b1100_0000]);
        expect_error(&[0b1100_0000, 0b0000_0001]);

        // not canonical
        expect_error(&[0b1100_0000, 0b0000_0001, 0b0000_0000]); // trailing zero
        expect_error(&[0b1100_0000, 0b0000_0000, 0b0000_0001]); // non-canonical 8192

        expect(&[0b1101_1111, 0b1111_1111, 0b1111_1111], VU29_MAX_3BYTE);

        // not canonical
        expect_error(&[0b1110_0000, 0b0000_0001, 0b0000_0000, 0b0000_0000]); // trailing zero

        expect(
            &[0b1110_0000, 0b0000_0000, 0b0000_0000, 0b0000_0001],
            VU29_MAX_3BYTE + 1,
        );
        expect(
            &[0b1111_1111, 0b1111_1111, 0b1111_1111, 0b1111_1111],
            VU29_MAX_4BYTE,
        );
    }
}