thruput-xdrs 0.17.1

XDR Serialization Library
Documentation
//! Provide a generic TXS implementation for any type
//! that implements some basic byte operations

#![allow(clippy::upper_case_acronyms)]

use crate::serialize::writer::*;
use crate::serialize::writer_unchecked::*;

/// Implement this basic byte operation in order
/// to get a `Writer<TXS>` implementation for free
pub(crate) trait Put {
    // fn put_byte(&mut self, b: u8) -> Result<()>;
    fn put_bytes(&mut self, bytes: &[u8]) -> WriteResult<()>;

    // Per benchmarks, putting an opaque vec using just put_bytes is quite expensiver
    // (and somewhat surprisingly, much slower than the equivalent on CXS).
    // As previously implemented with put_bytes(), it was 3 put_bytes calls
    // (header, value, padding).
    fn put_opaque_variable(&mut self, bytes: &[u8]) -> WriteResult<()>;

    // put_opaque_fixed is similarly expensive
    fn put_opaque_fixed(&mut self, bytes: &[u8]) -> WriteResult<()>;
}

pub struct TXS;

/// The number of zero bytes that pad an opaque of `len` bytes to a 4-byte
/// boundary.
#[inline]
fn padding_len(len: usize) -> usize {
    (4 - len % 4) % 4
}

static PADDING: [u8; 3] = [0; 3];

macro_rules! write_int {
    [ $($write:ident($nbytes:expr): $ty:ty),* $(,)? ] => {

    $(
        #[inline]
        fn $write(&mut self, n: $ty) -> WriteResult<()> {
            let bytes: [u8; $nbytes] = n.to_be_bytes();
            self.put_bytes(&bytes)
        }
    )*

    };
}

impl<P> Writer<TXS> for P
where
    P: Put,
{
    #[inline]
    fn write_opaque_fixed(&mut self, bytes: &[u8]) -> WriteResult<()> {
        debug_assert!(bytes.len() <= u32::MAX as usize);
        self.put_opaque_fixed(bytes)
    }

    #[inline]
    fn write_opaque_unpadded(&mut self, bytes: &[u8]) -> WriteResult<()> {
        self.put_bytes(bytes)
    }

    #[inline]
    fn write_opaque_padding(&mut self, len: usize) -> WriteResult<()> {
        self.put_bytes(&PADDING[..padding_len(len)])
    }

    #[inline]
    fn write_opaque_variable(&mut self, bytes: &[u8]) -> WriteResult<()> {
        if bytes.len() > u32::MAX as usize {
            return Err(WriteError::InvalidArrayLength);
        }

        self.put_opaque_variable(bytes)
    }

    #[inline]
    fn write_bool(&mut self, v: bool) -> WriteResult<()> {
        self.write_i32(if v { 1 } else { 0 })
    }

    #[inline]
    fn write_i16(&mut self, n: i16) -> WriteResult<()> {
        self.write_i32(n as i32)
    }

    #[inline]
    fn write_u16(&mut self, n: u16) -> WriteResult<()> {
        self.write_u32(n as u32)
    }

    write_int! [
        write_i64(8): i64,
        write_i32(4): i32,

        write_u64(8): u64,
        write_u32(4): u32,
    ];

    #[inline]
    fn write_f32(&mut self, n: f32) -> WriteResult<()> {
        let bytes: [u8; 4] = n.to_be_bytes();
        self.put_bytes(&bytes)
    }

    #[inline]
    fn write_f64(&mut self, n: f64) -> WriteResult<()> {
        let bytes: [u8; 8] = n.to_be_bytes();
        self.put_bytes(&bytes)
    }
}

/// The unchecked counterpart to [`Put`]: implement these basic byte
/// operations to get a `WriterUnchecked<TXS>` implementation for free.
pub(crate) trait PutUnchecked {
    /// # Safety
    /// The underlying buffer must have space for `bytes.len()` more bytes.
    unsafe fn put_bytes(&mut self, bytes: &[u8]);

    /// # Safety
    /// The underlying buffer must have space for the 4-byte length prefix
    /// plus the data padded to a 4-byte boundary, and
    /// `bytes.len() <= u32::MAX`.
    unsafe fn put_opaque_variable(&mut self, bytes: &[u8]);

    /// # Safety
    /// The underlying buffer must have space for the data padded to a
    /// 4-byte boundary, and `bytes.len() <= u32::MAX`.
    unsafe fn put_opaque_fixed(&mut self, bytes: &[u8]);
}

macro_rules! write_int_unchecked {
    [ $($write:ident($nbytes:expr): $ty:ty),* $(,)? ] => {

    $(
        #[inline]
        unsafe fn $write(&mut self, n: $ty) {
            let bytes: [u8; $nbytes] = n.to_be_bytes();
            // SAFETY: the caller guarantees space for the encoded form.
            unsafe { self.put_bytes(&bytes) }
        }
    )*

    };
}

impl<P> WriterUnchecked<TXS> for P
where
    P: PutUnchecked,
{
    #[inline]
    unsafe fn write_opaque_fixed(&mut self, bytes: &[u8]) {
        debug_assert!(bytes.len() <= u32::MAX as usize);
        // SAFETY: forwarded caller contract.
        unsafe { self.put_opaque_fixed(bytes) }
    }

    #[inline]
    unsafe fn write_opaque_unpadded(&mut self, bytes: &[u8]) {
        // SAFETY: forwarded caller contract.
        unsafe { self.put_bytes(bytes) }
    }

    #[inline]
    unsafe fn write_opaque_padding(&mut self, len: usize) {
        // SAFETY: forwarded caller contract.
        unsafe { self.put_bytes(&PADDING[..padding_len(len)]) }
    }

    #[inline]
    unsafe fn write_opaque_variable(&mut self, bytes: &[u8]) {
        debug_assert!(bytes.len() <= u32::MAX as usize);
        // SAFETY: forwarded caller contract.
        unsafe { self.put_opaque_variable(bytes) }
    }

    #[inline]
    unsafe fn write_bool(&mut self, v: bool) {
        // SAFETY: forwarded caller contract.
        unsafe { self.write_i32(if v { 1 } else { 0 }) }
    }

    #[inline]
    unsafe fn write_i16(&mut self, n: i16) {
        // SAFETY: forwarded caller contract.
        unsafe { self.write_i32(n as i32) }
    }

    #[inline]
    unsafe fn write_u16(&mut self, n: u16) {
        // SAFETY: forwarded caller contract.
        unsafe { self.write_u32(n as u32) }
    }

    write_int_unchecked![
        write_i64(8): i64,
        write_i32(4): i32,

        write_u64(8): u64,
        write_u32(4): u32,
    ];

    #[inline]
    unsafe fn write_f32(&mut self, n: f32) {
        let bytes: [u8; 4] = n.to_be_bytes();
        // SAFETY: forwarded caller contract.
        unsafe { self.put_bytes(&bytes) }
    }

    #[inline]
    unsafe fn write_f64(&mut self, n: f64) {
        let bytes: [u8; 8] = n.to_be_bytes();
        // SAFETY: forwarded caller contract.
        unsafe { self.put_bytes(&bytes) }
    }
}