sim-lib-journal 0.3.0

Domain-free atomic content journal contract for SIM.
Documentation
use sim_kernel::{Datum, NumberLiteral, Symbol};

use crate::JournalError;

pub(crate) fn encode(value: &Datum) -> Result<Vec<u8>, JournalError> {
    value
        .canonical_bytes()
        .map_err(|_| JournalError::NonCanonicalDatum)?;
    let mut out = b"SIMJDATUM1".to_vec();
    put_datum(&mut out, value)?;
    Ok(out)
}

pub(crate) fn decode(bytes: &[u8]) -> Result<Datum, JournalError> {
    let mut cursor = Cursor::new(bytes);
    if cursor.take(10)? != b"SIMJDATUM1" {
        return Err(JournalError::CorruptState("datum format"));
    }
    let value = cursor.datum()?;
    cursor.end()?;
    value
        .canonical_bytes()
        .map_err(|_| JournalError::NonCanonicalDatum)?;
    Ok(value)
}

fn put_datum(out: &mut Vec<u8>, value: &Datum) -> Result<(), JournalError> {
    match value {
        Datum::Nil => out.push(0),
        Datum::Bool(false) => out.push(1),
        Datum::Bool(true) => out.push(2),
        Datum::Number(number) => {
            out.push(3);
            put_symbol(out, &number.domain);
            put_bytes(out, number.canonical.as_bytes())?;
        }
        Datum::Symbol(symbol) => {
            out.push(4);
            put_symbol(out, symbol);
        }
        Datum::String(text) => {
            out.push(5);
            put_bytes(out, text.as_bytes())?;
        }
        Datum::Bytes(bytes) => {
            out.push(6);
            put_bytes(out, bytes)?;
        }
        Datum::List(items) => {
            out.push(7);
            put_items(out, items)?;
        }
        Datum::Vector(items) => {
            out.push(8);
            put_items(out, items)?;
        }
        Datum::Map(entries) => {
            out.push(9);
            let mut ordered = entries.to_vec();
            ordered.sort_by_key(|(key, _)| key.canonical_bytes().unwrap_or_default());
            put_len(out, ordered.len())?;
            for (key, value) in &ordered {
                put_datum(out, key)?;
                put_datum(out, value)?;
            }
        }
        Datum::Set(items) => {
            out.push(10);
            let mut ordered = items.to_vec();
            ordered.sort_by_key(|item| item.canonical_bytes().unwrap_or_default());
            put_items(out, &ordered)?;
        }
        Datum::Node { tag, fields } => {
            out.push(11);
            put_symbol(out, tag);
            let mut ordered = fields.to_vec();
            ordered.sort_by_key(|(name, _)| symbol_key(name));
            put_len(out, ordered.len())?;
            for (name, value) in &ordered {
                put_symbol(out, name);
                put_datum(out, value)?;
            }
        }
    }
    Ok(())
}

fn put_items(out: &mut Vec<u8>, items: &[Datum]) -> Result<(), JournalError> {
    put_len(out, items.len())?;
    for item in items {
        put_datum(out, item)?;
    }
    Ok(())
}

fn put_symbol(out: &mut Vec<u8>, symbol: &Symbol) {
    match &symbol.namespace {
        Some(namespace) => {
            out.push(1);
            put_bytes(out, namespace.as_bytes()).expect("symbol length fits u64");
        }
        None => out.push(0),
    }
    put_bytes(out, symbol.name.as_bytes()).expect("symbol length fits u64");
}

fn put_bytes(out: &mut Vec<u8>, bytes: &[u8]) -> Result<(), JournalError> {
    put_len(out, bytes.len())?;
    out.extend(bytes);
    Ok(())
}

fn put_len(out: &mut Vec<u8>, len: usize) -> Result<(), JournalError> {
    let len = u64::try_from(len).map_err(|_| JournalError::WorkBoundExceeded)?;
    out.extend(len.to_be_bytes());
    Ok(())
}

fn symbol_key(symbol: &Symbol) -> (Option<String>, String) {
    (
        symbol.namespace.as_ref().map(ToString::to_string),
        symbol.name.to_string(),
    )
}

struct Cursor<'a> {
    bytes: &'a [u8],
    at: usize,
}

impl<'a> Cursor<'a> {
    fn new(bytes: &'a [u8]) -> Self {
        Self { bytes, at: 0 }
    }
    fn take(&mut self, len: usize) -> Result<&'a [u8], JournalError> {
        let end = self
            .at
            .checked_add(len)
            .ok_or(JournalError::CorruptState("datum length"))?;
        let value = self
            .bytes
            .get(self.at..end)
            .ok_or(JournalError::CorruptState("truncated datum"))?;
        self.at = end;
        Ok(value)
    }
    fn byte(&mut self) -> Result<u8, JournalError> {
        Ok(self.take(1)?[0])
    }
    fn len(&mut self) -> Result<usize, JournalError> {
        usize::try_from(u64::from_be_bytes(
            self.take(8)?
                .try_into()
                .map_err(|_| JournalError::CorruptState("datum length"))?,
        ))
        .map_err(|_| JournalError::WorkBoundExceeded)
    }
    fn bytes(&mut self) -> Result<Vec<u8>, JournalError> {
        let len = self.len()?;
        Ok(self.take(len)?.to_vec())
    }
    fn text(&mut self) -> Result<String, JournalError> {
        String::from_utf8(self.bytes()?).map_err(|_| JournalError::CorruptState("datum utf8"))
    }
    fn symbol(&mut self) -> Result<Symbol, JournalError> {
        let namespace = match self.byte()? {
            0 => None,
            1 => Some(self.text()?),
            _ => return Err(JournalError::CorruptState("datum symbol")),
        };
        let name = self.text()?;
        match namespace {
            Some(namespace) => Ok(Symbol::qualified(namespace, name)),
            None => Ok(Symbol::new(name)),
        }
    }
    fn items(&mut self) -> Result<Vec<Datum>, JournalError> {
        let count = self.len()?;
        (0..count).map(|_| self.datum()).collect()
    }
    fn datum(&mut self) -> Result<Datum, JournalError> {
        Ok(match self.byte()? {
            0 => Datum::Nil,
            1 => Datum::Bool(false),
            2 => Datum::Bool(true),
            3 => Datum::Number(NumberLiteral {
                domain: self.symbol()?,
                canonical: self.text()?,
            }),
            4 => Datum::Symbol(self.symbol()?),
            5 => Datum::String(self.text()?),
            6 => Datum::Bytes(self.bytes()?),
            7 => Datum::List(self.items()?),
            8 => Datum::Vector(self.items()?),
            9 => {
                let count = self.len()?;
                let mut entries = Vec::with_capacity(count);
                for _ in 0..count {
                    entries.push((self.datum()?, self.datum()?));
                }
                Datum::Map(entries)
            }
            10 => Datum::Set(self.items()?),
            11 => {
                let tag = self.symbol()?;
                let count = self.len()?;
                let mut fields = Vec::with_capacity(count);
                for _ in 0..count {
                    fields.push((self.symbol()?, self.datum()?));
                }
                Datum::Node { tag, fields }
            }
            _ => return Err(JournalError::CorruptState("datum tag")),
        })
    }
    fn end(&self) -> Result<(), JournalError> {
        if self.at == self.bytes.len() {
            Ok(())
        } else {
            Err(JournalError::CorruptState("trailing datum bytes"))
        }
    }
}