#[cfg(feature = "alloc")]
#[allow(
unused_imports,
reason = "alloc prelude items; subset used per cfg/feature combination"
)]
use alloc::{format, vec::Vec};
use core::any::TypeId;
use core::marker::PhantomData;
use crate::core::counter::CounterCodeV1;
use crate::core::counter::CounterCodeV2;
use bytes::BytesMut;
use tokio_util::codec::Decoder;
use tokio_util::codec::Encoder;
use crate::stream::error::ParseError;
use crate::stream::group::AttachmentGroup;
use crate::stream::group::BodyWithAttachmentGroup;
use crate::stream::group::CesrGroup;
use crate::stream::group::DatagramSegmentGroup;
use crate::stream::group::ESSRPayloadGroup;
use crate::stream::group::ESSRWrapperGroup;
use crate::stream::group::FixBodyGroup;
use crate::stream::group::GenericGroup;
use crate::stream::group::GenericListGroup;
use crate::stream::group::GenericMapGroup;
use crate::stream::group::MapBodyGroup;
use crate::stream::group::NonNativeBodyGroup;
use crate::stream::group::QuadletGroup;
use crate::stream::group::parse_group;
use crate::stream::group::parse_group_inner;
use crate::stream::group::parse_group_inner_v2;
use crate::stream::group::parse_group_v2;
use crate::stream::parse::parse_counter;
use crate::stream::parse::parse_counter_v2;
use crate::stream::version::CesrEncode;
use crate::stream::version::V1;
use crate::stream::version::Version;
const fn is_quadlet_v1(code: CounterCodeV1) -> bool {
matches!(
code,
CounterCodeV1::AttachmentGroup
| CounterCodeV1::BigAttachmentGroup
| CounterCodeV1::GenericGroup
| CounterCodeV1::BigGenericGroup
| CounterCodeV1::BodyWithAttachmentGroup
| CounterCodeV1::BigBodyWithAttachmentGroup
| CounterCodeV1::NonNativeBodyGroup
| CounterCodeV1::BigNonNativeBodyGroup
| CounterCodeV1::ESSRPayloadGroup
| CounterCodeV1::BigESSRPayloadGroup
)
}
fn quadlet_to_group_v1(code: CounterCodeV1, qg: QuadletGroup) -> CesrGroup {
match code {
CounterCodeV1::AttachmentGroup | CounterCodeV1::BigAttachmentGroup => {
CesrGroup::AttachmentGroup(AttachmentGroup(qg))
}
CounterCodeV1::GenericGroup | CounterCodeV1::BigGenericGroup => {
CesrGroup::GenericGroup(GenericGroup(qg))
}
CounterCodeV1::BodyWithAttachmentGroup | CounterCodeV1::BigBodyWithAttachmentGroup => {
CesrGroup::BodyWithAttachmentGroup(BodyWithAttachmentGroup(qg))
}
CounterCodeV1::NonNativeBodyGroup | CounterCodeV1::BigNonNativeBodyGroup => {
CesrGroup::NonNativeBodyGroup(NonNativeBodyGroup(qg))
}
CounterCodeV1::ESSRPayloadGroup | CounterCodeV1::BigESSRPayloadGroup => {
CesrGroup::ESSRPayloadGroup(ESSRPayloadGroup(qg))
}
_ => unreachable!("is_quadlet_v1 should have returned false"),
}
}
const fn is_quadlet_v2(code: CounterCodeV2) -> bool {
matches!(
code,
CounterCodeV2::AttachmentGroup
| CounterCodeV2::BigAttachmentGroup
| CounterCodeV2::GenericGroup
| CounterCodeV2::BigGenericGroup
| CounterCodeV2::BodyWithAttachmentGroup
| CounterCodeV2::BigBodyWithAttachmentGroup
| CounterCodeV2::NonNativeBodyGroup
| CounterCodeV2::BigNonNativeBodyGroup
| CounterCodeV2::ESSRPayloadGroup
| CounterCodeV2::BigESSRPayloadGroup
| CounterCodeV2::DatagramSegmentGroup
| CounterCodeV2::BigDatagramSegmentGroup
| CounterCodeV2::ESSRWrapperGroup
| CounterCodeV2::BigESSRWrapperGroup
| CounterCodeV2::FixBodyGroup
| CounterCodeV2::BigFixBodyGroup
| CounterCodeV2::MapBodyGroup
| CounterCodeV2::BigMapBodyGroup
| CounterCodeV2::GenericMapGroup
| CounterCodeV2::BigGenericMapGroup
| CounterCodeV2::GenericListGroup
| CounterCodeV2::BigGenericListGroup
)
}
fn quadlet_to_group_v2(code: CounterCodeV2, qg: QuadletGroup) -> CesrGroup {
match code {
CounterCodeV2::AttachmentGroup | CounterCodeV2::BigAttachmentGroup => {
CesrGroup::AttachmentGroup(AttachmentGroup(qg))
}
CounterCodeV2::GenericGroup | CounterCodeV2::BigGenericGroup => {
CesrGroup::GenericGroup(GenericGroup(qg))
}
CounterCodeV2::BodyWithAttachmentGroup | CounterCodeV2::BigBodyWithAttachmentGroup => {
CesrGroup::BodyWithAttachmentGroup(BodyWithAttachmentGroup(qg))
}
CounterCodeV2::NonNativeBodyGroup | CounterCodeV2::BigNonNativeBodyGroup => {
CesrGroup::NonNativeBodyGroup(NonNativeBodyGroup(qg))
}
CounterCodeV2::ESSRPayloadGroup | CounterCodeV2::BigESSRPayloadGroup => {
CesrGroup::ESSRPayloadGroup(ESSRPayloadGroup(qg))
}
CounterCodeV2::DatagramSegmentGroup | CounterCodeV2::BigDatagramSegmentGroup => {
CesrGroup::DatagramSegmentGroup(DatagramSegmentGroup(qg))
}
CounterCodeV2::ESSRWrapperGroup | CounterCodeV2::BigESSRWrapperGroup => {
CesrGroup::ESSRWrapperGroup(ESSRWrapperGroup(qg))
}
CounterCodeV2::FixBodyGroup | CounterCodeV2::BigFixBodyGroup => {
CesrGroup::FixBodyGroup(FixBodyGroup(qg))
}
CounterCodeV2::MapBodyGroup | CounterCodeV2::BigMapBodyGroup => {
CesrGroup::MapBodyGroup(MapBodyGroup(qg))
}
CounterCodeV2::GenericMapGroup | CounterCodeV2::BigGenericMapGroup => {
CesrGroup::GenericMapGroup(GenericMapGroup(qg))
}
CounterCodeV2::GenericListGroup | CounterCodeV2::BigGenericListGroup => {
CesrGroup::GenericListGroup(GenericListGroup(qg))
}
_ => unreachable!("is_quadlet_v2 should have returned false"),
}
}
fn decode_v1(buf: &mut BytesMut) -> Result<Option<CesrGroup>, ParseError> {
let (code, count, after_counter) = match parse_counter(buf.as_ref()) {
Ok(result) => result,
Err(ParseError::NeedBytes(_)) => return Ok(None),
Err(e) => return Err(e),
};
let counter_size = buf.len() - after_counter.len();
if is_quadlet_v1(code) {
let inner_bytes = usize::try_from(count).unwrap_or(0).saturating_mul(4);
let total = counter_size + inner_bytes;
if buf.len() < total {
return Ok(None);
}
let frozen = buf.split_to(total).freeze();
let payload = frozen.slice(counter_size..);
let qg = QuadletGroup::new(payload, parse_group_inner);
Ok(Some(quadlet_to_group_v1(code, qg)))
} else {
match parse_group(buf.as_ref()) {
Ok((group, rest)) => {
let consumed = buf.len() - rest.len();
let _ = buf.split_to(consumed);
Ok(Some(group))
}
Err(ParseError::NeedBytes(_)) => Ok(None),
Err(e) => Err(e),
}
}
}
fn decode_v2(buf: &mut BytesMut) -> Result<Option<CesrGroup>, ParseError> {
let (code, count, after_counter) = match parse_counter_v2(buf.as_ref()) {
Ok(result) => result,
Err(ParseError::NeedBytes(_)) => return Ok(None),
Err(e) => return Err(e),
};
let counter_size = buf.len() - after_counter.len();
if is_quadlet_v2(code) {
let inner_bytes = usize::try_from(count).unwrap_or(0).saturating_mul(4);
let total = counter_size + inner_bytes;
if buf.len() < total {
return Ok(None);
}
let frozen = buf.split_to(total).freeze();
let payload = frozen.slice(counter_size..);
let qg = QuadletGroup::new(payload, parse_group_inner_v2);
Ok(Some(quadlet_to_group_v2(code, qg)))
} else {
match parse_group_v2(buf.as_ref()) {
Ok((group, rest)) => {
let consumed = buf.len() - rest.len();
let _ = buf.split_to(consumed);
Ok(Some(group))
}
Err(ParseError::NeedBytes(_)) => Ok(None),
Err(e) => Err(e),
}
}
}
pub struct CesrCodec<V: Version> {
_version: PhantomData<V>,
}
impl<V: Version> CesrCodec<V> {
#[must_use]
pub const fn new() -> Self {
Self {
_version: PhantomData,
}
}
}
impl<V: Version> Default for CesrCodec<V> {
fn default() -> Self {
Self::new()
}
}
impl<V: Version> Decoder for CesrCodec<V> {
type Item = CesrGroup;
type Error = ParseError;
fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
if buf.is_empty() {
return Ok(None);
}
if TypeId::of::<V>() == TypeId::of::<V1>() {
decode_v1(buf)
} else {
decode_v2(buf)
}
}
}
impl<V: Version> Encoder<CesrGroup> for CesrCodec<V>
where
CesrGroup: CesrEncode<V>,
{
type Error = ParseError;
fn encode(&mut self, item: CesrGroup, dst: &mut BytesMut) -> Result<(), Self::Error> {
item.encode_cesr(dst)
}
}
#[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 core::num::NonZeroUsize;
use crate::core::counter::CounterCodeV1;
use crate::core::indexer::IndexerBuilder;
use crate::core::indexer::code::IndexedSigCode;
use bytes::BytesMut;
use super::*;
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::utils::encode_int(count, ss_nz).unwrap();
format!("{hard}{soft}").into_bytes()
}
#[test]
fn decode_returns_none_on_empty() {
let mut codec = CesrCodec::<V1>::new();
let mut buf = BytesMut::new();
assert!(codec.decode(&mut buf).unwrap().is_none());
}
#[test]
fn decode_returns_none_on_incomplete() {
let mut codec = CesrCodec::<V1>::new();
let mut buf = BytesMut::from(&b"-A"[..]);
assert!(codec.decode(&mut buf).unwrap().is_none());
}
#[test]
fn decode_complete_group() {
let mut codec = CesrCodec::<V1>::new();
let mut data = build_counter_qb64(CounterCodeV1::ControllerIdxSigs, 1);
data.extend_from_slice(&build_siger_qb64(0));
let mut buf = BytesMut::from(data.as_slice());
let group = codec.decode(&mut buf).unwrap().unwrap();
assert!(matches!(group, CesrGroup::ControllerIdxSigs(_)));
assert!(buf.is_empty());
}
#[test]
fn decode_leaves_remainder() {
let mut codec = CesrCodec::<V1>::new();
let mut data = build_counter_qb64(CounterCodeV1::ControllerIdxSigs, 1);
data.extend_from_slice(&build_siger_qb64(0));
data.extend_from_slice(b"EXTRA");
let mut buf = BytesMut::from(data.as_slice());
let group = codec.decode(&mut buf).unwrap().unwrap();
assert!(matches!(group, CesrGroup::ControllerIdxSigs(_)));
assert_eq!(&buf[..], b"EXTRA");
}
#[test]
fn decode_incremental() {
let mut codec = CesrCodec::<V1>::new();
let mut full = build_counter_qb64(CounterCodeV1::ControllerIdxSigs, 1);
full.extend_from_slice(&build_siger_qb64(0));
let mut buf = BytesMut::from(&full[..10]);
assert!(codec.decode(&mut buf).unwrap().is_none());
assert_eq!(buf.len(), 10);
buf.extend_from_slice(&full[10..]);
let group = codec.decode(&mut buf).unwrap().unwrap();
assert!(matches!(group, CesrGroup::ControllerIdxSigs(_)));
}
#[test]
fn decode_malformed_returns_error() {
let mut codec = CesrCodec::<V1>::new();
let mut buf = BytesMut::from(&b"INVALID_NOT_A_COUNTER"[..]);
let result = codec.decode(&mut buf);
assert!(result.is_err());
}
#[test]
fn decode_quadlet_group_zero_copy() {
let mut codec = CesrCodec::<V1>::new();
let mut inner = build_counter_qb64(CounterCodeV1::ControllerIdxSigs, 1);
inner.extend_from_slice(&build_siger_qb64(0));
let quadlets = u32::try_from(inner.len() / 4).unwrap();
let mut outer = build_counter_qb64(CounterCodeV1::AttachmentGroup, quadlets);
outer.extend_from_slice(&inner);
let mut buf = BytesMut::from(outer.as_slice());
let group = codec.decode(&mut buf).unwrap().unwrap();
assert!(matches!(group, CesrGroup::AttachmentGroup(_)));
assert!(buf.is_empty());
}
#[test]
fn decode_quadlet_group_leaves_remainder() {
let mut codec = CesrCodec::<V1>::new();
let mut inner = build_counter_qb64(CounterCodeV1::ControllerIdxSigs, 1);
inner.extend_from_slice(&build_siger_qb64(0));
let quadlets = u32::try_from(inner.len() / 4).unwrap();
let mut outer = build_counter_qb64(CounterCodeV1::AttachmentGroup, quadlets);
outer.extend_from_slice(&inner);
outer.extend_from_slice(b"TRAILING");
let mut buf = BytesMut::from(outer.as_slice());
let group = codec.decode(&mut buf).unwrap().unwrap();
assert!(matches!(group, CesrGroup::AttachmentGroup(_)));
assert_eq!(&buf[..], b"TRAILING");
}
#[test]
fn decode_quadlet_group_incomplete_returns_none() {
let mut codec = CesrCodec::<V1>::new();
let mut inner = build_counter_qb64(CounterCodeV1::ControllerIdxSigs, 1);
inner.extend_from_slice(&build_siger_qb64(0));
let quadlets = u32::try_from(inner.len() / 4).unwrap();
let mut outer = build_counter_qb64(CounterCodeV1::AttachmentGroup, quadlets);
outer.extend_from_slice(&inner);
let mut buf = BytesMut::from(&outer[..outer.len() - 4]);
assert!(codec.decode(&mut buf).unwrap().is_none());
}
#[test]
fn decode_generic_group_zero_copy() {
let mut codec = CesrCodec::<V1>::new();
let mut inner = build_counter_qb64(CounterCodeV1::ControllerIdxSigs, 1);
inner.extend_from_slice(&build_siger_qb64(0));
let quadlets = u32::try_from(inner.len() / 4).unwrap();
let mut outer = build_counter_qb64(CounterCodeV1::GenericGroup, quadlets);
outer.extend_from_slice(&inner);
let mut buf = BytesMut::from(outer.as_slice());
let group = codec.decode(&mut buf).unwrap().unwrap();
assert!(matches!(group, CesrGroup::GenericGroup(_)));
assert!(buf.is_empty());
}
#[test]
fn encode_decode_controller_idx_sigs_roundtrip() {
use crate::core::primitives::Siger;
use bytes::Bytes;
use tokio_util::codec::Encoder;
let mut codec = CesrCodec::<V1>::new();
let indexer = IndexerBuilder::new()
.with_code(IndexedSigCode::Ed25519)
.with_index(0)
.unwrap()
.with_raw(&[0u8; 64])
.unwrap();
let siger = Siger::new(indexer);
let raw = siger.to_qb64().into_bytes();
let group = CesrGroup::ControllerIdxSigs(
crate::stream::group::types::ControllerIdxSigs::new(Bytes::from(raw), 1),
);
let mut buf = BytesMut::new();
Encoder::encode(&mut codec, group, &mut buf).unwrap();
let decoded = codec.decode(&mut buf).unwrap().unwrap();
assert!(matches!(decoded, CesrGroup::ControllerIdxSigs(g) if g.count() == 1));
assert!(buf.is_empty());
}
#[test]
fn encode_decode_attachment_group_roundtrip() {
use tokio_util::codec::Encoder;
let mut codec = CesrCodec::<V1>::new();
let mut inner = build_counter_qb64(CounterCodeV1::ControllerIdxSigs, 1);
inner.extend_from_slice(&build_siger_qb64(0));
let quadlets = u32::try_from(inner.len() / 4).unwrap();
let mut outer = build_counter_qb64(CounterCodeV1::AttachmentGroup, quadlets);
outer.extend_from_slice(&inner);
let mut buf = BytesMut::from(outer.as_slice());
let original = codec.decode(&mut buf).unwrap().unwrap();
assert!(matches!(original, CesrGroup::AttachmentGroup(_)));
assert!(buf.is_empty());
Encoder::encode(&mut codec, original, &mut buf).unwrap();
let roundtripped = codec.decode(&mut buf).unwrap().unwrap();
assert!(matches!(roundtripped, CesrGroup::AttachmentGroup(_)));
assert!(buf.is_empty());
}
#[test]
fn encode_v2_only_group_with_v1_codec_returns_error() {
use bytes::Bytes;
use tokio_util::codec::Encoder;
let mut codec = CesrCodec::<V1>::new();
let qg = QuadletGroup::new(
Bytes::from_static(b"ABCD"),
crate::stream::group::parse_group_inner_v2,
);
let group =
CesrGroup::DatagramSegmentGroup(crate::stream::group::types::DatagramSegmentGroup(qg));
let mut buf = BytesMut::new();
let result = Encoder::encode(&mut codec, group, &mut buf);
assert!(result.is_err());
}
#[test]
fn v2_codec_decodes_v2_groups() {
use core::num::NonZeroUsize;
use crate::core::counter::CounterCodeV2;
use crate::stream::version::V2;
use bytes::BytesMut;
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::utils::encode_int(count, ss_nz).unwrap();
format!("{hard}{soft}").into_bytes()
}
let mut codec = CesrCodec::<V2>::new();
let mut data = build_counter_v2_qb64(CounterCodeV2::ControllerIdxSigs, 1);
data.extend_from_slice(&build_siger_qb64(0));
let mut buf = BytesMut::from(data.as_slice());
let group = codec.decode(&mut buf).unwrap().unwrap();
assert!(matches!(group, CesrGroup::ControllerIdxSigs(_)));
assert!(buf.is_empty());
}
#[test]
fn default_codec_works() {
let mut codec = CesrCodec::<V1>::default();
let mut buf = BytesMut::new();
assert!(codec.decode(&mut buf).unwrap().is_none());
}
}