neodyn_xc 0.4.0

Neodyn Exchange is the serialization format for the Neodyn database engine
Documentation
//! This module codifies the bit-level details of the binary format.
//!
//! It is a separate module because it is needed by both the serializer
//! and the deserializer.

// We exceptionally allow missing docs for items because they
// will all be documented in logical groups at the module level.m
// TODO(H2CO3): actually add documentation for all the items here
#![allow(missing_docs, clippy::missing_docs_in_private_items)]

// We allow "inconsistent" digit grouping because the constants defined here
// have a specific, well-defined layout:
//
// * The 3 most significant bits define the major type of a value tag.
// * Then either:
//     * The next 3 bits define its minor type; and
//     * The 2 least significant bits specify the log (base 2) of the length
//       of the data that follows, or in case of null, optional, and bool,
//       they define the type and the value itself;
// * Or, instead:
//     * The 5 least significant bits define the payload of a small value.
//
// Thus, the bits should be grouped as 3 + 3 + 2. This is labelled "inconsistent"
// by Clippy, but in this (exceptional and special) case, Clippy is wrong.
#![allow(clippy::inconsistent_digit_grouping, clippy::unusual_byte_groupings)]

use std::ops::RangeInclusive;
use std::convert::TryFrom;

pub const MAJOR_TYPE_MASK:    u8 = 0b111_000_00;
pub const MINOR_TYPE_MASK:    u8 = 0b000_111_00;
pub const VALUE_TAG_MASK:     u8 = 0b000_000_11;
pub const LOG_LENGTH_MASK:    u8 = 0b000_000_11;
pub const SMALL_PAYLOAD_MASK: u8 = 0b000_111_11;
pub const SMALL_INT_SIGN_BIT: i8 = 0b000_100_00;

// Symbol Table Major Type Tags
pub const MAJOR_TYPE_SMALL_BLOB_ONCE:    u8 = 0b010_000_00;
pub const MAJOR_TYPE_SMALL_BLOB_MULTI:   u8 = 0b011_000_00;
pub const MAJOR_TYPE_SMALL_STRING_ONCE:  u8 = 0b100_000_00;
pub const MAJOR_TYPE_SMALL_STRING_MULTI: u8 = 0b101_000_00;
pub const MAJOR_TYPE_BIG_SYMBOL:         u8 = 0b111_000_00;

// For `MAJOR_TYPE_BIG_SYMBOL`
pub const MINOR_TYPE_BLOB_ONCE:    u8 = 0b000_010_00;
pub const MINOR_TYPE_BLOB_MULTI:   u8 = 0b000_011_00;
pub const MINOR_TYPE_STRING_ONCE:  u8 = 0b000_100_00;
pub const MINOR_TYPE_STRING_MULTI: u8 = 0b000_101_00;

// Value Major Type Tags
pub const MAJOR_TYPE_SIMPLE:       u8 = 0b000_000_00;
pub const MAJOR_TYPE_SMALL_INT:    u8 = 0b001_000_00;
pub const MAJOR_TYPE_SMALL_UINT:   u8 = 0b010_000_00;
pub const MAJOR_TYPE_SMALL_STRING: u8 = 0b011_000_00;
pub const MAJOR_TYPE_SMALL_BLOB:   u8 = 0b100_000_00;
pub const MAJOR_TYPE_SMALL_ARRAY:  u8 = 0b101_000_00;
pub const MAJOR_TYPE_SMALL_MAP:    u8 = 0b110_000_00;
pub const MAJOR_TYPE_BIG_VALUE:    u8 = 0b111_000_00;

// For `MAJOR_TYPE_SIMPLE`
pub const MINOR_TYPE_SYMTAB: u8 = 0b000_000_00;
pub const MINOR_TYPE_VALUE:  u8 = 0b000_001_00;
pub const MINOR_TYPE_EMPTY:  u8 = 0b000_010_00;

// For `MAJOR_TYPE_BIG_VALUE`
pub const MINOR_TYPE_INT:    u8 = 0b000_001_00;
pub const MINOR_TYPE_UINT:   u8 = 0b000_010_00;
pub const MINOR_TYPE_STRING: u8 = 0b000_011_00;
pub const MINOR_TYPE_BLOB:   u8 = 0b000_100_00;
pub const MINOR_TYPE_ARRAY:  u8 = 0b000_101_00;
pub const MINOR_TYPE_MAP:    u8 = 0b000_110_00;
pub const MINOR_TYPE_FLOAT:  u8 = 0b000_111_00;

// For `MINOR_TYPE_VALUE`
pub const VALUE_TAG_NULL:  u8 = 0b000_000_00;
pub const VALUE_TAG_OPT:   u8 = 0b000_000_01;
pub const VALUE_TAG_FALSE: u8 = 0b000_000_10;
pub const VALUE_TAG_TRUE:  u8 = 0b000_000_11;

// For `MINOR_TYPE_EMPTY`
pub const VALUE_TAG_EMPTY_STRING: u8 = 0b000_000_00;
pub const VALUE_TAG_EMPTY_BLOB:   u8 = 0b000_000_01;

// Convenience constants for the above value tags
pub const SYMTAB_MAJOR_MINOR: u8 = MAJOR_TYPE_SIMPLE | MINOR_TYPE_SYMTAB;
pub const SIMPLE_VALUE_MAJOR_MINOR: u8 = MAJOR_TYPE_SIMPLE | MINOR_TYPE_VALUE;
pub const EMPTY_VALUE_MAJOR_MINOR: u8 = MAJOR_TYPE_SIMPLE | MINOR_TYPE_EMPTY;

pub const VALUE_BYTE_NULL:  u8 = SIMPLE_VALUE_MAJOR_MINOR | VALUE_TAG_NULL;
pub const VALUE_BYTE_OPT:   u8 = SIMPLE_VALUE_MAJOR_MINOR | VALUE_TAG_OPT;
pub const VALUE_BYTE_FALSE: u8 = SIMPLE_VALUE_MAJOR_MINOR | VALUE_TAG_FALSE;
pub const VALUE_BYTE_TRUE:  u8 = SIMPLE_VALUE_MAJOR_MINOR | VALUE_TAG_TRUE;

pub const VALUE_BYTE_EMPTY_STRING: u8 =
    EMPTY_VALUE_MAJOR_MINOR | VALUE_TAG_EMPTY_STRING;
pub const VALUE_BYTE_EMPTY_BLOB:   u8 =
    EMPTY_VALUE_MAJOR_MINOR | VALUE_TAG_EMPTY_BLOB;

/// Helper for creating the inline encoding of a small unsigned integer,
/// or that of a short array, map, or low-index interned string or blob.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SmallUint(u8);

impl SmallUint {
    /// The range of values that fit in a `SmallUint`.
    pub const RANGE: RangeInclusive<u8> = 0..=31;

    /// Returns the encoded number as a single byte,
    /// using the specified major type for tagging it.
    #[must_use]
    pub const fn encode(self, major: u8) -> u8 {
        (major & MAJOR_TYPE_MASK) | (self.0 & SMALL_PAYLOAD_MASK)
    }
}

impl TryFrom<u64> for SmallUint {
    type Error = ();

    fn try_from(value: u64) -> Result<Self, Self::Error> {
        if let Ok(x) = u8::try_from(value) {
            if Self::RANGE.contains(&x) {
                return Ok(SmallUint(x));
            }
        }

        Err(())
    }
}

impl TryFrom<usize> for SmallUint {
    type Error = ();

    fn try_from(value: usize) -> Result<Self, Self::Error> {
        if let Ok(x) = u8::try_from(value) {
            if Self::RANGE.contains(&x) {
                return Ok(SmallUint(x));
            }
        }

        Err(())
    }
}

/// Helper for creating the inline encoding of a small signed integer.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SmallInt(i8);

impl SmallInt {
    /// The range of values that fit in a `SmallInt`.
    pub const RANGE: RangeInclusive<i8> = -16..=15;

    /// Returns the encoded number as a single byte and `MAJOR_TYPE_SMALL_INT`.
    #[allow(clippy::cast_sign_loss)]
    #[must_use]
    pub const fn encode(self) -> u8 {
        MAJOR_TYPE_SMALL_INT | (self.0 as u8 & SMALL_PAYLOAD_MASK)
    }
}

impl TryFrom<i64> for SmallInt {
    type Error = ();

    fn try_from(value: i64) -> Result<Self, Self::Error> {
        if let Ok(x) = i8::try_from(value) {
            if Self::RANGE.contains(&x) {
                return Ok(SmallInt(x));
            }
        }

        Err(())
    }
}