use core::fmt;
use bytes::Bytes;
use crate::stream::error::ParseError;
use super::types::CesrGroup;
type GroupParser = fn(&Bytes) -> Result<(CesrGroup, Bytes), ParseError>;
pub struct QuadletGroup {
input: Bytes,
cursor: usize,
errored: bool,
parser: GroupParser,
}
impl QuadletGroup {
pub(crate) fn new(input: Bytes, parser: GroupParser) -> Self {
Self {
input,
cursor: 0,
errored: false,
parser,
}
}
pub const fn quadlet_count(&self) -> usize {
self.input.len() / 4
}
pub fn raw_bytes(&self) -> &[u8] {
&self.input
}
#[must_use]
pub fn to_bytes(&self) -> Bytes {
self.input.clone()
}
}
impl fmt::Debug for QuadletGroup {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("QuadletGroup")
.field("quadlets", &self.quadlet_count())
.field("cursor", &self.cursor)
.field("errored", &self.errored)
.finish_non_exhaustive()
}
}
impl Iterator for QuadletGroup {
type Item = Result<CesrGroup, ParseError>;
fn next(&mut self) -> Option<Self::Item> {
if self.cursor >= self.input.len() || self.errored {
return None;
}
let remaining = self.input.slice(self.cursor..);
match (self.parser)(&remaining) {
Ok((group, rest)) => {
self.cursor = self.input.len() - rest.len();
Some(Ok(group))
}
Err(e) => {
self.errored = true;
Some(Err(e))
}
}
}
}
pub(super) fn parse_quadlets(
input: &Bytes,
count: u32,
) -> Result<(QuadletGroup, Bytes), ParseError> {
let total_bytes = usize::try_from(count)
.ok()
.and_then(|c| c.checked_mul(4))
.ok_or_else(|| ParseError::Malformed("quadlet count overflow".into()))?;
if input.len() < total_bytes {
return Err(ParseError::NeedBytes(total_bytes - input.len()));
}
let group_bytes = input.slice(..total_bytes);
let rest = input.slice(total_bytes..);
Ok((
QuadletGroup::new(group_bytes, super::parse_group_bytes),
rest,
))
}
pub(super) fn parse_quadlets_v2(
input: &Bytes,
count: u32,
) -> Result<(QuadletGroup, Bytes), ParseError> {
let total_bytes = usize::try_from(count)
.ok()
.and_then(|c| c.checked_mul(4))
.ok_or_else(|| ParseError::Malformed("quadlet count overflow".into()))?;
if input.len() < total_bytes {
return Err(ParseError::NeedBytes(total_bytes - input.len()));
}
let group_bytes = input.slice(..total_bytes);
let rest = input.slice(total_bytes..);
Ok((
QuadletGroup::new(group_bytes, super::parse_group_bytes_v2),
rest,
))
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::as_conversions,
reason = "test code: panics and type conversions acceptable"
)]
mod tests {
use super::*;
use crate::core::counter::CounterCodeV1;
use crate::core::indexer::IndexerBuilder;
use crate::core::indexer::code::IndexedSigCode;
use alloc::format;
use alloc::vec::Vec;
use core::num::NonZeroUsize;
fn build_siger_qb64(index: u32) -> Vec<u8> {
IndexerBuilder::new()
.with_code(IndexedSigCode::Ed25519)
.with_index(index)
.unwrap()
.with_raw(&[0u8; 64])
.unwrap()
.to_qb64()
.into_bytes()
}
fn build_counter_qb64(code: CounterCodeV1, count: u32) -> Vec<u8> {
let hard = code.as_str();
let ss = code.soft_size();
let ss_nz = NonZeroUsize::new(ss).unwrap();
let soft = crate::b64::encode_int(count, ss_nz);
format!("{hard}{soft}").into_bytes()
}
fn build_controller_idx_sigs_group() -> Vec<u8> {
let mut g = build_counter_qb64(CounterCodeV1::ControllerIdxSigs, 1);
g.extend_from_slice(&build_siger_qb64(0));
g
}
#[test]
fn parse_quadlets_huge_count_needs_bytes_no_panic() {
let input = Bytes::from_static(b"AAAA");
let err = parse_quadlets(&input, u32::MAX).unwrap_err();
assert!(matches!(err, ParseError::NeedBytes(_)));
}
#[test]
fn parse_quadlets_v2_huge_count_needs_bytes_no_panic() {
let input = Bytes::from_static(b"AAAA");
let err = parse_quadlets_v2(&input, u32::MAX).unwrap_err();
assert!(matches!(err, ParseError::NeedBytes(_)));
}
#[test]
fn parse_quadlets_need_bytes_reports_exact_shortfall() {
let input = Bytes::from_static(b"AAAA");
let err = parse_quadlets(&input, 2).unwrap_err();
assert_eq!(err, ParseError::NeedBytes(4));
}
#[test]
fn parse_quadlets_v2_need_bytes_reports_exact_shortfall() {
let input = Bytes::from_static(b"AAAA");
let err = parse_quadlets_v2(&input, 2).unwrap_err();
assert_eq!(err, ParseError::NeedBytes(4));
}
#[test]
fn parse_quadlets_v2_exact_size_succeeds() {
let input = Bytes::from_static(b"AAAABBBB");
let (group, rest) = parse_quadlets_v2(&input, 2).unwrap();
assert_eq!(group.quadlet_count(), 2);
assert_eq!(group.raw_bytes(), b"AAAABBBB");
assert!(rest.is_empty());
}
#[test]
fn parse_quadlets_exact_size_succeeds() {
let input = Bytes::from_static(b"AAAABBBB");
let (group, rest) = parse_quadlets(&input, 2).unwrap();
assert_eq!(group.quadlet_count(), 2);
assert_eq!(group.raw_bytes(), b"AAAABBBB");
assert!(rest.is_empty());
}
#[test]
fn quadlet_group_iterates_all_inner_groups() {
let mut payload = build_controller_idx_sigs_group();
payload.extend_from_slice(&build_controller_idx_sigs_group());
let quadlets = u32::try_from(payload.len() / 4).unwrap();
let parent = Bytes::copy_from_slice(&payload);
let (group, rest) = parse_quadlets(&parent, quadlets).unwrap();
assert!(rest.is_empty());
let inner: Vec<_> = group.collect::<Result<_, _>>().unwrap();
assert_eq!(inner.len(), 2, "QuadletGroup must yield both inner groups");
assert!(matches!(inner[0], CesrGroup::ControllerIdxSigs(_)));
assert!(matches!(inner[1], CesrGroup::ControllerIdxSigs(_)));
}
#[test]
fn parse_quadlets_slices_without_copying() {
let payload = b"AAAABBBB";
let parent = Bytes::copy_from_slice(payload);
let parent_start = parent.as_ptr() as usize;
let parent_end = parent_start + parent.len();
let (group, _rest) = parse_quadlets(&parent, 2).unwrap();
let raw_ptr = group.raw_bytes().as_ptr() as usize;
assert!(
raw_ptr >= parent_start && raw_ptr < parent_end,
"QuadletGroup raw must be a slice of the parent buffer, not a copy"
);
}
}