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 = 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)
}