extern crate alloc;
use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use thiserror::Error;
use super::super::arrival::{Admission, Endorsement};
use super::super::gate::{EpochAddress, InvalidEpochAddress};
use super::shared::{WIRE_ADDRESS_LEN, encode_address_into, read_address_parts, read_u32};
const ENDORSEMENT_WIRE_V1: u8 = 0x01;
const ENDORSEMENT_WIRE_PREFIX_LEN: usize = 1 + WIRE_ADDRESS_LEN + 4;
const ENDORSEMENT_WIRE_FIXED_LEN: usize = ENDORSEMENT_WIRE_PREFIX_LEN + 32;
const ENDORSEMENT_DEFAULT_MAX_JOINERS: usize = 1024;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct EndorsementDecodeBudget {
max_joiners: usize,
}
impl EndorsementDecodeBudget {
#[must_use]
pub const fn new(max_joiners: usize) -> Self {
Self { max_joiners }
}
#[must_use]
pub const fn default_ceiling() -> Self {
Self {
max_joiners: ENDORSEMENT_DEFAULT_MAX_JOINERS,
}
}
#[must_use]
pub const fn max_joiners(self) -> usize {
self.max_joiners
}
}
impl Default for EndorsementDecodeBudget {
fn default() -> Self {
Self::default_ceiling()
}
}
#[non_exhaustive]
#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum EndorsementDecodeError {
#[error("unknown endorsement wire version: {0:#04x}")]
UnknownVersion(u8),
#[error("unexpected endorsement frame length: expected {expected}, found {found}")]
UnexpectedLength {
expected: usize,
found: usize,
},
#[error("endorsement base: {0}")]
Address(#[from] InvalidEpochAddress),
#[error("endorsement admission carries no joiners")]
EmptyAdmission,
#[error("endorsement joiners not ascending: {found} after {previous}")]
NonAscendingJoiners {
previous: u32,
found: u32,
},
#[error("endorsement frame declares {count} joiners, past the budget's {budget}")]
TooManyJoiners {
count: u64,
budget: u64,
},
}
impl Endorsement {
#[must_use]
pub fn to_bytes(&self) -> Vec<u8> {
let mut out = Vec::with_capacity(self.encoded_len());
out.push(ENDORSEMENT_WIRE_V1);
encode_address_into(&mut out, self.admission().base());
let joiner_count = u32::try_from(self.admission().joiners().count()).unwrap_or(u32::MAX);
out.extend_from_slice(&joiner_count.to_be_bytes());
for joiner in self.admission().joiners() {
out.extend_from_slice(&joiner.to_be_bytes());
}
out.extend_from_slice(self.commitment());
out
}
#[must_use]
pub fn encoded_len(&self) -> usize {
ENDORSEMENT_WIRE_PREFIX_LEN + self.admission().joiners().count() * 4 + 32
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, EndorsementDecodeError> {
Self::decode(bytes, None)
}
pub fn from_bytes_with_budget(
bytes: &[u8],
budget: EndorsementDecodeBudget,
) -> Result<Self, EndorsementDecodeError> {
Self::decode(bytes, Some(budget))
}
fn decode(
bytes: &[u8],
budget: Option<EndorsementDecodeBudget>,
) -> Result<Self, EndorsementDecodeError> {
if bytes.len() < ENDORSEMENT_WIRE_FIXED_LEN {
return Err(EndorsementDecodeError::UnexpectedLength {
expected: ENDORSEMENT_WIRE_FIXED_LEN,
found: bytes.len(),
});
}
let version = bytes[0];
if version != ENDORSEMENT_WIRE_V1 {
return Err(EndorsementDecodeError::UnknownVersion(version));
}
let short = EndorsementDecodeError::UnexpectedLength {
expected: ENDORSEMENT_WIRE_FIXED_LEN,
found: bytes.len(),
};
let ((generation, dot), rest) = read_address_parts(&bytes[1..]).ok_or(short)?;
let base = EpochAddress::try_from_parts(generation, dot)?;
let (count, mut rest) = read_u32(rest).ok_or(short)?;
if count == 0 {
return Err(EndorsementDecodeError::EmptyAdmission);
}
let backable = rest.len().saturating_sub(32) / 4;
if count as usize > backable {
let floor = u64::from(count).saturating_mul(4).saturating_add(32);
return Err(EndorsementDecodeError::UnexpectedLength {
expected: usize::try_from(
(ENDORSEMENT_WIRE_PREFIX_LEN as u64).saturating_add(floor),
)
.unwrap_or(usize::MAX),
found: bytes.len(),
});
}
if let Some(budget) = budget
&& count as usize > budget.max_joiners()
{
return Err(EndorsementDecodeError::TooManyJoiners {
count: u64::from(count),
budget: budget.max_joiners() as u64,
});
}
let mut joiners = BTreeSet::new();
let mut previous: Option<u32> = None;
for _ in 0..count {
let (joiner, tail) = read_u32(rest).ok_or(short)?;
if let Some(previous) = previous
&& joiner <= previous
{
return Err(EndorsementDecodeError::NonAscendingJoiners {
previous,
found: joiner,
});
}
previous = Some(joiner);
let _ = joiners.insert(joiner);
rest = tail;
}
let commitment: [u8; 32] = rest
.get(..32)
.and_then(|bytes| bytes.try_into().ok())
.ok_or(short)?;
let tail = &rest[32..];
if !tail.is_empty() {
return Err(EndorsementDecodeError::UnexpectedLength {
expected: bytes.len() - tail.len(),
found: bytes.len(),
});
}
let admission = Admission::new(base, joiners)
.map_err(|_refusal| EndorsementDecodeError::EmptyAdmission)?;
Ok(Self::from_parts(admission, commitment))
}
}