use crate::cbor;
use crate::cbor::value::Value;
use crate::error::DecodeError;
use crate::nostd_prelude::*;
use crate::types::tags::{TAG_LEGACY_SIGNED, TAG_LEGACY_TOP};
use core::borrow::Borrow;
pub fn peel_tcg_wrappers(bytes: &[u8]) -> Result<PeelOutcome<'_>, DecodeError> {
if !starts_with_legacy_tag(bytes) {
return Ok(PeelOutcome::Unchanged(bytes));
}
let mut v: Value = cbor::decode(bytes)
.map_err(|e| DecodeError::Deserialization(format!("peel: cannot decode CBOR: {}", e)))?;
let mut peeled = false;
loop {
match v {
Value::Tag(TAG_LEGACY_TOP, inner) | Value::Tag(TAG_LEGACY_SIGNED, inner) => {
v = *inner;
peeled = true;
}
other => {
v = other;
break;
}
}
}
if !peeled {
return Ok(PeelOutcome::Unchanged(bytes));
}
let out = cbor::encode(&v).map_err(|e| {
DecodeError::InvalidStructure(format!("peel: failed to re-encode inner CBOR: {}", e))
})?;
Ok(PeelOutcome::Peeled(out))
}
#[derive(Debug)]
pub enum PeelOutcome<'a> {
Unchanged(&'a [u8]),
Peeled(Vec<u8>),
}
impl<'a> PeelOutcome<'a> {
pub fn as_bytes(&self) -> &[u8] {
match self {
PeelOutcome::Unchanged(b) => b,
PeelOutcome::Peeled(v) => v.as_slice(),
}
}
pub fn was_peeled(&self) -> bool {
matches!(self, PeelOutcome::Peeled(_))
}
}
impl<'a> Borrow<[u8]> for PeelOutcome<'a> {
fn borrow(&self) -> &[u8] {
self.as_bytes()
}
}
fn starts_with_legacy_tag(bytes: &[u8]) -> bool {
matches!(bytes, [0xD9, 0x01, 0xF4, ..] | [0xD9, 0x01, 0xF6, ..])
}
pub fn wrap_bare_corim_map<'a>(bytes: &'a [u8]) -> WrapOutcome<'a> {
if bytes.is_empty() {
return WrapOutcome::Unchanged(bytes);
}
if bytes.len() >= 3 && bytes[0] == 0xD9 && bytes[1] == 0x01 && bytes[2] == 0xF5 {
return WrapOutcome::Unchanged(bytes);
}
let first = bytes[0];
if (0xA0..=0xBB).contains(&first) {
let mut out = Vec::with_capacity(3 + bytes.len());
out.extend_from_slice(&[0xD9, 0x01, 0xF5]);
out.extend_from_slice(bytes);
return WrapOutcome::Wrapped(out);
}
WrapOutcome::Unchanged(bytes)
}
#[derive(Debug)]
pub enum WrapOutcome<'a> {
Unchanged(&'a [u8]),
Wrapped(Vec<u8>),
}
impl<'a> WrapOutcome<'a> {
pub fn as_bytes(&self) -> &[u8] {
match self {
WrapOutcome::Unchanged(b) => b,
WrapOutcome::Wrapped(v) => v.as_slice(),
}
}
pub fn was_wrapped(&self) -> bool {
matches!(self, WrapOutcome::Wrapped(_))
}
}
impl<'a> Borrow<[u8]> for WrapOutcome<'a> {
fn borrow(&self) -> &[u8] {
self.as_bytes()
}
}
pub fn decode_comid_from_tcg_bstr(
bytes: &[u8],
) -> Result<crate::types::comid::ComidTag, DecodeError> {
use crate::types::tags::TAG_COMID;
let v: Value = cbor::decode(bytes)
.map_err(|e| DecodeError::Deserialization(format!("decode_comid_from_tcg_bstr: {}", e)))?;
let map_value = match v {
Value::Tag(TAG_COMID, boxed) => match *boxed {
map @ Value::Map(_) => map,
other => {
return Err(DecodeError::InvalidStructure(format!(
"expected #6.{}(map), got #6.{}({})",
TAG_COMID,
TAG_COMID,
value_kind(&other),
)));
}
},
map @ Value::Map(_) => map,
Value::Tag(t, _) => {
return Err(DecodeError::InvalidStructure(format!(
"expected bare map or #6.{}(map), got unrelated tag #6.{}",
TAG_COMID, t
)));
}
other => {
return Err(DecodeError::InvalidStructure(format!(
"expected bare map or #6.{}(map), got {}",
TAG_COMID,
value_kind(&other),
)));
}
};
let map_bytes = cbor::encode(&map_value).map_err(|e| {
DecodeError::InvalidStructure(format!(
"decode_comid_from_tcg_bstr: re-encode failed: {}",
e
))
})?;
cbor::decode(&map_bytes).map_err(|e| {
DecodeError::Deserialization(format!(
"decode_comid_from_tcg_bstr: ComidTag decode: {}",
e
))
})
}
fn value_kind(v: &Value) -> &'static str {
match v {
Value::Integer(_) => "integer",
Value::Bytes(_) => "bytes",
Value::Text(_) => "text",
Value::Array(_) => "array",
Value::Map(_) => "map",
Value::Tag(_, _) => "tag",
Value::Bool(_) => "bool",
Value::Null => "null",
Value::Float(_) => "float",
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cbor::value::Tagged;
use crate::types::tags::{TAG_CORIM, TAG_SIGNED_CORIM};
#[test]
fn no_legacy_wrapper_returns_unchanged() {
let bytes = cbor::encode(&Tagged::new(TAG_CORIM, Value::Map(vec![]))).unwrap();
let out = peel_tcg_wrappers(&bytes).unwrap();
assert!(!out.was_peeled());
assert_eq!(out.as_bytes(), bytes.as_slice());
}
#[test]
fn peels_500_wrapper() {
let inner = Value::Tag(TAG_CORIM, Box::new(Value::Map(vec![])));
let bytes = cbor::encode(&Tagged::new(TAG_LEGACY_TOP, inner.clone())).unwrap();
let out = peel_tcg_wrappers(&bytes).unwrap();
assert!(out.was_peeled());
let expected = cbor::encode(&inner).unwrap();
assert_eq!(out.as_bytes(), expected.as_slice());
}
#[test]
fn peels_502_wrapper() {
let cose = Value::Tag(TAG_SIGNED_CORIM, Box::new(Value::Array(vec![])));
let bytes = cbor::encode(&Tagged::new(TAG_LEGACY_SIGNED, cose.clone())).unwrap();
let out = peel_tcg_wrappers(&bytes).unwrap();
assert!(out.was_peeled());
let expected = cbor::encode(&cose).unwrap();
assert_eq!(out.as_bytes(), expected.as_slice());
}
#[test]
fn peels_nested_500_502_wrappers() {
let cose = Value::Tag(TAG_SIGNED_CORIM, Box::new(Value::Array(vec![])));
let inner502 = Value::Tag(TAG_LEGACY_SIGNED, Box::new(cose.clone()));
let bytes = cbor::encode(&Tagged::new(TAG_LEGACY_TOP, inner502)).unwrap();
let out = peel_tcg_wrappers(&bytes).unwrap();
assert!(out.was_peeled());
let expected = cbor::encode(&cose).unwrap();
assert_eq!(out.as_bytes(), expected.as_slice());
}
#[test]
fn malformed_cbor_after_legacy_marker_returns_decode_error() {
let garbage = [0xD9, 0x01, 0xF4, 0xFF, 0xFF, 0xFF];
let err = peel_tcg_wrappers(&garbage).unwrap_err();
match err {
DecodeError::Deserialization(msg) => assert!(msg.starts_with("peel:")),
other => panic!("expected Deserialization, got {:?}", other),
}
}
#[test]
fn wrap_passes_through_already_tagged_corim_map() {
let bytes = cbor::encode(&Tagged::new(TAG_CORIM, Value::Map(vec![]))).unwrap();
let out = wrap_bare_corim_map(&bytes);
assert!(!out.was_wrapped());
assert_eq!(out.as_bytes(), bytes.as_slice());
}
#[test]
fn wrap_prefixes_bare_small_map() {
let bytes = cbor::encode(&Value::Map(vec![])).unwrap();
assert_eq!(bytes[0], 0xA0);
let out = wrap_bare_corim_map(&bytes);
assert!(out.was_wrapped());
let tagged: Tagged<Value> = cbor::decode(out.as_bytes()).unwrap();
assert_eq!(tagged.tag, TAG_CORIM);
assert_eq!(tagged.value, Value::Map(vec![]));
}
#[test]
fn wrap_prefixes_bare_larger_map() {
let entries: Vec<(Value, Value)> = (0..24)
.map(|i| (Value::Integer(i as i128), Value::Integer(0)))
.collect();
let bytes = cbor::encode(&Value::Map(entries.clone())).unwrap();
assert_eq!(bytes[0], 0xB8);
let out = wrap_bare_corim_map(&bytes);
assert!(out.was_wrapped());
let tagged: Tagged<Value> = cbor::decode(out.as_bytes()).unwrap();
assert_eq!(tagged.tag, TAG_CORIM);
assert_eq!(tagged.value, Value::Map(entries));
}
#[test]
fn wrap_passes_through_non_map() {
let bytes = cbor::encode(&Value::Integer(42)).unwrap();
let out = wrap_bare_corim_map(&bytes);
assert!(!out.was_wrapped());
assert_eq!(out.as_bytes(), bytes.as_slice());
}
#[test]
fn wrap_passes_through_other_tags() {
let bytes = cbor::encode(&Tagged::new(TAG_SIGNED_CORIM, Value::Array(vec![]))).unwrap();
let out = wrap_bare_corim_map(&bytes);
assert!(!out.was_wrapped());
assert_eq!(out.as_bytes(), bytes.as_slice());
}
#[test]
fn wrap_passes_through_empty_input() {
let out = wrap_bare_corim_map(&[]);
assert!(!out.was_wrapped());
assert_eq!(out.as_bytes(), &[] as &[u8]);
}
fn minimal_comid_map_value() -> Value {
let tag_identity = Value::Map(vec![(Value::Integer(0), Value::Text("test-id".into()))]);
let env = Value::Map(vec![(
Value::Integer(0), Value::Map(vec![(Value::Integer(1), Value::Text("acme".into()))]), )]);
let meas = Value::Map(vec![(
Value::Integer(1), Value::Map(vec![(Value::Integer(11), Value::Text("fw".into()))]),
)]);
let ref_triple = Value::Array(vec![env, Value::Array(vec![meas])]);
let triples = Value::Map(vec![(
Value::Integer(0), Value::Array(vec![ref_triple]),
)]);
Value::Map(vec![
(Value::Integer(1), tag_identity),
(Value::Integer(4), triples),
])
}
#[test]
fn decode_comid_accepts_tag_then_map_shape() {
let inner = minimal_comid_map_value();
let bytes =
cbor::encode(&Value::Tag(crate::types::tags::TAG_COMID, Box::new(inner))).unwrap();
let comid = decode_comid_from_tcg_bstr(&bytes).expect("must decode");
match &comid.tag_identity.tag_id {
crate::types::common::TagIdChoice::Text(s) => assert_eq!(s, "test-id"),
other => panic!("unexpected tag-id: {:?}", other),
}
}
#[test]
fn decode_comid_accepts_bare_map_shape() {
let bytes = cbor::encode(&minimal_comid_map_value()).unwrap();
let comid = decode_comid_from_tcg_bstr(&bytes).expect("must decode");
match &comid.tag_identity.tag_id {
crate::types::common::TagIdChoice::Text(s) => assert_eq!(s, "test-id"),
other => panic!("unexpected tag-id: {:?}", other),
}
}
#[test]
fn decode_comid_rejects_unrelated_tag() {
let bytes = cbor::encode(&Value::Tag(999, Box::new(minimal_comid_map_value()))).unwrap();
let err = decode_comid_from_tcg_bstr(&bytes).unwrap_err();
match err {
DecodeError::InvalidStructure(msg) => {
assert!(msg.contains("unrelated tag"), "got: {}", msg);
assert!(msg.contains("999"), "got: {}", msg);
}
other => panic!("expected InvalidStructure, got {:?}", other),
}
}
#[test]
fn decode_comid_rejects_non_map_value() {
let bytes = cbor::encode(&Value::Integer(42)).unwrap();
let err = decode_comid_from_tcg_bstr(&bytes).unwrap_err();
match err {
DecodeError::InvalidStructure(msg) => {
assert!(msg.contains("integer"), "got: {}", msg);
}
other => panic!("expected InvalidStructure, got {:?}", other),
}
}
#[test]
fn decode_comid_rejects_invalid_cbor() {
let garbage = [0xFF, 0xFE, 0xFD];
let err = decode_comid_from_tcg_bstr(&garbage).unwrap_err();
match err {
DecodeError::Deserialization(msg) => {
assert!(
msg.starts_with("decode_comid_from_tcg_bstr:"),
"got: {}",
msg
);
}
other => panic!("expected Deserialization, got {:?}", other),
}
}
}