kcode-server-object-envelopes 0.1.0

Typed application payload envelopes stored by KennedyServer in Kweb objects
Documentation
use crate::{Error, Result};

pub(crate) struct Reader<'a> {
    bytes: &'a [u8],
    offset: usize,
}

impl<'a> Reader<'a> {
    pub(crate) fn new(bytes: &'a [u8]) -> Self {
        Self { bytes, offset: 0 }
    }

    pub(crate) fn take(&mut self, length: usize, label: &str) -> Result<&'a [u8]> {
        let end = self
            .offset
            .checked_add(length)
            .ok_or_else(|| Error::new(format!("{label} length overflow")))?;
        if end > self.bytes.len() {
            return Err(Error::new(format!("{label} is truncated")));
        }
        let value = &self.bytes[self.offset..end];
        self.offset = end;
        Ok(value)
    }

    pub(crate) fn array<const N: usize>(&mut self, label: &str) -> Result<[u8; N]> {
        self.take(N, label)?
            .try_into()
            .map_err(|_| Error::new(format!("{label} has an invalid length")))
    }

    pub(crate) fn u32(&mut self, label: &str) -> Result<u32> {
        Ok(u32::from_be_bytes(self.array(label)?))
    }

    pub(crate) fn u64(&mut self, label: &str) -> Result<u64> {
        Ok(u64::from_be_bytes(self.array(label)?))
    }

    pub(crate) fn string(&mut self, label: &str) -> Result<String> {
        let length = usize::try_from(self.u32(label)?)
            .map_err(|_| Error::new(format!("{label} length exceeds usize")))?;
        let bytes = self.take(length, label)?;
        let value =
            std::str::from_utf8(bytes).map_err(|_| Error::new(format!("{label} is not UTF-8")))?;
        copy_string(value, label)
    }

    pub(crate) fn position(&self) -> usize {
        self.offset
    }

    pub(crate) fn finish(self, label: &str) -> Result<()> {
        if self.offset != self.bytes.len() {
            return Err(Error::new(format!("{label} has trailing bytes")));
        }
        Ok(())
    }
}

pub(crate) fn copy_string(value: &str, label: &str) -> Result<String> {
    let mut output = String::new();
    output
        .try_reserve_exact(value.len())
        .map_err(|_| Error::new(format!("unable to allocate {label}")))?;
    output.push_str(value);
    Ok(output)
}

pub(crate) fn push_string(output: &mut Vec<u8>, value: &str, label: &str) -> Result<()> {
    let length =
        u32::try_from(value.len()).map_err(|_| Error::new(format!("{label} exceeds u32")))?;
    let additional = 4usize
        .checked_add(value.len())
        .ok_or_else(|| Error::new(format!("{label} encoded length overflow")))?;
    output
        .len()
        .checked_add(additional)
        .ok_or_else(|| Error::new(format!("{label} encoded length overflow")))?;
    output
        .try_reserve(additional)
        .map_err(|_| Error::new(format!("unable to allocate {label}")))?;
    output.extend_from_slice(&length.to_be_bytes());
    output.extend_from_slice(value.as_bytes());
    Ok(())
}