#[cfg(feature = "alloc")]
#[allow(
unused_imports,
reason = "alloc prelude items; subset used per cfg/feature combination"
)]
use alloc::{format, string::String, vec, vec::Vec};
use core::num::NonZeroUsize;
use crate::core::counter::CounterCodeV1;
use crate::core::counter::CounterCodeV2;
use crate::core::matter::Matter;
use crate::core::matter::code::CesrCode;
use crate::core::matter::sizage::SizeType;
use base64::Engine;
use base64::engine::general_purpose as b64;
use bytes::BytesMut;
use crate::stream::cold::ColdCode;
use crate::stream::error::ParseError;
use crate::stream::group::types::AttachmentGroup;
use crate::stream::group::types::BackerRegistrarSealCouples;
use crate::stream::group::types::BlindedStateQuadruples;
use crate::stream::group::types::BodyWithAttachmentGroup;
use crate::stream::group::types::BoundStateSextuples;
use crate::stream::group::types::CesrGroup;
use crate::stream::group::types::ControllerIdxSigs;
use crate::stream::group::types::DatagramSegmentGroup;
use crate::stream::group::types::DigestSealSingles;
use crate::stream::group::types::ESSRPayloadGroup;
use crate::stream::group::types::ESSRWrapperGroup;
use crate::stream::group::types::FirstSeenReplayCouples;
use crate::stream::group::types::FixBodyGroup;
use crate::stream::group::types::GenericGroup;
use crate::stream::group::types::GenericListGroup;
use crate::stream::group::types::GenericMapGroup;
use crate::stream::group::types::MapBodyGroup;
use crate::stream::group::types::MerkleRootSealSingles;
use crate::stream::group::types::NonNativeBodyGroup;
use crate::stream::group::types::NonTransReceiptCouples;
use crate::stream::group::types::PathedMaterialCouples;
use crate::stream::group::types::SealSourceCouples;
use crate::stream::group::types::SealSourceLastSingles;
use crate::stream::group::types::SealSourceTriples;
use crate::stream::group::types::TransIdxSigGroups;
use crate::stream::group::types::TransLastIdxSigGroups;
use crate::stream::group::types::TransReceiptQuadruples;
use crate::stream::group::types::TypedDigestSealCouples;
use crate::stream::group::types::TypedMediaQuadruples;
use crate::stream::group::types::WitnessIdxSigs;
use crate::stream::message::VersionStringV2;
use crate::stream::util::int_to_b64;
use crate::stream::version::CesrEncode;
use crate::stream::version::V1;
use crate::stream::version::V2;
use crate::stream::version::Version;
#[must_use]
pub fn matter_to_qb64<C: CesrCode>(matter: &Matter<'_, C>) -> Vec<u8> {
let sizage = matter.code().get_sizage();
let hs = sizage.hs();
let ss = sizage.ss();
let xs = sizage.xs();
let ls = sizage.ls();
let cs = hs + ss;
let ps = cs % 4;
let code_str = matter.code().as_str();
let soft_str = if ss > 0 {
let xtra = "_".repeat(xs);
let tail = matter.soft();
format!("{xtra}{tail}")
} else {
String::new()
};
let fs = match sizage.fs() {
SizeType::Fixed(fixed) => usize::from(*fixed),
SizeType::Small | SizeType::Large => {
let raw_with_lead = matter.raw().len() + ls;
let quadlets = raw_with_lead.div_ceil(3);
(quadlets * 4) + cs
}
};
let mut padded = vec![0u8; ls + ps];
padded.extend_from_slice(matter.raw());
let b64_raw = b64::URL_SAFE_NO_PAD.encode(&padded);
let stripped = &b64_raw[ps..];
let mut out = Vec::with_capacity(fs);
out.extend_from_slice(code_str.as_bytes());
out.extend_from_slice(soft_str.as_bytes());
out.extend_from_slice(stripped.as_bytes());
debug_assert_eq!(out.len(), fs, "qb64 length mismatch for code {code_str}");
out
}
pub fn encode_counter_v1(code: CounterCodeV1, count: u32) -> Result<Vec<u8>, ParseError> {
let hard = code.as_str();
let ss = code.soft_size();
let ss_nz = NonZeroUsize::new(ss)
.ok_or_else(|| ParseError::Malformed(format!("counter code {hard} has zero soft size")))?;
let soft = crate::utils::encode_int(count, ss_nz).map_err(|_| {
ParseError::Malformed(format!("count {count} does not fit in {ss} base64 chars"))
})?;
Ok(format!("{hard}{soft}").into_bytes())
}
pub fn encode_counter_v2(code: CounterCodeV2, count: u32) -> Result<Vec<u8>, ParseError> {
let hard = code.as_str();
let ss = code.soft_size();
let ss_nz = NonZeroUsize::new(ss).ok_or_else(|| {
ParseError::Malformed(format!("V2 counter code {hard} has zero soft size"))
})?;
let soft = crate::utils::encode_int(count, ss_nz).map_err(|_| {
ParseError::Malformed(format!("count {count} does not fit in {ss} base64 chars"))
})?;
Ok(format!("{hard}{soft}").into_bytes())
}
pub fn encode_counter_auto_v1(code: CounterCodeV1, count: u32) -> Result<Vec<u8>, ParseError> {
if count > 4095 {
if let Some(big) = code.to_big() {
return encode_counter_v1(big, count);
}
return Err(ParseError::Malformed(format!(
"count {count} exceeds small limit and no big variant for {}",
code.as_str()
)));
}
encode_counter_v1(code, count)
}
pub fn encode_counter_auto_v2(code: CounterCodeV2, count: u32) -> Result<Vec<u8>, ParseError> {
if count > 4095 {
if let Some(big) = code.to_big() {
return encode_counter_v2(big, count);
}
return Err(ParseError::Malformed(format!(
"count {count} exceeds small limit and no big variant for {}",
code.as_str()
)));
}
encode_counter_v2(code, count)
}
pub fn encode_controller_idx_sigs_v1(group: &ControllerIdxSigs) -> Result<Vec<u8>, ParseError> {
encode_via_trait::<V1, _>(group)
}
pub fn encode_witness_idx_sigs_v1(group: &WitnessIdxSigs) -> Result<Vec<u8>, ParseError> {
encode_via_trait::<V1, _>(group)
}
pub fn encode_non_trans_receipt_couples_v1(
group: &NonTransReceiptCouples,
) -> Result<Vec<u8>, ParseError> {
encode_via_trait::<V1, _>(group)
}
pub fn encode_trans_receipt_quadruples_v1(
group: &TransReceiptQuadruples,
) -> Result<Vec<u8>, ParseError> {
encode_via_trait::<V1, _>(group)
}
pub fn encode_first_seen_replay_couples_v1(
group: &FirstSeenReplayCouples,
) -> Result<Vec<u8>, ParseError> {
encode_via_trait::<V1, _>(group)
}
pub fn encode_seal_source_couples_v1(group: &SealSourceCouples) -> Result<Vec<u8>, ParseError> {
encode_via_trait::<V1, _>(group)
}
pub fn encode_seal_source_triples_v1(group: &SealSourceTriples) -> Result<Vec<u8>, ParseError> {
encode_via_trait::<V1, _>(group)
}
pub fn encode_trans_idx_sig_groups_v1(group: &TransIdxSigGroups) -> Result<Vec<u8>, ParseError> {
encode_via_trait::<V1, _>(group)
}
pub fn encode_trans_last_idx_sig_groups_v1(
group: &TransLastIdxSigGroups,
) -> Result<Vec<u8>, ParseError> {
encode_via_trait::<V1, _>(group)
}
fn encode_via_trait<V: Version, T: CesrEncode<V>>(group: &T) -> Result<Vec<u8>, ParseError> {
let mut dst = BytesMut::new();
group.encode_cesr(&mut dst)?;
Ok(dst.to_vec())
}
pub fn encode_attachment_group_v1(inner_bytes: &[u8]) -> Result<Vec<u8>, ParseError> {
encode_quadlet_group_v1(CounterCodeV1::AttachmentGroup, inner_bytes)
}
pub fn encode_generic_group_v1(inner_bytes: &[u8]) -> Result<Vec<u8>, ParseError> {
encode_quadlet_group_v1(CounterCodeV1::GenericGroup, inner_bytes)
}
pub fn encode_body_with_attachment_group_v1(inner_bytes: &[u8]) -> Result<Vec<u8>, ParseError> {
encode_quadlet_group_v1(CounterCodeV1::BodyWithAttachmentGroup, inner_bytes)
}
pub fn encode_non_native_body_group_v1(inner_bytes: &[u8]) -> Result<Vec<u8>, ParseError> {
encode_quadlet_group_v1(CounterCodeV1::NonNativeBodyGroup, inner_bytes)
}
pub fn encode_essr_payload_group_v1(inner_bytes: &[u8]) -> Result<Vec<u8>, ParseError> {
encode_quadlet_group_v1(CounterCodeV1::ESSRPayloadGroup, inner_bytes)
}
fn encode_quadlet_group_v1(code: CounterCodeV1, inner_bytes: &[u8]) -> Result<Vec<u8>, ParseError> {
if !inner_bytes.len().is_multiple_of(4) {
return Err(ParseError::Malformed(
"quadlet group inner bytes must be a multiple of 4".into(),
));
}
let quadlets = u32::try_from(inner_bytes.len() / 4)
.map_err(|_| ParseError::Malformed("too many quadlets".into()))?;
let mut out = encode_counter_v1(code, quadlets)?;
out.extend_from_slice(inner_bytes);
Ok(out)
}
fn encode_quadlet_group_v2(code: CounterCodeV2, inner_bytes: &[u8]) -> Result<Vec<u8>, ParseError> {
if !inner_bytes.len().is_multiple_of(4) {
return Err(ParseError::Malformed(
"quadlet group inner bytes must be a multiple of 4".into(),
));
}
let quadlets = u32::try_from(inner_bytes.len() / 4)
.map_err(|_| ParseError::Malformed("too many quadlets".into()))?;
let mut out = encode_counter_v2(code, quadlets)?;
out.extend_from_slice(inner_bytes);
Ok(out)
}
pub fn encode_group_v1(group: &CesrGroup) -> Result<Vec<u8>, ParseError> {
let mut dst = BytesMut::new();
CesrEncode::<V1>::encode_cesr(group, &mut dst)?;
Ok(dst.to_vec())
}
pub fn encode_group_v2(group: &CesrGroup) -> Result<Vec<u8>, ParseError> {
let mut dst = BytesMut::new();
CesrEncode::<V2>::encode_cesr(group, &mut dst)?;
Ok(dst.to_vec())
}
macro_rules! impl_encode_element {
($ty:ty, v1 = $v1:expr, v2 = $v2:expr) => {
impl CesrEncode<V1> for $ty {
fn encode_cesr(&self, dst: &mut BytesMut) -> Result<(), ParseError> {
let counter = encode_counter_v1($v1, self.count())?;
dst.extend_from_slice(&counter);
dst.extend_from_slice(self.raw_bytes());
Ok(())
}
}
impl CesrEncode<V2> for $ty {
fn encode_cesr(&self, dst: &mut BytesMut) -> Result<(), ParseError> {
let counter = encode_counter_v2($v2, self.count())?;
dst.extend_from_slice(&counter);
dst.extend_from_slice(self.raw_bytes());
Ok(())
}
}
};
($ty:ty, v2 = $v2:expr) => {
impl CesrEncode<V2> for $ty {
fn encode_cesr(&self, dst: &mut BytesMut) -> Result<(), ParseError> {
let counter = encode_counter_v2($v2, self.count())?;
dst.extend_from_slice(&counter);
dst.extend_from_slice(self.raw_bytes());
Ok(())
}
}
};
}
macro_rules! impl_encode_quadlet {
($ty:ty, v1 = $v1:expr, v2 = $v2:expr) => {
impl CesrEncode<V1> for $ty {
fn encode_cesr(&self, dst: &mut BytesMut) -> Result<(), ParseError> {
let encoded = encode_quadlet_group_v1($v1, self.0.raw_bytes())?;
dst.extend_from_slice(&encoded);
Ok(())
}
}
impl CesrEncode<V2> for $ty {
fn encode_cesr(&self, dst: &mut BytesMut) -> Result<(), ParseError> {
let encoded = encode_quadlet_group_v2($v2, self.0.raw_bytes())?;
dst.extend_from_slice(&encoded);
Ok(())
}
}
};
($ty:ty, v2 = $v2:expr) => {
impl CesrEncode<V2> for $ty {
fn encode_cesr(&self, dst: &mut BytesMut) -> Result<(), ParseError> {
let encoded = encode_quadlet_group_v2($v2, self.0.raw_bytes())?;
dst.extend_from_slice(&encoded);
Ok(())
}
}
};
}
impl_encode_element!(
ControllerIdxSigs,
v1 = CounterCodeV1::ControllerIdxSigs,
v2 = CounterCodeV2::ControllerIdxSigs
);
impl_encode_element!(
WitnessIdxSigs,
v1 = CounterCodeV1::WitnessIdxSigs,
v2 = CounterCodeV2::WitnessIdxSigs
);
impl_encode_element!(
NonTransReceiptCouples,
v1 = CounterCodeV1::NonTransReceiptCouples,
v2 = CounterCodeV2::NonTransReceiptCouples
);
impl_encode_element!(
TransReceiptQuadruples,
v1 = CounterCodeV1::TransReceiptQuadruples,
v2 = CounterCodeV2::TransReceiptQuadruples
);
impl_encode_element!(
FirstSeenReplayCouples,
v1 = CounterCodeV1::FirstSeenReplayCouples,
v2 = CounterCodeV2::FirstSeenReplayCouples
);
impl_encode_element!(
TransIdxSigGroups,
v1 = CounterCodeV1::TransIdxSigGroups,
v2 = CounterCodeV2::TransIdxSigGroups
);
impl_encode_element!(
SealSourceCouples,
v1 = CounterCodeV1::SealSourceCouples,
v2 = CounterCodeV2::SealSourceCouples
);
impl_encode_element!(
TransLastIdxSigGroups,
v1 = CounterCodeV1::TransLastIdxSigGroups,
v2 = CounterCodeV2::TransLastIdxSigGroups
);
impl_encode_element!(
SealSourceTriples,
v1 = CounterCodeV1::SealSourceTriples,
v2 = CounterCodeV2::SealSourceTriples
);
impl_encode_element!(DigestSealSingles, v2 = CounterCodeV2::DigestSealSingles);
impl_encode_element!(
MerkleRootSealSingles,
v2 = CounterCodeV2::MerkleRootSealSingles
);
impl_encode_element!(
SealSourceLastSingles,
v2 = CounterCodeV2::SealSourceLastSingles
);
impl_encode_element!(
BackerRegistrarSealCouples,
v2 = CounterCodeV2::BackerRegistrarSealCouples
);
impl_encode_element!(
TypedDigestSealCouples,
v2 = CounterCodeV2::TypedDigestSealCouples
);
impl_encode_element!(
BlindedStateQuadruples,
v2 = CounterCodeV2::BlindedStateQuadruples
);
impl_encode_element!(BoundStateSextuples, v2 = CounterCodeV2::BoundStateSextuples);
impl_encode_element!(
TypedMediaQuadruples,
v2 = CounterCodeV2::TypedMediaQuadruples
);
impl_encode_quadlet!(
PathedMaterialCouples,
v1 = CounterCodeV1::PathedMaterialCouples,
v2 = CounterCodeV2::PathedMaterialCouples
);
impl_encode_quadlet!(
AttachmentGroup,
v1 = CounterCodeV1::AttachmentGroup,
v2 = CounterCodeV2::AttachmentGroup
);
impl_encode_quadlet!(
GenericGroup,
v1 = CounterCodeV1::GenericGroup,
v2 = CounterCodeV2::GenericGroup
);
impl_encode_quadlet!(
BodyWithAttachmentGroup,
v1 = CounterCodeV1::BodyWithAttachmentGroup,
v2 = CounterCodeV2::BodyWithAttachmentGroup
);
impl_encode_quadlet!(
NonNativeBodyGroup,
v1 = CounterCodeV1::NonNativeBodyGroup,
v2 = CounterCodeV2::NonNativeBodyGroup
);
impl_encode_quadlet!(
ESSRPayloadGroup,
v1 = CounterCodeV1::ESSRPayloadGroup,
v2 = CounterCodeV2::ESSRPayloadGroup
);
impl_encode_quadlet!(
DatagramSegmentGroup,
v2 = CounterCodeV2::DatagramSegmentGroup
);
impl_encode_quadlet!(ESSRWrapperGroup, v2 = CounterCodeV2::ESSRWrapperGroup);
impl_encode_quadlet!(FixBodyGroup, v2 = CounterCodeV2::FixBodyGroup);
impl_encode_quadlet!(MapBodyGroup, v2 = CounterCodeV2::MapBodyGroup);
impl_encode_quadlet!(GenericMapGroup, v2 = CounterCodeV2::GenericMapGroup);
impl_encode_quadlet!(GenericListGroup, v2 = CounterCodeV2::GenericListGroup);
impl CesrEncode<V2> for CesrGroup {
fn encode_cesr(&self, dst: &mut BytesMut) -> Result<(), ParseError> {
match self {
Self::ControllerIdxSigs(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::WitnessIdxSigs(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::NonTransReceiptCouples(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::TransReceiptQuadruples(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::FirstSeenReplayCouples(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::TransIdxSigGroups(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::SealSourceCouples(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::TransLastIdxSigGroups(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::SealSourceTriples(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::PathedMaterialCouples(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::AttachmentGroup(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::GenericGroup(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::BodyWithAttachmentGroup(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::NonNativeBodyGroup(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::ESSRPayloadGroup(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::DatagramSegmentGroup(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::ESSRWrapperGroup(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::FixBodyGroup(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::MapBodyGroup(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::GenericMapGroup(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::GenericListGroup(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::DigestSealSingles(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::MerkleRootSealSingles(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::SealSourceLastSingles(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::BackerRegistrarSealCouples(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::TypedDigestSealCouples(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::BlindedStateQuadruples(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::BoundStateSextuples(g) => CesrEncode::<V2>::encode_cesr(g, dst),
Self::TypedMediaQuadruples(g) => CesrEncode::<V2>::encode_cesr(g, dst),
}
}
}
impl CesrEncode<V1> for CesrGroup {
fn encode_cesr(&self, dst: &mut BytesMut) -> Result<(), ParseError> {
match self {
Self::ControllerIdxSigs(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::WitnessIdxSigs(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::NonTransReceiptCouples(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::TransReceiptQuadruples(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::FirstSeenReplayCouples(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::TransIdxSigGroups(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::SealSourceCouples(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::TransLastIdxSigGroups(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::SealSourceTriples(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::PathedMaterialCouples(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::AttachmentGroup(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::GenericGroup(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::BodyWithAttachmentGroup(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::NonNativeBodyGroup(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::ESSRPayloadGroup(g) => CesrEncode::<V1>::encode_cesr(g, dst),
Self::DatagramSegmentGroup(_)
| Self::ESSRWrapperGroup(_)
| Self::FixBodyGroup(_)
| Self::MapBodyGroup(_)
| Self::GenericMapGroup(_)
| Self::GenericListGroup(_)
| Self::DigestSealSingles(_)
| Self::MerkleRootSealSingles(_)
| Self::SealSourceLastSingles(_)
| Self::BackerRegistrarSealCouples(_)
| Self::TypedDigestSealCouples(_)
| Self::BlindedStateQuadruples(_)
| Self::BoundStateSextuples(_)
| Self::TypedMediaQuadruples(_) => Err(ParseError::Malformed(
"V2-only group type cannot be encoded with V1 counters".into(),
)),
}
}
}
const fn kind_to_bytes(kind: ColdCode) -> &'static [u8; 4] {
match kind {
ColdCode::Json => b"JSON",
ColdCode::Cbor => b"CBOR",
ColdCode::MessagePack => b"MGPK",
ColdCode::CesrBase64 | ColdCode::CesrBinary => b"CESR",
}
}
#[must_use]
pub fn encode_version_string_v2(vs: &VersionStringV2<'_>) -> Vec<u8> {
let mut out = Vec::with_capacity(19);
out.extend_from_slice(vs.protocol.as_bytes());
out.extend_from_slice(&int_to_b64(u64::from(vs.proto_major), 1));
out.extend_from_slice(&int_to_b64(u64::from(vs.proto_minor), 2));
out.extend_from_slice(&int_to_b64(u64::from(vs.genus_major), 1));
out.extend_from_slice(&int_to_b64(u64::from(vs.genus_minor), 2));
out.extend_from_slice(kind_to_bytes(vs.kind));
out.extend_from_slice(&int_to_b64(u64::from(vs.size), 4));
out.push(b'.');
out
}
#[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::*;
#[test]
fn encode_v1_controller_idx_sigs_count_2() {
let bytes = encode_counter_v1(CounterCodeV1::ControllerIdxSigs, 2).unwrap();
assert_eq!(&bytes, b"-AAC");
}
#[test]
fn encode_v1_controller_idx_sigs_count_0() {
let bytes = encode_counter_v1(CounterCodeV1::ControllerIdxSigs, 0).unwrap();
assert_eq!(&bytes, b"-AAA");
}
#[test]
fn encode_v1_controller_idx_sigs_count_1() {
let bytes = encode_counter_v1(CounterCodeV1::ControllerIdxSigs, 1).unwrap();
assert_eq!(&bytes, b"-AAB");
}
#[test]
fn encode_v1_witness_idx_sigs() {
let bytes = encode_counter_v1(CounterCodeV1::WitnessIdxSigs, 3).unwrap();
assert_eq!(&bytes, b"-BAD");
}
#[test]
fn encode_v1_attachment_group() {
let bytes = encode_counter_v1(CounterCodeV1::AttachmentGroup, 23).unwrap();
assert_eq!(&bytes, b"-VAX");
}
#[test]
fn encode_v2_controller_idx_sigs_count_2() {
let bytes = encode_counter_v2(CounterCodeV2::ControllerIdxSigs, 2).unwrap();
assert_eq!(&bytes, b"-KAC");
}
#[test]
fn encode_v2_attachment_group() {
let bytes = encode_counter_v2(CounterCodeV2::AttachmentGroup, 23).unwrap();
assert_eq!(&bytes, b"-CAX");
}
#[test]
fn encode_v1_roundtrip() {
use crate::stream::parse::parse_counter;
let original_code = CounterCodeV1::SealSourceCouples;
let original_count = 5_u32;
let encoded = encode_counter_v1(original_code, original_count).unwrap();
let (decoded_code, decoded_count, rest) = parse_counter(&encoded).unwrap();
assert_eq!(decoded_code, original_code);
assert_eq!(decoded_count, original_count);
assert!(rest.is_empty());
}
#[test]
fn encode_v2_roundtrip() {
use crate::stream::parse::parse_counter_v2;
let original_code = CounterCodeV2::SealSourceCouples;
let original_count = 5_u32;
let encoded = encode_counter_v2(original_code, original_count).unwrap();
let (decoded_code, decoded_count, rest) = parse_counter_v2(&encoded).unwrap();
assert_eq!(decoded_code, original_code);
assert_eq!(decoded_count, original_count);
assert!(rest.is_empty());
}
#[test]
fn auto_promote_v1_small_count_stays_small() {
let result = encode_counter_auto_v1(CounterCodeV1::GenericGroup, 100).unwrap();
assert_eq!(result.len(), 4);
assert!(result.starts_with(b"-T"));
}
#[test]
fn auto_promote_v1_large_count_promotes() {
let result = encode_counter_auto_v1(CounterCodeV1::GenericGroup, 8193).unwrap();
assert_eq!(result.len(), 8);
assert!(result.starts_with(b"--T"));
}
#[test]
fn auto_promote_v1_boundary() {
let small = encode_counter_auto_v1(CounterCodeV1::GenericGroup, 4095).unwrap();
assert_eq!(small.len(), 4);
let big = encode_counter_auto_v1(CounterCodeV1::GenericGroup, 4096).unwrap();
assert_eq!(big.len(), 8);
}
#[test]
fn auto_promote_v1_no_big_variant_errors() {
let result = encode_counter_auto_v1(CounterCodeV1::ControllerIdxSigs, 5000);
assert!(result.is_err());
}
#[test]
fn auto_promote_v2_large_count_promotes() {
let result = encode_counter_auto_v2(CounterCodeV2::ControllerIdxSigs, 8193).unwrap();
assert_eq!(result.len(), 8);
assert!(result.starts_with(b"--K"));
}
#[test]
fn auto_promote_v2_small_count_stays_small() {
let result = encode_counter_auto_v2(CounterCodeV2::ControllerIdxSigs, 100).unwrap();
assert_eq!(result.len(), 4);
assert!(result.starts_with(b"-K"));
}
mod matter_qb64 {
use super::*;
use crate::core::matter::builder::MatterBuilder;
#[test]
fn ed25519_verkey_roundtrip() {
let raw = [0xABu8; 32];
let ps = 1_usize;
let mut padded = vec![0u8; ps];
padded.extend_from_slice(&raw);
let payload_b64 = b64::URL_SAFE_NO_PAD.encode(&padded);
let expected = format!("D{}", &payload_b64[ps..]);
let matter = MatterBuilder::new()
.from_qualified_base64(expected.as_bytes())
.unwrap();
let encoded = matter_to_qb64(&matter);
assert_eq!(encoded, expected.as_bytes());
}
#[test]
fn ed25519_sig_roundtrip() {
let raw = [0xEFu8; 64];
let ps = 2_usize;
let mut padded = vec![0u8; ps];
padded.extend_from_slice(&raw);
let payload_b64 = b64::URL_SAFE_NO_PAD.encode(&padded);
let expected = format!("0B{}", &payload_b64[ps..]);
let matter = MatterBuilder::new()
.from_qualified_base64(expected.as_bytes())
.unwrap();
let encoded = matter_to_qb64(&matter);
assert_eq!(encoded, expected.as_bytes());
}
#[test]
fn blake3_256_digest_roundtrip() {
let raw = [0xCDu8; 32];
let ps = 1_usize;
let mut padded = vec![0u8; ps];
padded.extend_from_slice(&raw);
let payload_b64 = b64::URL_SAFE_NO_PAD.encode(&padded);
let expected = format!("E{}", &payload_b64[ps..]);
let matter = MatterBuilder::new()
.from_qualified_base64(expected.as_bytes())
.unwrap();
let encoded = matter_to_qb64(&matter);
assert_eq!(encoded, expected.as_bytes());
}
#[test]
fn short_number_roundtrip() {
let qb64 = b"MAAB";
let matter = MatterBuilder::new()
.from_qualified_base64(&qb64[..])
.unwrap();
let encoded = matter_to_qb64(&matter);
assert_eq!(&encoded, qb64);
}
#[test]
fn narrow_and_encode_verkey() {
use crate::core::matter::code::MatterCode;
use crate::core::matter::code::VerKeyCode;
let qb64 = b"DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
let untyped = MatterBuilder::new()
.from_qualified_base64(&qb64[..])
.unwrap();
assert_eq!(*untyped.code(), MatterCode::Ed25519);
let typed: crate::core::matter::Matter<'_, VerKeyCode> = untyped.narrow().unwrap();
let encoded = matter_to_qb64(&typed);
assert_eq!(&encoded, qb64);
}
}
mod element_groups {
use super::*;
use crate::core::indexer::IndexerBuilder;
use crate::core::indexer::code::IndexedSigCode;
use crate::core::primitives::Siger;
use crate::stream::group::types::CesrGroup;
use crate::stream::parse_group;
use bytes::Bytes;
fn build_siger(index: u32) -> Siger<'static> {
let indexer = IndexerBuilder::new()
.with_code(IndexedSigCode::Ed25519)
.with_index(index)
.unwrap()
.with_raw(&[0u8; 64])
.unwrap();
Siger::new(indexer)
}
fn build_prefixer_qb64() -> Vec<u8> {
let raw = [0xABu8; 32];
let ps = 1_usize;
let mut padded = vec![0u8; ps];
padded.extend_from_slice(&raw);
let payload_b64 = b64::URL_SAFE_NO_PAD.encode(&padded);
format!("D{}", &payload_b64[ps..]).into_bytes()
}
fn build_cigar_qb64() -> Vec<u8> {
let raw = [0xEFu8; 64];
let ps = 2_usize;
let mut padded = vec![0u8; ps];
padded.extend_from_slice(&raw);
let payload_b64 = b64::URL_SAFE_NO_PAD.encode(&padded);
format!("0B{}", &payload_b64[ps..]).into_bytes()
}
fn build_saider_qb64() -> Vec<u8> {
let raw = [0xCDu8; 32];
let ps = 1_usize;
let mut padded = vec![0u8; ps];
padded.extend_from_slice(&raw);
let payload_b64 = b64::URL_SAFE_NO_PAD.encode(&padded);
format!("E{}", &payload_b64[ps..]).into_bytes()
}
fn build_seqner_qb64() -> Vec<u8> {
b"MAAB".to_vec()
}
fn build_dater_qb64() -> Vec<u8> {
let raw = [0x11u8; 32];
let ps = 1_usize;
let mut padded = vec![0u8; ps];
padded.extend_from_slice(&raw);
let payload_b64 = b64::URL_SAFE_NO_PAD.encode(&padded);
format!("D{}", &payload_b64[ps..]).into_bytes()
}
#[test]
fn encode_controller_idx_sigs_roundtrip() {
let siger0 = build_siger(0);
let siger1 = build_siger(1);
let mut raw = Vec::new();
raw.extend_from_slice(siger0.to_qb64().as_bytes());
raw.extend_from_slice(siger1.to_qb64().as_bytes());
let group = ControllerIdxSigs::new(Bytes::from(raw), 2);
let encoded = encode_controller_idx_sigs_v1(&group).unwrap();
let (parsed, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
match parsed {
CesrGroup::ControllerIdxSigs(g) => assert_eq!(g.count() as usize, 2),
other => panic!("expected ControllerIdxSigs, got {other:?}"),
}
}
#[test]
fn encode_controller_idx_sigs_empty() {
let group = ControllerIdxSigs::new(Bytes::new(), 0);
let encoded = encode_controller_idx_sigs_v1(&group).unwrap();
let (parsed, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
match parsed {
CesrGroup::ControllerIdxSigs(g) => assert_eq!(g.count() as usize, 0),
other => panic!("expected ControllerIdxSigs, got {other:?}"),
}
}
#[test]
fn encode_witness_idx_sigs_roundtrip() {
let siger0 = build_siger(0);
let mut raw = Vec::new();
raw.extend_from_slice(siger0.to_qb64().as_bytes());
let group = WitnessIdxSigs::new(Bytes::from(raw), 1);
let encoded = encode_witness_idx_sigs_v1(&group).unwrap();
let (parsed, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
match parsed {
CesrGroup::WitnessIdxSigs(g) => assert_eq!(g.count() as usize, 1),
other => panic!("expected WitnessIdxSigs, got {other:?}"),
}
}
#[test]
fn encode_non_trans_receipt_couples_roundtrip() {
let mut raw = build_prefixer_qb64();
raw.extend_from_slice(&build_cigar_qb64());
let group = NonTransReceiptCouples::new(Bytes::from(raw), 1);
let encoded = encode_non_trans_receipt_couples_v1(&group).unwrap();
let (parsed, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
match parsed {
CesrGroup::NonTransReceiptCouples(g) => assert_eq!(g.count() as usize, 1),
other => panic!("expected NonTransReceiptCouples, got {other:?}"),
}
}
#[test]
fn encode_trans_receipt_quadruples_roundtrip() {
let mut raw = build_prefixer_qb64();
raw.extend_from_slice(&build_seqner_qb64());
raw.extend_from_slice(&build_saider_qb64());
raw.extend_from_slice(build_siger(0).to_qb64().as_bytes());
let group = TransReceiptQuadruples::new(Bytes::from(raw), 1);
let encoded = encode_trans_receipt_quadruples_v1(&group).unwrap();
let (parsed, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
match parsed {
CesrGroup::TransReceiptQuadruples(g) => assert_eq!(g.count() as usize, 1),
other => panic!("expected TransReceiptQuadruples, got {other:?}"),
}
}
#[test]
fn encode_first_seen_replay_couples_roundtrip() {
let mut raw = build_seqner_qb64();
raw.extend_from_slice(&build_dater_qb64());
let group = FirstSeenReplayCouples::new(Bytes::from(raw), 1);
let encoded = encode_first_seen_replay_couples_v1(&group).unwrap();
let (parsed, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
match parsed {
CesrGroup::FirstSeenReplayCouples(g) => assert_eq!(g.count() as usize, 1),
other => panic!("expected FirstSeenReplayCouples, got {other:?}"),
}
}
#[test]
fn encode_seal_source_couples_roundtrip() {
let mut raw = build_seqner_qb64();
raw.extend_from_slice(&build_saider_qb64());
let group = SealSourceCouples::new(Bytes::from(raw), 1);
let encoded = encode_seal_source_couples_v1(&group).unwrap();
let (parsed, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
match parsed {
CesrGroup::SealSourceCouples(g) => assert_eq!(g.count() as usize, 1),
other => panic!("expected SealSourceCouples, got {other:?}"),
}
}
#[test]
fn encode_seal_source_triples_roundtrip() {
let mut raw = build_prefixer_qb64();
raw.extend_from_slice(&build_seqner_qb64());
raw.extend_from_slice(&build_saider_qb64());
let group = SealSourceTriples::new(Bytes::from(raw), 1);
let encoded = encode_seal_source_triples_v1(&group).unwrap();
let (parsed, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
match parsed {
CesrGroup::SealSourceTriples(g) => assert_eq!(g.count() as usize, 1),
other => panic!("expected SealSourceTriples, got {other:?}"),
}
}
}
mod quadlet_groups {
use super::*;
use crate::core::indexer::IndexerBuilder;
use crate::core::indexer::code::IndexedSigCode;
use crate::stream::group::types::CesrGroup;
use crate::stream::parse_group;
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()
}
fn build_inner_group() -> Vec<u8> {
let mut inner = build_counter_qb64(CounterCodeV1::ControllerIdxSigs, 1);
inner.extend_from_slice(&build_siger_qb64(0));
inner
}
#[test]
fn encode_attachment_group_roundtrip() {
let inner = build_inner_group();
let encoded = encode_attachment_group_v1(&inner).unwrap();
let (group, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
assert!(matches!(group, CesrGroup::AttachmentGroup(_)));
}
#[test]
fn encode_generic_group_roundtrip() {
let inner = build_inner_group();
let encoded = encode_generic_group_v1(&inner).unwrap();
let (group, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
assert!(matches!(group, CesrGroup::GenericGroup(_)));
}
#[test]
fn encode_body_with_attachment_group_roundtrip() {
let inner = build_inner_group();
let encoded = encode_body_with_attachment_group_v1(&inner).unwrap();
let (group, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
assert!(matches!(group, CesrGroup::BodyWithAttachmentGroup(_)));
}
#[test]
fn encode_non_native_body_group_roundtrip() {
let inner = build_inner_group();
let encoded = encode_non_native_body_group_v1(&inner).unwrap();
let (group, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
assert!(matches!(group, CesrGroup::NonNativeBodyGroup(_)));
}
#[test]
fn encode_essr_payload_group_roundtrip() {
let inner = build_inner_group();
let encoded = encode_essr_payload_group_v1(&inner).unwrap();
let (group, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
assert!(matches!(group, CesrGroup::ESSRPayloadGroup(_)));
}
#[test]
fn encode_quadlet_group_rejects_non_multiple_of_4() {
let inner = vec![0u8; 5];
let result = encode_attachment_group_v1(&inner);
assert!(result.is_err());
}
#[test]
fn encode_quadlet_group_empty() {
let encoded = encode_attachment_group_v1(&[]).unwrap();
let (group, rest) = parse_group(&encoded).unwrap();
assert!(rest.is_empty());
match group {
CesrGroup::AttachmentGroup(ag) => assert_eq!(ag.0.quadlet_count(), 0),
other => panic!("expected AttachmentGroup, got {other:?}"),
}
}
}
mod version_string_v2 {
use super::*;
use crate::stream::message::parse_version_string_v2;
fn make_vs(
protocol: &str,
proto_minor: u16,
genus_minor: u16,
kind: ColdCode,
size: u32,
) -> VersionStringV2<'_> {
VersionStringV2 {
protocol,
proto_major: 2,
proto_minor,
genus_major: 2,
genus_minor,
kind,
size,
}
}
#[test]
fn encode_keri_json_size_zero() {
let vs = make_vs("KERI", 0, 0, ColdCode::Json, 0);
assert_eq!(encode_version_string_v2(&vs), b"KERICAACAAJSONAAAA.");
}
#[test]
fn encode_keri_json_size_65() {
let vs = make_vs("KERI", 0, 0, ColdCode::Json, 65);
assert_eq!(encode_version_string_v2(&vs), b"KERICAACAAJSONAABB.");
}
#[test]
fn encode_acdc_json_size_86() {
let vs = make_vs("ACDC", 0, 0, ColdCode::Json, 86);
assert_eq!(encode_version_string_v2(&vs), b"ACDCCAACAAJSONAABW.");
}
#[test]
fn encode_keri_mgpk_size_zero() {
let vs = make_vs("KERI", 0, 0, ColdCode::MessagePack, 0);
assert_eq!(encode_version_string_v2(&vs), b"KERICAACAAMGPKAAAA.");
}
#[test]
fn encode_keri_json_versioned() {
let vs = make_vs("KERI", 1, 1, ColdCode::Json, 0);
assert_eq!(encode_version_string_v2(&vs), b"KERICABCABJSONAAAA.");
}
#[test]
fn encode_length_is_19() {
let vs = make_vs("KERI", 0, 0, ColdCode::Json, 0);
assert_eq!(encode_version_string_v2(&vs).len(), 19);
}
#[test]
fn encode_cbor() {
let vs = make_vs("KERI", 0, 0, ColdCode::Cbor, 0);
assert_eq!(encode_version_string_v2(&vs), b"KERICAACAACBORAAAA.");
}
#[test]
fn encode_cesr() {
let vs = make_vs("KERI", 0, 0, ColdCode::CesrBase64, 0);
assert_eq!(encode_version_string_v2(&vs), b"KERICAACAACESRAAAA.");
}
#[test]
fn roundtrip_keri_json_size_zero() {
let vs = make_vs("KERI", 0, 0, ColdCode::Json, 0);
let encoded = encode_version_string_v2(&vs);
let (parsed, rest) = parse_version_string_v2(&encoded).unwrap();
assert_eq!(parsed.protocol, "KERI");
assert_eq!(parsed.proto_major, 2);
assert_eq!(parsed.proto_minor, 0);
assert_eq!(parsed.genus_major, 2);
assert_eq!(parsed.genus_minor, 0);
assert_eq!(parsed.kind, ColdCode::Json);
assert_eq!(parsed.size, 0);
assert!(rest.is_empty());
}
#[test]
fn roundtrip_acdc_json_size_86() {
let vs = make_vs("ACDC", 0, 0, ColdCode::Json, 86);
let encoded = encode_version_string_v2(&vs);
let (parsed, _) = parse_version_string_v2(&encoded).unwrap();
assert_eq!(parsed.protocol, "ACDC");
assert_eq!(parsed.size, 86);
}
#[test]
fn roundtrip_versioned() {
let vs = make_vs("KERI", 1, 1, ColdCode::Json, 0);
let encoded = encode_version_string_v2(&vs);
let (parsed, _) = parse_version_string_v2(&encoded).unwrap();
assert_eq!(parsed.proto_minor, 1);
assert_eq!(parsed.genus_minor, 1);
}
#[test]
fn roundtrip_max_size() {
let max_4_b64 = 16_777_215_u32;
let vs = make_vs("KERI", 0, 0, ColdCode::Json, max_4_b64);
let encoded = encode_version_string_v2(&vs);
let (parsed, _) = parse_version_string_v2(&encoded).unwrap();
assert_eq!(parsed.size, max_4_b64);
}
}
mod encode_cesr {
use crate::core::indexer::IndexerBuilder;
use crate::core::indexer::code::IndexedSigCode;
use crate::core::primitives::Siger;
use bytes::BytesMut;
use super::*;
use crate::stream::parse_group;
use crate::stream::parse_group_v2;
use crate::stream::version::CesrEncode;
use crate::stream::version::V1;
use crate::stream::version::V2;
fn build_siger_raw() -> Vec<u8> {
let indexer = IndexerBuilder::new()
.with_code(IndexedSigCode::Ed25519)
.with_index(0)
.unwrap()
.with_raw(&[0u8; 64])
.unwrap();
Siger::new(indexer).to_qb64().into_bytes()
}
#[test]
fn encode_cesr_v1_element_roundtrips() {
let raw = build_siger_raw();
let group = ControllerIdxSigs::new(bytes::Bytes::from(raw), 1);
let mut dst = BytesMut::new();
CesrEncode::<V1>::encode_cesr(&group, &mut dst).unwrap();
let (parsed, rest) = parse_group(&dst).unwrap();
assert!(rest.is_empty());
assert!(matches!(parsed, CesrGroup::ControllerIdxSigs(g) if g.count() == 1));
}
#[test]
fn encode_cesr_v2_element_roundtrips() {
let raw = build_siger_raw();
let group = ControllerIdxSigs::new(bytes::Bytes::from(raw), 1);
let mut dst = BytesMut::new();
CesrEncode::<V2>::encode_cesr(&group, &mut dst).unwrap();
let (parsed, rest) = parse_group_v2(&dst).unwrap();
assert!(rest.is_empty());
assert!(matches!(parsed, CesrGroup::ControllerIdxSigs(g) if g.count() == 1));
}
#[test]
fn encode_cesr_v1_and_legacy_produce_identical_output() {
let raw = build_siger_raw();
let group = ControllerIdxSigs::new(bytes::Bytes::from(raw), 1);
let legacy = encode_controller_idx_sigs_v1(&group).unwrap();
let mut trait_dst = BytesMut::new();
CesrEncode::<V1>::encode_cesr(&group, &mut trait_dst).unwrap();
assert_eq!(&trait_dst[..], &legacy[..]);
}
#[test]
fn encode_cesr_v2_only_type_works() {
let raw = build_siger_raw();
let group = DigestSealSingles::new(bytes::Bytes::from(raw), 1);
let mut dst = BytesMut::new();
CesrEncode::<V2>::encode_cesr(&group, &mut dst).unwrap();
assert!(!dst.is_empty());
}
#[test]
fn encode_cesr_v1_enum_rejects_v2_only() {
let qg = crate::stream::group::QuadletGroup::new(
bytes::Bytes::from_static(b"ABCD"),
crate::stream::group::parse_group_inner_v2,
);
let group = CesrGroup::DatagramSegmentGroup(DatagramSegmentGroup(qg));
let mut dst = BytesMut::new();
let result = CesrEncode::<V1>::encode_cesr(&group, &mut dst);
assert!(result.is_err());
}
#[test]
fn encode_cesr_v2_enum_accepts_all() {
let raw = build_siger_raw();
let group =
CesrGroup::ControllerIdxSigs(ControllerIdxSigs::new(bytes::Bytes::from(raw), 1));
let mut dst = BytesMut::new();
CesrEncode::<V2>::encode_cesr(&group, &mut dst).unwrap();
let (parsed, rest) = parse_group_v2(&dst).unwrap();
assert!(rest.is_empty());
assert!(matches!(parsed, CesrGroup::ControllerIdxSigs(g) if g.count() == 1));
}
#[test]
fn encode_cesr_quadlet_v1_roundtrips() {
let mut inner_raw = Vec::new();
inner_raw.extend_from_slice(
&encode_counter_v1(CounterCodeV1::ControllerIdxSigs, 1).unwrap(),
);
inner_raw.extend_from_slice(&build_siger_raw());
let qg = crate::stream::group::QuadletGroup::new(
bytes::Bytes::from(inner_raw),
crate::stream::group::parse_group_inner,
);
let group = AttachmentGroup(qg);
let mut dst = BytesMut::new();
CesrEncode::<V1>::encode_cesr(&group, &mut dst).unwrap();
let (parsed, rest) = parse_group(&dst).unwrap();
assert!(rest.is_empty());
assert!(matches!(parsed, CesrGroup::AttachmentGroup(_)));
}
}
}