extern crate alloc;
use alloc::vec::Vec;
use core::num::NonZeroU64;
use crate::kairos::{KAIROS_WIRE_LEN, Kairos};
use super::super::Dot;
use super::super::rhapsody::{Anchor, Locus};
use super::{Metatheses, Metathesis};
use crate::metis::dot::RawDot;
mod error;
pub use error::{MetathesesDecodeBudget, MetathesesDecodeError};
const METATHESES_WIRE_V1: u8 = 0x01;
const METATHESES_WIRE_HEADER_LEN: usize = 9;
const METATHESES_WIRE_DOT_LEN: usize = 12;
const METATHESES_WIRE_ROW_PREFIX_LEN: usize = 2 * METATHESES_WIRE_DOT_LEN + 1;
const METATHESES_WIRE_ROW_MIN_LEN: usize = METATHESES_WIRE_ROW_PREFIX_LEN + KAIROS_WIRE_LEN;
const ANCHOR_TAG_ORIGIN: u8 = 0x00;
const ANCHOR_TAG_AFTER: u8 = 0x01;
const ANCHOR_TAG_BEFORE: u8 = 0x02;
impl Metatheses {
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
let count = self.len() as u64;
let mut out = Vec::with_capacity(
METATHESES_WIRE_HEADER_LEN + self.len() * METATHESES_WIRE_ROW_MIN_LEN,
);
out.push(METATHESES_WIRE_V1);
out.extend_from_slice(&count.to_be_bytes());
for (dot, metathesis) in self {
out.extend_from_slice(&dot.station().to_be_bytes());
out.extend_from_slice(&dot.counter().to_be_bytes());
let RawDot {
station: target_station,
counter: target_index,
} = metathesis.target;
out.extend_from_slice(&target_station.to_be_bytes());
out.extend_from_slice(&target_index.to_be_bytes());
match metathesis.to.anchor {
Anchor::Origin => out.push(ANCHOR_TAG_ORIGIN),
Anchor::After(RawDot {
station: astation,
counter: aindex,
}) => {
out.push(ANCHOR_TAG_AFTER);
out.extend_from_slice(&astation.to_be_bytes());
out.extend_from_slice(&aindex.to_be_bytes());
}
Anchor::Before(RawDot {
station: astation,
counter: aindex,
}) => {
out.push(ANCHOR_TAG_BEFORE);
out.extend_from_slice(&astation.to_be_bytes());
out.extend_from_slice(&aindex.to_be_bytes());
}
}
out.extend_from_slice(&metathesis.to.rank.to_bytes());
}
out
}
#[must_use]
pub fn encoded_len(&self) -> usize {
let mut len = METATHESES_WIRE_HEADER_LEN;
for (_, metathesis) in self {
len += METATHESES_WIRE_ROW_PREFIX_LEN + KAIROS_WIRE_LEN;
if !matches!(metathesis.to.anchor, Anchor::Origin) {
len += METATHESES_WIRE_DOT_LEN;
}
}
len
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, MetathesesDecodeError> {
Self::decode(bytes, None)
}
pub fn from_bytes_with_budget(
bytes: &[u8],
budget: MetathesesDecodeBudget,
) -> Result<Self, MetathesesDecodeError> {
Self::decode(bytes, Some(budget))
}
fn decode(
bytes: &[u8],
budget: Option<MetathesesDecodeBudget>,
) -> Result<Self, MetathesesDecodeError> {
let header: [u8; METATHESES_WIRE_HEADER_LEN] = bytes
.get(..METATHESES_WIRE_HEADER_LEN)
.and_then(|h| h.try_into().ok())
.ok_or(MetathesesDecodeError::UnexpectedLength {
expected: METATHESES_WIRE_HEADER_LEN,
found: bytes.len(),
})?;
let [version, c0, c1, c2, c3, c4, c5, c6, c7] = header;
if version != METATHESES_WIRE_V1 {
return Err(MetathesesDecodeError::UnknownVersion(version));
}
let count = u64::from_be_bytes([c0, c1, c2, c3, c4, c5, c6, c7]);
if let Some(budget) = budget
&& count > budget.max_testimonies() as u64
{
return Err(MetathesesDecodeError::TooManyTestimonies {
count,
budget: budget.max_testimonies() as u64,
});
}
let body = bytes.len() - METATHESES_WIRE_HEADER_LEN;
if count > (body / METATHESES_WIRE_ROW_MIN_LEN) as u64 {
let expected = count
.saturating_mul(METATHESES_WIRE_ROW_MIN_LEN as u64)
.saturating_add(METATHESES_WIRE_HEADER_LEN as u64);
return Err(MetathesesDecodeError::UnexpectedLength {
expected: usize::try_from(expected).unwrap_or(usize::MAX),
found: bytes.len(),
});
}
let mut rest = &bytes[METATHESES_WIRE_HEADER_LEN..];
let mut decoded = Self::new();
let mut previous_dot: Option<Dot> = None;
for _ in 0..count {
let (dot, metathesis, tail) = decode_row(bytes, rest)?;
rest = tail;
if let Some(previous) = previous_dot
&& dot <= previous
{
return Err(MetathesesDecodeError::NonAscendingTestimonies {
previous: previous.into(),
found: dot.into(),
});
}
previous_dot = Some(dot);
let _ = decoded.insert(dot, metathesis);
}
if rest.is_empty() {
Ok(decoded)
} else {
Err(MetathesesDecodeError::UnexpectedLength {
expected: bytes.len() - rest.len(),
found: bytes.len(),
})
}
}
}
type DecodedRow<'a> = (Dot, Metathesis, &'a [u8]);
fn decode_row<'a>(bytes: &[u8], rest: &'a [u8]) -> Result<DecodedRow<'a>, MetathesesDecodeError> {
let prefix: [u8; METATHESES_WIRE_ROW_PREFIX_LEN] = rest
.get(..METATHESES_WIRE_ROW_PREFIX_LEN)
.and_then(|p| p.try_into().ok())
.ok_or(MetathesesDecodeError::UnexpectedLength {
expected: bytes.len() - rest.len() + METATHESES_WIRE_ROW_MIN_LEN,
found: bytes.len(),
})?;
let [
s0,
s1,
s2,
s3,
i0,
i1,
i2,
i3,
i4,
i5,
i6,
i7,
t0,
t1,
t2,
t3,
t4,
t5,
t6,
t7,
t8,
t9,
t10,
t11,
tag,
] = prefix;
let station = u32::from_be_bytes([s0, s1, s2, s3]);
let index = u64::from_be_bytes([i0, i1, i2, i3, i4, i5, i6, i7]);
let Some(index) = NonZeroU64::new(index) else {
return Err(MetathesesDecodeError::ZeroDot { station });
};
let dot = Dot::new(station, index);
let target = (
u32::from_be_bytes([t0, t1, t2, t3]),
u64::from_be_bytes([t4, t5, t6, t7, t8, t9, t10, t11]),
);
let mut rest = &rest[METATHESES_WIRE_ROW_PREFIX_LEN..];
let anchor = match tag {
ANCHOR_TAG_ORIGIN => Anchor::Origin,
ANCHOR_TAG_AFTER | ANCHOR_TAG_BEFORE => {
let anchor: [u8; METATHESES_WIRE_DOT_LEN] = rest
.get(..METATHESES_WIRE_DOT_LEN)
.and_then(|a| a.try_into().ok())
.ok_or(MetathesesDecodeError::UnexpectedLength {
expected: bytes.len() - rest.len() + METATHESES_WIRE_DOT_LEN + KAIROS_WIRE_LEN,
found: bytes.len(),
})?;
rest = &rest[METATHESES_WIRE_DOT_LEN..];
let [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11] = anchor;
let dot = (
u32::from_be_bytes([a0, a1, a2, a3]),
u64::from_be_bytes([a4, a5, a6, a7, a8, a9, a10, a11]),
);
let dot = RawDot::from(dot);
if tag == ANCHOR_TAG_AFTER {
Anchor::After(dot)
} else {
Anchor::Before(dot)
}
}
other => return Err(MetathesesDecodeError::BadAnchorTag { tag: other }),
};
let rank_bytes: [u8; KAIROS_WIRE_LEN] = rest
.get(..KAIROS_WIRE_LEN)
.and_then(|r| r.try_into().ok())
.ok_or(MetathesesDecodeError::UnexpectedLength {
expected: bytes.len() - rest.len() + KAIROS_WIRE_LEN,
found: bytes.len(),
})?;
let rank = Kairos::from_bytes(&rank_bytes).map_err(MetathesesDecodeError::Rank)?;
rest = &rest[KAIROS_WIRE_LEN..];
Ok((
dot,
Metathesis {
target: RawDot::from(target),
to: Locus { anchor, rank },
},
rest,
))
}