use crate::core::counter::CounterCodeV2;
#[cfg(feature = "alloc")]
#[allow(
unused_imports,
reason = "alloc prelude items; subset used per cfg/feature combination"
)]
use alloc::{format, vec::Vec};
use bytes::Bytes;
use crate::stream::error::ParseError;
use crate::stream::group::QuadletGroup;
use crate::stream::group::parse_group_bytes;
use crate::stream::group::parse_group_bytes_v2;
use crate::stream::group::types::CesrGroup;
use crate::stream::parse::parse_counter_v2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CesrVersion {
V1,
V2,
}
const MAX_DEPTH: usize = 8;
pub fn unwrap_generic_group(
group: &QuadletGroup,
version: CesrVersion,
) -> Result<Vec<CesrGroup>, ParseError> {
let mut results = Vec::new();
let initial = group.to_bytes();
let mut stack: Vec<(CesrVersion, Bytes, usize)> = Vec::new();
let mut current_version = version;
let mut current_data = initial;
let mut depth: usize = 0;
loop {
if current_data.is_empty() {
if let Some((prev_version, prev_data, prev_depth)) = stack.pop() {
current_version = prev_version;
current_data = prev_data;
depth = prev_depth;
continue;
}
break;
}
let (parsed_group, rest) = match current_version {
CesrVersion::V1 => parse_group_bytes(¤t_data)?,
CesrVersion::V2 => parse_group_bytes_v2(¤t_data)?,
};
match parsed_group {
CesrGroup::GenericGroup(g) => {
if depth >= MAX_DEPTH {
return Err(ParseError::Malformed("max nesting depth exceeded".into()));
}
let inner_full = g.0.to_bytes();
let (inner_version, genus_size) =
check_genus_version_offset(&inner_full, current_version)?;
let inner_bytes = inner_full.slice(genus_size..);
if !rest.is_empty() {
stack.push((current_version, rest, depth));
}
current_version = inner_version;
current_data = inner_bytes;
depth += 1;
}
other => {
results.push(other);
current_data = rest;
}
}
}
Ok(results)
}
fn check_genus_version_offset(
input: &[u8],
default: CesrVersion,
) -> Result<(CesrVersion, usize), ParseError> {
if input.len() < 8 {
return Ok((default, 0));
}
if input[0] == b'-' && input[1] == b'_' {
match parse_counter_v2(input) {
Ok((CounterCodeV2::KERIACDCGenusVersion, _count, rest)) => {
let genus_size = input.len() - rest.len();
let soft_bytes = &input[5..8];
let version = decode_genus_version(soft_bytes)?;
Ok((version, genus_size))
}
_ => Ok((default, 0)),
}
} else {
Ok((default, 0))
}
}
fn decode_genus_version(soft: &[u8]) -> Result<CesrVersion, ParseError> {
let soft_str = core::str::from_utf8(soft)
.map_err(|_| ParseError::Malformed("invalid UTF-8 in genus version".into()))?;
let value: u32 = crate::b64::decode_int(soft_str)?;
let major = value >> 12;
match major {
1 => Ok(CesrVersion::V1),
2 => Ok(CesrVersion::V2),
_ => Err(ParseError::Malformed(format!(
"unsupported genus version major={major}"
))),
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::as_conversions,
clippy::cast_possible_truncation,
reason = "test code: panics and type conversions acceptable"
)]
mod tests {
use super::*;
use crate::core::counter::CounterCodeV1;
use crate::core::counter::CounterCodeV2;
use crate::core::indexer::IndexerBuilder;
use crate::core::indexer::code::IndexedSigCode;
use bytes::Bytes;
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_counter_v2_qb64(code: CounterCodeV2, 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_simple_inner_group() -> Vec<u8> {
let mut inner = build_counter_qb64(CounterCodeV1::ControllerIdxSigs, 1);
inner.extend_from_slice(&build_siger_qb64(0));
inner
}
fn wrap_in_quadlet_group_v1(inner: &[u8]) -> QuadletGroup {
assert_eq!(inner.len() % 4, 0, "inner must be multiple of 4 bytes");
let group_bytes = Bytes::copy_from_slice(inner);
QuadletGroup::new(group_bytes, crate::stream::group::parse_group_bytes)
}
#[test]
fn unwrap_simple_v1() {
let inner = build_simple_inner_group();
let group = wrap_in_quadlet_group_v1(&inner);
let results = unwrap_generic_group(&group, CesrVersion::V1).unwrap();
assert_eq!(results.len(), 1);
assert!(matches!(results[0], CesrGroup::ControllerIdxSigs(_)));
}
#[test]
fn unwrap_multiple_groups_inside() {
let mut inner = build_simple_inner_group();
inner.extend_from_slice(&build_counter_qb64(CounterCodeV1::WitnessIdxSigs, 1));
inner.extend_from_slice(&build_siger_qb64(1));
let group = wrap_in_quadlet_group_v1(&inner);
let results = unwrap_generic_group(&group, CesrVersion::V1).unwrap();
assert_eq!(results.len(), 2);
assert!(matches!(results[0], CesrGroup::ControllerIdxSigs(_)));
assert!(matches!(results[1], CesrGroup::WitnessIdxSigs(_)));
}
#[test]
fn unwrap_nested_generic_groups() {
let inner_content = build_simple_inner_group();
let inner_quadlets = inner_content.len() / 4;
let mut nested = build_counter_qb64(CounterCodeV1::GenericGroup, inner_quadlets as u32);
nested.extend_from_slice(&inner_content);
let outer = wrap_in_quadlet_group_v1(&nested);
let results = unwrap_generic_group(&outer, CesrVersion::V1).unwrap();
assert_eq!(results.len(), 1);
assert!(matches!(results[0], CesrGroup::ControllerIdxSigs(_)));
}
#[test]
fn unwrap_nested_generic_groups_slices_without_copying() {
let inner_content = build_simple_inner_group();
let inner_quadlets = inner_content.len() / 4;
let mut nested = build_counter_qb64(CounterCodeV1::GenericGroup, inner_quadlets as u32);
nested.extend_from_slice(&inner_content);
let outer = wrap_in_quadlet_group_v1(&nested);
let parent = outer.raw_bytes();
let start = parent.as_ptr() as usize;
let end = start + parent.len();
let results = unwrap_generic_group(&outer, CesrVersion::V1).unwrap();
assert_eq!(results.len(), 1);
let CesrGroup::ControllerIdxSigs(inner) = &results[0] else {
panic!("expected ControllerIdxSigs group");
};
let inner_ptr = inner.raw_bytes().as_ptr() as usize;
assert!(
inner_ptr >= start && inner_ptr < end,
"unwrapped inner group must be a slice of the parent buffer, not a copy"
);
}
#[test]
fn unwrap_empty_group() {
let group_bytes = Bytes::new();
let group = QuadletGroup::new(group_bytes, crate::stream::group::parse_group_bytes);
let results = unwrap_generic_group(&group, CesrVersion::V1).unwrap();
assert!(results.is_empty());
}
#[test]
fn unwrap_v2_mode() {
let mut inner = build_counter_v2_qb64(CounterCodeV2::ControllerIdxSigs, 1);
inner.extend_from_slice(&build_siger_qb64(0));
let group_bytes = Bytes::copy_from_slice(&inner);
let group = QuadletGroup::new(group_bytes, crate::stream::group::parse_group_bytes_v2);
let results = unwrap_generic_group(&group, CesrVersion::V2).unwrap();
assert_eq!(results.len(), 1);
assert!(matches!(results[0], CesrGroup::ControllerIdxSigs(_)));
}
#[test]
fn genus_version_decode_v1() {
let soft = b"BAA";
let version = decode_genus_version(soft).unwrap();
assert_eq!(version, CesrVersion::V1);
}
#[test]
fn genus_version_decode_v2() {
let soft = b"CAA";
let version = decode_genus_version(soft).unwrap();
assert_eq!(version, CesrVersion::V2);
}
#[test]
fn genus_version_decode_unsupported() {
let soft = b"AAA";
let result = decode_genus_version(soft);
assert!(result.is_err());
}
#[test]
fn genus_version_switches_parsing() {
let genus_counter = build_genus_version_counter(2, 0);
let mut v2_group = build_counter_v2_qb64(CounterCodeV2::ControllerIdxSigs, 1);
v2_group.extend_from_slice(&build_siger_qb64(0));
let mut inner_of_nested = genus_counter;
inner_of_nested.extend_from_slice(&v2_group);
let nested_quadlets = inner_of_nested.len() / 4;
let mut nested_group =
build_counter_qb64(CounterCodeV1::GenericGroup, nested_quadlets as u32);
nested_group.extend_from_slice(&inner_of_nested);
let outer = wrap_in_quadlet_group_v1(&nested_group);
let results = unwrap_generic_group(&outer, CesrVersion::V1).unwrap();
assert_eq!(results.len(), 1);
assert!(matches!(results[0], CesrGroup::ControllerIdxSigs(_)));
}
fn build_genus_version_counter(major: u32, minor: u32) -> Vec<u8> {
let value = (major << 12) | minor;
let ss_nz = NonZeroUsize::new(3).unwrap();
let soft = crate::b64::encode_int(value, ss_nz);
format!("-_AAA{soft}").into_bytes()
}
#[test]
fn unwrap_generic_group_preserves_trailing_sibling() {
let inner_content = build_simple_inner_group();
let inner_quadlets = inner_content.len() / 4;
let mut generic = build_counter_qb64(CounterCodeV1::GenericGroup, inner_quadlets as u32);
generic.extend_from_slice(&inner_content);
let mut sibling = build_counter_qb64(CounterCodeV1::WitnessIdxSigs, 1);
sibling.extend_from_slice(&build_siger_qb64(1));
let mut payload = generic;
payload.extend_from_slice(&sibling);
let outer = wrap_in_quadlet_group_v1(&payload);
let results = unwrap_generic_group(&outer, CesrVersion::V1).unwrap();
assert_eq!(results.len(), 2, "trailing sibling must survive recursion");
assert!(matches!(results[0], CesrGroup::ControllerIdxSigs(_)));
assert!(matches!(results[1], CesrGroup::WitnessIdxSigs(_)));
}
#[test]
fn check_genus_offset_parses_exact_8_byte_counter() {
let genus = build_genus_version_counter(2, 0);
assert_eq!(genus.len(), 8, "genus counter must be exactly 8 bytes");
let (version, size) = check_genus_version_offset(&genus, CesrVersion::V1).unwrap();
assert_eq!(version, CesrVersion::V2);
assert_eq!(size, 8);
}
#[test]
fn max_depth_exceeded() {
let mut content = build_simple_inner_group();
for _ in 0..=MAX_DEPTH {
let quadlets = content.len() / 4;
let mut wrapped = build_counter_qb64(CounterCodeV1::GenericGroup, quadlets as u32);
wrapped.extend_from_slice(&content);
content = wrapped;
}
let outer = wrap_in_quadlet_group_v1(&content);
let result = unwrap_generic_group(&outer, CesrVersion::V1);
assert!(result.is_err());
match result.unwrap_err() {
ParseError::Malformed(msg) => {
assert!(msg.contains("max nesting depth exceeded"));
}
other => panic!("expected Malformed, got {other:?}"),
}
}
}