#![allow(missing_docs)]
use wolfcose::{
from_slice, from_slice_detailed, to_vec, Algorithm, AlgorithmClass, ByteBuf, ByteStr,
CborDeserialize, CborItemReader, CborSerialize, CborSerializer, CborValue, CoseKeyBuilder,
CoseKeyView, CoseSign1Message, Error, HeaderLabel, HeaderMap, HeaderValue, PayloadMode,
ProtectedHeader, SymmetricKey,
};
#[derive(Debug, Eq, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(rename_all = "snake_case")]
struct DerivedOptions {
device_id: u32,
#[cbor(skip_serializing_if = "Option::is_none", default)]
display_name: Option<String>,
}
#[derive(Debug, Eq, PartialEq, CborSerialize, CborDeserialize)]
#[cbor(transparent)]
struct TransparentId(u32);
#[derive(Debug, Eq, PartialEq, CborSerialize, CborDeserialize)]
enum Event {
Started,
Reading(u32, String),
#[cbor(rename = "device_state")]
State {
online: bool,
},
}
mod plus_one {
pub fn serialize(
value: &u8,
serializer: &mut wolfcose::CborSerializer<'_>,
) -> wolfcose::Result<()> {
(value + 1).serialize(serializer)
}
pub fn deserialize(deserializer: &mut wolfcose::CborDeserializer<'_>) -> wolfcose::Result<u8> {
Ok(u8::deserialize(deserializer)? - 1)
}
use wolfcose::{CborDeserialize, CborSerialize};
}
#[derive(Debug, Eq, PartialEq, CborSerialize, CborDeserialize)]
struct WithModule {
#[cbor(with = "plus_one")]
value: u8,
}
#[test]
fn algorithm_metadata_and_key_builder_work() {
assert_eq!(Algorithm::A128GCM.class(), AlgorithmClass::Encryption);
assert!(Algorithm::ES256.is_signing());
assert!(Algorithm::HMAC256.is_mac());
assert!(Algorithm::DIRECT.is_key_management());
assert_eq!(Algorithm::A128GCM.key_bits_hint(), Some(128));
assert_eq!(Algorithm::A128GCM.iv_len_hint(), Some(12));
let key = CoseKeyBuilder::symmetric([0u8; 16])
.algorithm(Algorithm::A128GCM)
.kid(b"k1")
.has_private(true)
.build()
.unwrap();
let view = CoseKeyView::from_key(&key);
assert_eq!(view.algorithm, Algorithm::A128GCM);
assert_eq!(view.kid, b"k1");
assert_eq!(view.symmetric_len, Some(16));
assert!(view.has_private);
assert_eq!(
CoseKeyBuilder::symmetric([0u8; 15])
.algorithm(Algorithm::A128GCM)
.build()
.err(),
Some(Error::CoseKeyType)
);
let key = SymmetricKey::new([0u8; 32])
.with_algorithm(Algorithm::A256GCM)
.with_kid(b"k2")
.into_cose_key()
.unwrap();
assert_eq!(CoseKeyView::from_key(&key).symmetric_len, Some(32));
}
#[test]
fn key_debug_output_redacts_symmetric_material() {
let key = SymmetricKey::new([1u8, 2, 3, 4])
.with_algorithm(Algorithm::A128GCM)
.with_kid(b"kid");
let debug = format!("{key:?}");
assert!(debug.contains("material_len"));
assert!(!debug.contains("[1, 2, 3, 4]"));
let builder = CoseKeyBuilder::symmetric([5u8, 6, 7, 8]).algorithm(Algorithm::A128GCM);
let debug = format!("{builder:?}");
assert!(debug.contains("symmetric_len"));
assert!(!debug.contains("[5, 6, 7, 8]"));
}
#[test]
fn header_map_and_sign1_message_parse() {
let mut protected = HeaderMap::new();
protected.insert(
HeaderLabel::Algorithm,
HeaderValue::Algorithm(Algorithm::HMAC256),
);
let protected_bytes = to_vec(&protected).unwrap();
let mut unprotected = HeaderMap::new();
unprotected.insert(HeaderLabel::Kid, HeaderValue::Bytes(b"kid".to_vec()));
unprotected.insert(
HeaderLabel::Text("vendor".to_owned()),
HeaderValue::Text("wolf".to_owned()),
);
let mut out = [0u8; 256];
let mut serializer = CborSerializer::new(&mut out);
serializer
.tag(wolfcose::raw::WOLFCOSE_TAG_SIGN1 as u64)
.unwrap();
serializer.array(4).unwrap();
ByteStr(&protected_bytes)
.serialize(&mut serializer)
.unwrap();
unprotected.serialize(&mut serializer).unwrap();
Option::<u8>::None.serialize(&mut serializer).unwrap();
ByteStr(b"signature").serialize(&mut serializer).unwrap();
let message = CoseSign1Message::parse(serializer.as_written()).unwrap();
assert_eq!(message.protected().algorithm(), Some(Algorithm::HMAC256));
assert_eq!(message.unprotected().kid(), Some(&b"kid"[..]));
assert!(!message.payload_attached());
assert_eq!(message.raw(), serializer.as_written());
assert_eq!(
ProtectedHeader::from_bstr(&protected_bytes)
.unwrap()
.0
.algorithm(),
Some(Algorithm::HMAC256)
);
}
#[test]
fn serializer_helpers_stream_and_detailed_errors_work() {
let mut out = [0u8; 128];
let mut serializer = CborSerializer::new(&mut out);
serializer.sequence([1u8, 2, 3]).unwrap();
let decoded: Vec<u8> = from_slice(serializer.as_written()).unwrap();
assert_eq!(decoded, vec![1, 2, 3]);
let mut map_out = [0u8; 128];
let mut serializer = CborSerializer::new(&mut map_out);
serializer.map_entries([("a", 1u8), ("b", 2u8)]).unwrap();
let value: CborValue = from_slice(serializer.as_written()).unwrap();
assert!(matches!(value, CborValue::Map(_)));
let encoded = to_vec(&(1u8, 2u8)).unwrap();
let mut reader = CborItemReader::new(&encoded);
assert!(reader.skip_next().unwrap());
assert!(reader.is_finished());
let mut trailing = to_vec(&1u8).unwrap();
trailing.push(0);
let err = from_slice_detailed::<u8>(&trailing).unwrap_err();
assert_eq!(err.error, Error::CborMalformed);
}
#[test]
fn derive_macro_enhancements_round_trip() {
let options = DerivedOptions {
device_id: 7,
display_name: None,
};
let value: CborValue = from_slice(&to_vec(&options).unwrap()).unwrap();
let CborValue::Map(entries) = value else {
panic!("expected map");
};
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].0, CborValue::Text("device_id".to_owned()));
assert_eq!(
from_slice::<DerivedOptions>(&to_vec(&options).unwrap()).unwrap(),
options
);
let transparent = TransparentId(9);
assert_eq!(
from_slice::<TransparentId>(&to_vec(&transparent).unwrap()).unwrap(),
transparent
);
for event in [
Event::Started,
Event::Reading(3, "temp".to_owned()),
Event::State { online: true },
] {
assert_eq!(
from_slice::<Event>(&to_vec(&event).unwrap()).unwrap(),
event
);
}
let with_module = WithModule { value: 4 };
assert_eq!(
from_slice::<WithModule>(&to_vec(&with_module).unwrap()).unwrap(),
with_module
);
let encoded: CborValue = from_slice(&to_vec(&with_module).unwrap()).unwrap();
assert!(format!("{encoded:?}").contains('5'));
}
#[test]
fn payload_mode_remains_explicit() {
assert_eq!(PayloadMode::Attached(b"a"), PayloadMode::Attached(b"a"));
assert_ne!(PayloadMode::Attached(b"a"), PayloadMode::Detached(b"a"));
assert_eq!(ByteBuf(b"x".to_vec()).into_vec(), b"x");
}