use crate::{
cbor::value::Value,
common::AsCborValue,
iana,
iana::{EnumI64, WithPrivateRange},
util::{cbor_type_error, ValueTryAs},
CoseError,
};
use alloc::{collections::BTreeSet, string::String, vec::Vec};
use core::convert::TryInto;
#[cfg(test)]
mod tests;
#[derive(Clone, Debug, PartialEq)]
pub enum Timestamp {
WholeSeconds(i64),
FractionalSeconds(f64),
}
impl AsCborValue for Timestamp {
fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
match value {
Value::Integer(i) => Ok(Timestamp::WholeSeconds(i.try_into()?)),
Value::Float(f) => Ok(Timestamp::FractionalSeconds(f)),
_ => cbor_type_error(&value, "int/float"),
}
}
fn to_cbor_value(self) -> Result<Value, CoseError> {
Ok(match self {
Timestamp::WholeSeconds(t) => Value::Integer(t.into()),
Timestamp::FractionalSeconds(f) => Value::Float(f),
})
}
}
pub type ClaimName = crate::RegisteredLabelWithPrivate<iana::CwtClaimName>;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ClaimsSet {
pub issuer: Option<String>,
pub subject: Option<String>,
pub audience: Option<String>,
pub expiration_time: Option<Timestamp>,
pub not_before: Option<Timestamp>,
pub issued_at: Option<Timestamp>,
pub cwt_id: Option<Vec<u8>>,
pub rest: Vec<(ClaimName, Value)>,
}
impl crate::CborSerializable for ClaimsSet {}
const ISS: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Iss);
const SUB: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Sub);
const AUD: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Aud);
const EXP: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Exp);
const NBF: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Nbf);
const IAT: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Iat);
const CTI: ClaimName = ClaimName::Assigned(iana::CwtClaimName::Cti);
impl AsCborValue for ClaimsSet {
fn from_cbor_value(value: Value) -> Result<Self, CoseError> {
let m = match value {
Value::Map(m) => m,
v => return cbor_type_error(&v, "map"),
};
let mut claims = Self::default();
let mut seen = BTreeSet::new();
for (n, value) in m.into_iter() {
let name = ClaimName::from_cbor_value(n)?;
if seen.contains(&name) {
return Err(CoseError::DuplicateMapKey);
}
seen.insert(name.clone());
match name {
x if x == ISS => claims.issuer = Some(value.try_as_string()?),
x if x == SUB => claims.subject = Some(value.try_as_string()?),
x if x == AUD => claims.audience = Some(value.try_as_string()?),
x if x == EXP => claims.expiration_time = Some(Timestamp::from_cbor_value(value)?),
x if x == NBF => claims.not_before = Some(Timestamp::from_cbor_value(value)?),
x if x == IAT => claims.issued_at = Some(Timestamp::from_cbor_value(value)?),
x if x == CTI => claims.cwt_id = Some(value.try_as_bytes()?),
name => claims.rest.push((name, value)),
}
}
Ok(claims)
}
fn to_cbor_value(self) -> Result<Value, CoseError> {
let mut map = Vec::new();
if let Some(iss) = self.issuer {
map.push((ISS.to_cbor_value()?, Value::Text(iss)));
}
if let Some(sub) = self.subject {
map.push((SUB.to_cbor_value()?, Value::Text(sub)));
}
if let Some(aud) = self.audience {
map.push((AUD.to_cbor_value()?, Value::Text(aud)));
}
if let Some(exp) = self.expiration_time {
map.push((EXP.to_cbor_value()?, exp.to_cbor_value()?));
}
if let Some(nbf) = self.not_before {
map.push((NBF.to_cbor_value()?, nbf.to_cbor_value()?));
}
if let Some(iat) = self.issued_at {
map.push((IAT.to_cbor_value()?, iat.to_cbor_value()?));
}
if let Some(cti) = self.cwt_id {
map.push((CTI.to_cbor_value()?, Value::Bytes(cti)));
}
for (label, value) in self.rest {
map.push((label.to_cbor_value()?, value));
}
Ok(Value::Map(map))
}
}
#[derive(Default)]
pub struct ClaimsSetBuilder(ClaimsSet);
impl ClaimsSetBuilder {
builder! {ClaimsSet}
builder_set_optional! {issuer: String}
builder_set_optional! {subject: String}
builder_set_optional! {audience: String}
builder_set_optional! {expiration_time: Timestamp}
builder_set_optional! {not_before: Timestamp}
builder_set_optional! {issued_at: Timestamp}
builder_set_optional! {cwt_id: Vec<u8>}
#[must_use]
pub fn claim(mut self, name: iana::CwtClaimName, value: Value) -> Self {
if name.to_i64() >= iana::CwtClaimName::Iss.to_i64()
&& name.to_i64() <= iana::CwtClaimName::Cti.to_i64()
{
panic!("claim() method used to set core claim"); }
self.0.rest.push((ClaimName::Assigned(name), value));
self
}
#[must_use]
pub fn text_claim(mut self, name: String, value: Value) -> Self {
self.0.rest.push((ClaimName::Text(name), value));
self
}
#[must_use]
pub fn private_claim(mut self, id: i64, value: Value) -> Self {
assert!(iana::CwtClaimName::is_private(id));
self.0.rest.push((ClaimName::PrivateUse(id), value));
self
}
}