use super::super::{
CapsuleDecodeError, ClosureAccounting, ClosureAccountingError, ClosureState,
RetainedRecordCharge,
};
use super::{Reader, Writer, read_admission_order, read_list, write_admission_order, write_list};
use crate::algebra::{ResourceVector, WideResourceVector};
use alloc::vec::Vec;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FrontierAccountingDecodeError {
Framing(CapsuleDecodeError),
Accounting(ClosureAccountingError),
}
pub(in crate::lifecycle) fn encode_accounting(value: &ClosureAccounting) -> Vec<u8> {
let mut w = Writer::default();
w.put_u64(value.marker_capacity_credits());
w.put_u64(value.marker_anchors());
w.put_u64(value.edge_sequence_claims());
w.put_u64(value.edge_order_position_claims());
w.put_u64(value.edge_k_remaining().entries);
w.put_u64(value.edge_k_remaining().bytes);
w.put_u128(value.baseline().entries);
w.put_u128(value.baseline().bytes);
w.put_u64(value.configured_cap().entries);
w.put_u64(value.configured_cap().bytes);
w.put_u64(value.episode_churn_used());
w.put_u64(value.episode_churn_limit());
w.bytes
}
pub(in crate::lifecycle) fn decode_accounting(
state: ClosureState,
body: &[u8],
) -> Result<ClosureAccounting, FrontierAccountingDecodeError> {
let read = || -> Result<_, CapsuleDecodeError> {
let mut r = Reader::new(body, 5);
let credits = r.take_u64()?;
let anchors = r.take_u64()?;
let sequence = r.take_u64()?;
let order = r.take_u64()?;
let remaining = ResourceVector {
entries: r.take_u64()?,
bytes: r.take_u64()?,
};
let baseline = WideResourceVector {
entries: r.take_u128()?,
bytes: r.take_u128()?,
};
let cap = ResourceVector {
entries: r.take_u64()?,
bytes: r.take_u64()?,
};
let churn = r.take_u64()?;
let limit = r.take_u64()?;
r.finish()?;
Ok((
credits, anchors, sequence, order, remaining, baseline, cap, churn, limit,
))
};
let (credits, anchors, sequence, order, remaining, baseline, cap, churn, limit) =
read().map_err(FrontierAccountingDecodeError::Framing)?;
ClosureAccounting::try_new(
state, credits, anchors, sequence, order, remaining, baseline, cap, churn, limit,
)
.map_err(FrontierAccountingDecodeError::Accounting)
}
pub(in crate::lifecycle) fn encode_charges(values: &[RetainedRecordCharge]) -> Option<Vec<u8>> {
let mut w = Writer::default();
write_list(&mut w, values, |w, value| {
w.put_u64(value.delivery_seq());
write_admission_order(w, value.admission_order());
w.put_u64(value.encoded_charge().entries);
w.put_u64(value.encoded_charge().bytes);
});
w.finish()
}
pub(in crate::lifecycle) fn decode_charges(
body: &[u8],
) -> Result<Vec<RetainedRecordCharge>, CapsuleDecodeError> {
let mut r = Reader::new(body, 6);
let charges = read_list(&mut r, |r| {
let sequence = r.take_u64()?;
let order = read_admission_order(r)?;
let charge = ResourceVector {
entries: r.take_u64()?,
bytes: r.take_u64()?,
};
if charge.entries != 1 {
return Err(r.invalid());
}
Ok(RetainedRecordCharge::new(sequence, order, charge))
})?;
r.finish()?;
for pair in charges.windows(2) {
if pair[0].delivery_seq().checked_add(1) != Some(pair[1].delivery_seq())
|| pair[0].admission_order() >= pair[1].admission_order()
{
return Err(r.invalid());
}
}
Ok(charges)
}
#[cfg(test)]
mod tests {
use super::super::super::AdmissionOrder;
use super::*;
use crate::outcome::CandidatePhase;
#[test]
fn checkpoint_accounting_canonical_bytes_and_refusals() -> Result<(), &'static str> {
let accounting = ClosureAccounting::try_new(
ClosureState::Clear,
8,
4,
0,
0,
ResourceVector::default(),
WideResourceVector {
entries: 3,
bytes: 97,
},
ResourceVector {
entries: 99,
bytes: 999,
},
7,
11,
);
let accounting = accounting.map_err(|_| "valid accounting refused")?;
let bytes = encode_accounting(&accounting);
assert_eq!(bytes.len(), 112);
let mut expected = Vec::new();
for value in [8_u64, 4, 0, 0, 0, 0] {
expected.extend_from_slice(&value.to_be_bytes());
}
expected.extend_from_slice(&3_u128.to_be_bytes());
expected.extend_from_slice(&97_u128.to_be_bytes());
for value in [99_u64, 999, 7, 11] {
expected.extend_from_slice(&value.to_be_bytes());
}
assert_eq!(bytes, expected);
assert_eq!(
decode_accounting(ClosureState::Clear, &bytes),
Ok(accounting)
);
let mut invalid = bytes;
invalid[15] = 9;
assert!(matches!(
decode_accounting(ClosureState::Clear, &invalid),
Err(FrontierAccountingDecodeError::Accounting(
ClosureAccountingError::MarkerAnchorsExceedCredits { .. }
))
));
invalid.push(0);
assert!(matches!(
decode_accounting(ClosureState::Clear, &invalid),
Err(FrontierAccountingDecodeError::Framing(_))
));
Ok(())
}
#[test]
fn checkpoint_retained_charges_canonical_bytes_and_refusals() -> Result<(), &'static str> {
let order = AdmissionOrder::new(17, CandidatePhase::OrdinaryRecord, 3);
let charge = RetainedRecordCharge::new(
5,
order,
ResourceVector {
entries: 1,
bytes: 107,
},
);
let bytes = encode_charges(&[charge]).ok_or("bounded charge refused")?;
let mut expected = Writer::default();
expected.put_u32(1);
expected.put_u64(5);
write_admission_order(&mut expected, order);
expected.put_u64(1);
expected.put_u64(107);
assert_eq!(bytes, expected.bytes);
assert_eq!(decode_charges(&bytes), Ok(alloc::vec![charge]));
let mut invalid = bytes;
let entries_end = invalid.len() - 8;
invalid[entries_end - 1] = 0;
assert!(decode_charges(&invalid).is_err());
invalid.push(0);
assert!(decode_charges(&invalid).is_err());
Ok(())
}
}