extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use crate::kairos::{DecodeError as KairosDecodeError, Kairos};
use crate::metis::HaveSetDecodeBudget;
use super::super::placement::Dot;
use super::super::{Anchor, DotSet, Locus, Rhapsody};
mod encode;
mod error;
#[cfg(kani)]
mod proofs;
mod pure;
pub use error::SnapshotDecodeError;
#[cfg(any(test, kani))]
pub use pure::rank_successor;
use crate::metis::dot::RawDot;
use pure::{ANCHOR_TAG_AFTER, ANCHOR_TAG_BEFORE, ANCHOR_TAG_ORIGIN, PureLocus, PureSnapshotError};
pub(in crate::metis::rhapsody) const fn chain_step(
prev_dot: (u32, u64),
prev: &Locus,
next_dot: (u32, u64),
next: &Locus,
) -> Option<u64> {
let flat_prev = flatten(prev_dot, prev);
let flat_next = flatten(next_dot, next);
let consecutive = flat_prev.station == flat_next.station
&& flat_prev.index < u64::MAX
&& flat_next.index == flat_prev.index + 1;
if !(consecutive && pure::chainable(&flat_prev, &flat_next)) {
return None;
}
let (successor_physical, successor_logical) =
pure::rank_successor(flat_prev.physical, flat_prev.logical);
if flat_next.physical == successor_physical && flat_next.logical == successor_logical {
Some(0)
} else {
Some(flat_next.physical - flat_prev.physical)
}
}
pub(in crate::metis::rhapsody) fn apply_chain_step(
prev_dot: (u32, u64),
prev: &Locus,
step: u64,
) -> Locus {
let (physical, logical) = if step == 0 {
pure::rank_successor(prev.rank.physical(), prev.rank.logical())
} else {
(prev.rank.physical() + step, 0)
};
Locus {
anchor: Anchor::After(prev_dot.into()),
rank: Kairos::new(
physical,
logical,
prev.rank.station_id(),
prev.rank.kairotic(),
),
}
}
const fn lift(error: PureSnapshotError) -> SnapshotDecodeError {
match error {
PureSnapshotError::UnknownVersion(version) => SnapshotDecodeError::UnknownVersion(version),
PureSnapshotError::UnexpectedLength { expected, found } => {
SnapshotDecodeError::UnexpectedLength { expected, found }
}
PureSnapshotError::ZeroDot { station } => SnapshotDecodeError::ZeroDot { station },
PureSnapshotError::RunTooShort { station, base } => {
SnapshotDecodeError::RunTooShort { station, base }
}
PureSnapshotError::RunOverflow { station, base, len } => {
SnapshotDecodeError::RunOverflow { station, base, len }
}
PureSnapshotError::BadAnchorTag { tag } => SnapshotDecodeError::BadAnchorTag { tag },
PureSnapshotError::NonCanonicalOriginAnchor { station, index } => {
SnapshotDecodeError::NonCanonicalOriginAnchor { station, index }
}
PureSnapshotError::BadRankVersion(version) => {
SnapshotDecodeError::Rank(KairosDecodeError::UnknownVersion(version))
}
PureSnapshotError::RankOverflow { station, index } => {
SnapshotDecodeError::RankOverflow { station, index }
}
PureSnapshotError::NonCanonicalRankStep { station, index } => {
SnapshotDecodeError::NonCanonicalRankStep { station, index }
}
PureSnapshotError::NonAscendingLoci {
previous_station,
previous_index,
found_station,
found_index,
} => SnapshotDecodeError::NonAscendingLoci {
previous_station,
previous_index,
found_station,
found_index,
},
PureSnapshotError::SplitChain { station, index } => {
SnapshotDecodeError::SplitChain { station, index }
}
}
}
const fn flatten((station, index): (u32, u64), locus: &Locus) -> PureLocus {
let (anchor_tag, anchor_station, anchor_index) = match locus.anchor {
Anchor::Origin => (ANCHOR_TAG_ORIGIN, 0, 0),
Anchor::After(RawDot {
station: anchor_station,
counter: anchor_index,
}) => (ANCHOR_TAG_AFTER, anchor_station, anchor_index),
Anchor::Before(RawDot {
station: anchor_station,
counter: anchor_index,
}) => (ANCHOR_TAG_BEFORE, anchor_station, anchor_index),
};
PureLocus {
station,
index,
anchor_tag,
anchor_station,
anchor_index,
physical: locus.rank.physical(),
logical: locus.rank.logical(),
kairotic: locus.rank.kairotic(),
rank_station: locus.rank.station_id(),
}
}
fn thicken(flat: &PureLocus) -> Option<(Dot, Locus)> {
let anchor = if flat.anchor_tag == ANCHOR_TAG_AFTER {
Anchor::After(RawDot {
station: flat.anchor_station,
counter: flat.anchor_index,
})
} else if flat.anchor_tag == ANCHOR_TAG_BEFORE {
Anchor::Before(RawDot {
station: flat.anchor_station,
counter: flat.anchor_index,
})
} else {
Anchor::Origin
};
Some((
Dot::from_parts(flat.station, flat.index).ok()?,
Locus {
anchor,
rank: Kairos::new(
flat.physical,
flat.logical,
flat.rank_station,
flat.kairotic,
),
},
))
}
impl Rhapsody {
#[must_use]
pub fn snapshot_encoded_len(&self) -> usize {
const HEADER_LEN: usize = 9;
const RUN_ROW_LEN: usize = 46;
const RANK_STEP_LEN: usize = 6;
const DOT_LEN: usize = 12;
const ANCHOR_TAG_LEN: usize = 1;
const RANK_LEN: usize = 17;
let mut length = HEADER_LEN;
let mut loci = self.skeleton.iter().peekable();
while let Some((head_dot, head)) = loci.next() {
let mut previous_dot: (u32, u64) = head_dot.into();
let mut previous = head;
let mut run_len = 1usize;
while let Some(&(next_dot, next)) = loci.peek() {
if chain_step(previous_dot, &previous, next_dot.into(), &next).is_none() {
break;
}
let _ = loci.next();
previous_dot = next_dot.into();
previous = next;
run_len = run_len.saturating_add(1);
}
if run_len >= 2 {
length = length
.saturating_add(RUN_ROW_LEN)
.saturating_add((run_len - 1).saturating_mul(RANK_STEP_LEN));
} else {
length = length
.saturating_add(DOT_LEN + ANCHOR_TAG_LEN + RANK_LEN)
.saturating_add(usize::from(head.anchor.dot().is_some()) * DOT_LEN);
}
}
length.saturating_add(self.visible.encoded_len())
}
#[must_use]
pub fn to_snapshot_bytes(&self) -> Vec<u8> {
let mut flat: Vec<PureLocus> = Vec::with_capacity(self.skeleton.len());
for (dot, locus) in self.skeleton.iter() {
flat.push(flatten(dot.into(), &locus));
}
let mut out = encode::encode_loci(&flat);
out.extend_from_slice(&self.visible.to_bytes());
out
}
pub fn from_snapshot_bytes(bytes: &[u8]) -> Result<Self, SnapshotDecodeError> {
let (rhapsody, tail) = Self::from_snapshot_prefix(bytes)?;
if tail.is_empty() {
Ok(rhapsody)
} else {
Err(SnapshotDecodeError::UnexpectedLength {
expected: bytes.len() - tail.len(),
found: bytes.len(),
})
}
}
pub fn from_snapshot_prefix(bytes: &[u8]) -> Result<(Self, &[u8]), SnapshotDecodeError> {
let decoded = pure::decode_prefix(bytes).map_err(lift)?;
let total = decoded.runs.len().saturating_add(decoded.free.len());
if total > super::super::identity::PLANE_CAPACITY {
return Err(SnapshotDecodeError::TooManyLoci {
count: total as u64,
capacity: super::super::identity::PLANE_CAPACITY as u64,
});
}
let mut skeleton = BTreeMap::new();
for flat in decoded.runs.iter().chain(decoded.free.iter()) {
let Some((dot, locus)) = thicken(flat) else {
return Err(SnapshotDecodeError::ZeroDot {
station: flat.station,
});
};
let _ = skeleton.insert(dot, locus);
}
let rest = bytes.get(decoded.consumed..).unwrap_or(&[]);
let budget = HaveSetDecodeBudget::new(skeleton.len());
let (visible, tail) =
DotSet::from_prefix_with_budget(rest, budget).map_err(SnapshotDecodeError::Visible)?;
for dot in visible.dots() {
if !skeleton.contains_key(&dot) {
return Err(SnapshotDecodeError::VisibleWithoutLocus {
station: dot.station(),
index: dot.counter(),
});
}
}
Ok((Self::from_parts(skeleton, visible), tail))
}
}
#[cfg(test)]
mod layout_pins {
use super::{Kairos, pure};
use crate::kairos::KAIROS_WIRE_LEN;
#[test]
fn the_rank_frame_mirrors_the_stamp_codec() {
assert_eq!(pure::SNAPSHOT_RANK_FRAME_LEN, KAIROS_WIRE_LEN);
let stamp = Kairos::new(0x0102_0304_0506_0708, 0x090A, 0x0D0E_0F10, 0x0B0Cu16);
let frame = stamp.to_bytes();
assert_eq!(frame[0], pure::RANK_WIRE_V1);
assert_eq!(&frame[1..9], &0x0102_0304_0506_0708u64.to_be_bytes());
assert_eq!(&frame[9..11], &0x090Au16.to_be_bytes());
assert_eq!(&frame[11..13], &0x0B0Cu16.to_be_bytes());
assert_eq!(&frame[13..17], &0x0D0E_0F10u32.to_be_bytes());
}
}