liminal-server 0.24.1

Standalone server for the liminal messaging bus
Documentation
//! Fresh decoding of the permanent server indexes in the shared owner body.

use std::collections::BTreeMap;

use liminal_protocol::lifecycle::{RetiredIdentity, RetiredIdentityRestore};

use super::super::facts::Digest;
use super::super::restore_checkpoint::{CheckpointError, CheckpointPositions};
use super::super::restore_checkpoint_history_retired::RetiredHistoryError;
use super::CommittedAdmissionKey;
use super::checkpoint_body::{CheckpointBodyDecoder, invalid};

fn scalar(bytes: &[u8], field: &'static str) -> Result<u64, CheckpointError> {
    let mut decoder = CheckpointBodyDecoder::new(bytes);
    let value = decoder.u64(field)?;
    decoder.finish()?;
    Ok(value)
}

fn ordered<T>(previous: &mut Option<T>, key: T, field: &'static str) -> Result<T, CheckpointError>
where
    T: Copy + Ord,
{
    if previous.is_some_and(|last| last >= key) {
        return Err(invalid(field));
    }
    *previous = Some(key);
    Ok(key)
}

pub(super) fn decode_tokens(
    bytes: &[u8],
    positions: &CheckpointPositions,
) -> Result<BTreeMap<[u8; 16], u64>, CheckpointError> {
    let mut decoder = CheckpointBodyDecoder::new(bytes);
    let count = decoder.count("enrollment tokens", 28)?;
    let mut values = BTreeMap::new();
    let mut previous = None;
    for _ in 0..count {
        let token: [u8; 16] = decoder
            .take("enrollment token", 16)?
            .try_into()
            .map_err(|_| invalid("enrollment token"))?;
        let token = ordered(&mut previous, token, "enrollment token order")?;
        let participant = scalar(
            decoder.bytes("enrollment participant")?,
            "enrollment participant",
        )?;
        if participant >= positions.next_participant {
            return Err(invalid("enrollment participant allocator"));
        }
        values.insert(token, participant);
    }
    decoder.finish()?;
    Ok(values)
}

pub(super) fn decode_admissions(
    bytes: &[u8],
    positions: &CheckpointPositions,
) -> Result<BTreeMap<CommittedAdmissionKey, u64>, CheckpointError> {
    let mut decoder = CheckpointBodyDecoder::new(bytes);
    let count = decoder.count("committed admissions", 68)?;
    let mut values = BTreeMap::new();
    let mut previous = None;
    for _ in 0..count {
        let attempt: [u8; 16] = decoder
            .take("admission attempt", 16)?
            .try_into()
            .map_err(|_| invalid("admission attempt"))?;
        let participant = decoder.u64("admission participant")?;
        let fingerprint = Digest::new(
            decoder
                .take("admission fingerprint", 32)?
                .try_into()
                .map_err(|_| invalid("admission fingerprint"))?,
        );
        let key = ordered(
            &mut previous,
            (attempt, participant, fingerprint),
            "admission order",
        )?;
        let delivery = scalar(decoder.bytes("admission delivery")?, "admission delivery")?;
        if participant >= positions.next_participant
            || delivery == 0
            || delivery >= positions.next_seq
        {
            return Err(invalid("admission allocator"));
        }
        values.insert(key, delivery);
    }
    decoder.finish()?;
    Ok(values)
}

pub(super) fn decode_retired(
    bytes: &[u8],
    positions: &CheckpointPositions,
) -> Result<BTreeMap<u64, RetiredIdentity<Digest, Digest, Digest>>, CheckpointError> {
    let mut decoder = CheckpointBodyDecoder::new(bytes);
    let count = decoder.count("retired identities", 12)?;
    let mut values = BTreeMap::new();
    let mut previous = None;
    for _ in 0..count {
        let participant = ordered(
            &mut previous,
            decoder.u64("retired participant")?,
            "retired identity order",
        )?;
        if participant >= positions.next_participant {
            return Err(invalid("retired participant allocator"));
        }
        let body = decoder.bytes("retired identity")?;
        let raw = RetiredIdentityRestore::<Digest, Digest, Digest>::decode_canonical(
            body,
            positions.conversation_id,
            participant,
        )
        .map_err(|error| CheckpointError::LiveState {
            source: Box::new(RetiredHistoryError::Protocol(error)),
        })?;
        if raw.encode_canonical() != body {
            return Err(invalid("canonical retired identity"));
        }
        let value = raw.restore().map_err(|error| CheckpointError::LiveState {
            source: Box::new(RetiredHistoryError::Restore(error)),
        })?;
        values.insert(participant, value);
    }
    decoder.finish()?;
    Ok(values)
}