use std::collections::btree_map;
use std::ops::{Deref, DerefMut};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::{iana, CoseMap, Error, Label, Value};
#[derive(Clone, Debug, Default, PartialEq)]
pub struct Header(CoseMap);
impl Header {
pub fn new() -> Self {
Header(CoseMap::new())
}
pub fn from_slice(data: &[u8]) -> Result<Self, Error> {
Ok(Header(CoseMap::from_slice(data)?))
}
pub fn to_vec(&self) -> Result<Vec<u8>, Error> {
self.0.to_vec()
}
pub fn as_map(&self) -> &CoseMap {
&self.0
}
pub fn as_mut_map(&mut self) -> &mut CoseMap {
&mut self.0
}
pub fn into_map(self) -> CoseMap {
self.0
}
pub fn iter(&self) -> btree_map::Iter<'_, Label, Value> {
self.0.iter()
}
pub fn alg(&self) -> Result<Option<Label>, Error> {
self.0.get_label(iana::HeaderParameterAlg)
}
pub fn set_alg(&mut self, alg: impl Into<Label>) -> &mut Self {
self.0
.insert(iana::HeaderParameterAlg, Value::from(alg.into()));
self
}
pub fn kid(&self) -> Result<Option<&[u8]>, Error> {
self.0.get_bytes(iana::HeaderParameterKid)
}
pub fn set_kid(&mut self, kid: impl Into<Vec<u8>>) -> &mut Self {
self.0.insert(iana::HeaderParameterKid, kid.into());
self
}
pub fn content_type(&self) -> Result<Option<Label>, Error> {
self.0.get_label(iana::HeaderParameterContentType)
}
pub fn set_content_type(&mut self, content_type: impl Into<Label>) -> &mut Self {
self.0.insert(
iana::HeaderParameterContentType,
Value::from(content_type.into()),
);
self
}
pub fn iv(&self) -> Result<Option<&[u8]>, Error> {
self.0.get_bytes(iana::HeaderParameterIV)
}
pub fn set_iv(&mut self, iv: impl Into<Vec<u8>>) -> &mut Self {
self.0.insert(iana::HeaderParameterIV, iv.into());
self
}
pub fn partial_iv(&self) -> Result<Option<&[u8]>, Error> {
self.0.get_bytes(iana::HeaderParameterPartialIV)
}
pub fn set_partial_iv(&mut self, partial_iv: impl Into<Vec<u8>>) -> &mut Self {
self.0
.insert(iana::HeaderParameterPartialIV, partial_iv.into());
self
}
pub fn crit(&self) -> Result<Option<Vec<Label>>, Error> {
labels_from_value(self.0.get(iana::HeaderParameterCrit))
}
pub fn set_crit<I, L>(&mut self, labels: I) -> &mut Self
where
I: IntoIterator<Item = L>,
L: Into<Label>,
{
let labels = labels
.into_iter()
.map(|label| Value::from(label.into()))
.collect::<Vec<_>>();
self.0.insert(iana::HeaderParameterCrit, labels);
self
}
pub fn ensure_crit_understood(&self, understood: &[Label]) -> Result<(), Error> {
let Some(crit) = self.crit()? else {
return Ok(());
};
for label in crit {
if is_understood_header(&label) || understood.contains(&label) {
continue;
}
return Err(Error::Custom(format!(
"unsupported critical header parameter {label}"
)));
}
Ok(())
}
}
pub fn is_understood_header(label: &Label) -> bool {
matches!(
label,
Label::Int(
iana::HeaderParameterAlg
| iana::HeaderParameterCrit
| iana::HeaderParameterContentType
| iana::HeaderParameterKid
| iana::HeaderParameterIV
| iana::HeaderParameterPartialIV
)
)
}
impl From<CoseMap> for Header {
fn from(value: CoseMap) -> Self {
Header(value)
}
}
impl From<Header> for CoseMap {
fn from(value: Header) -> Self {
value.0
}
}
impl FromIterator<(Label, Value)> for Header {
fn from_iter<T: IntoIterator<Item = (Label, Value)>>(iter: T) -> Self {
Header(CoseMap::from_iter(iter))
}
}
impl Deref for Header {
type Target = CoseMap;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Header {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Serialize for Header {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for Header {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
CoseMap::deserialize(deserializer).map(Header)
}
}
pub(crate) fn encode_protected(header: &Header) -> Result<Vec<u8>, Error> {
if header.is_empty() {
Ok(Vec::new())
} else {
Ok(cbor2::to_canonical_vec(header)?)
}
}
pub(crate) fn decode_protected(data: &[u8]) -> Result<Header, Error> {
if data.is_empty() {
Ok(Header::new())
} else {
Header::from_slice(data)
}
}
pub(crate) fn validate_header_buckets(
protected: &Header,
unprotected: &Header,
) -> Result<(), Error> {
for (label, _) in protected.iter() {
if unprotected.contains_key(label.clone()) {
return Err(Error::Custom(format!(
"header label {label} appears in both protected and unprotected buckets"
)));
}
}
if unprotected.contains_key(iana::HeaderParameterCrit) {
return Err(Error::Custom(
"crit header parameter must be protected".into(),
));
}
if let Some(crit) = protected.crit()? {
if crit.is_empty() {
return Err(Error::Custom(
"crit header parameter must not be empty".into(),
));
}
for label in crit {
if !protected.contains_key(label.clone()) {
return Err(Error::Custom(format!(
"crit references absent protected header label {label}"
)));
}
}
}
Ok(())
}
fn labels_from_value(value: Option<&Value>) -> Result<Option<Vec<Label>>, Error> {
let Some(Value::Array(values)) = value else {
return match value {
None => Ok(None),
Some(_) => Err(Error::UnexpectedType(
"crit must be an array of labels".into(),
)),
};
};
let mut labels = Vec::with_capacity(values.len());
for value in values {
labels.push(label_from_value(value)?);
}
Ok(Some(labels))
}
fn label_from_value(value: &Value) -> Result<Label, Error> {
match value {
Value::Integer(i) => i64::try_from(*i)
.map(Label::Int)
.map_err(|_| Error::UnexpectedType("integer label out of range".into())),
Value::Text(s) => Ok(Label::Text(s.clone())),
_ => Err(Error::UnexpectedType(
"expected an integer or text string label".into(),
)),
}
}