ssi_status/impl/token_status_list/
json.rsuse std::borrow::Cow;
use base64::prelude::{Engine, BASE64_URL_SAFE};
use flate2::Compression;
use iref::UriBuf;
use serde::{Deserialize, Serialize};
use ssi_claims_core::ValidateClaims;
use ssi_jws::ValidateJwsHeader;
use ssi_jwt::{
match_claim_type, AnyClaims, Claim, ClaimSet, InvalidClaimValue, IssuedAt, Issuer, JWTClaims,
Subject,
};
use crate::{
token_status_list::{BitString, StatusSize},
StatusMapEntry, StatusMapEntrySet,
};
use super::{DecodeError, StatusList};
pub type StatusListJwt = JWTClaims<StatusListJwtPrivateClaims>;
pub fn decode_status_list_jwt(mut claims: impl ClaimSet) -> Result<StatusList, DecodeError> {
let _ = claims
.try_remove::<Issuer>()
.map_err(DecodeError::claim)?
.ok_or(DecodeError::MissingIssuer)?;
let _ = claims
.try_remove::<Subject>()
.map_err(DecodeError::claim)?
.ok_or(DecodeError::MissingSubject)?;
let _ = claims
.try_remove::<IssuedAt>()
.map_err(DecodeError::claim)?
.ok_or(DecodeError::MissingSubject)?;
let ttl = claims
.try_remove()
.map_err(DecodeError::claim)?
.map(TimeToLiveClaim::unwrap);
let bit_string = claims
.try_remove::<JsonStatusList>()
.map_err(DecodeError::claim)?
.ok_or(DecodeError::MissingStatusList)?
.decode(None)?;
Ok(StatusList::new(bit_string, ttl))
}
#[derive(Serialize, Deserialize)]
pub struct StatusListJwtPrivateClaims {
#[serde(rename = "ttl")]
pub time_to_live: Option<TimeToLiveClaim>,
pub status_list: Option<JsonStatusList>,
#[serde(flatten)]
pub other_claims: AnyClaims,
}
impl ClaimSet for StatusListJwtPrivateClaims {
fn contains<C: Claim>(&self) -> bool {
match_claim_type! {
match C {
TimeToLiveClaim => self.time_to_live.is_some(),
JsonStatusList => self.status_list.is_some(),
_ => ClaimSet::contains::<C>(&self.other_claims)
}
}
}
fn try_get<C: Claim>(&self) -> Result<Option<Cow<C>>, InvalidClaimValue> {
match_claim_type! {
match C {
TimeToLiveClaim => {
Ok(self.time_to_live.as_ref().map(Cow::Borrowed))
},
JsonStatusList => {
Ok(self.status_list.as_ref().map(Cow::Borrowed))
},
_ => {
self.other_claims.try_get()
}
}
}
}
fn try_set<C: Claim>(&mut self, claim: C) -> Result<Result<(), C>, InvalidClaimValue> {
match_claim_type! {
match claim: C {
TimeToLiveClaim => {
self.time_to_live = Some(claim);
Ok(Ok(()))
},
JsonStatusList => {
self.status_list = Some(claim);
Ok(Ok(()))
},
_ => {
self.other_claims.try_set(claim)
}
}
}
}
fn try_remove<C: Claim>(&mut self) -> Result<Option<C>, InvalidClaimValue> {
match_claim_type! {
match C {
TimeToLiveClaim => {
Ok(self.time_to_live.take())
},
JsonStatusList => {
Ok(self.status_list.take())
},
_ => {
self.other_claims.try_remove()
}
}
}
}
}
impl<E, P> ValidateClaims<E, P> for StatusListJwtPrivateClaims {
fn validate_claims(&self, _env: &E, _proof: &P) -> ssi_claims_core::ClaimsValidity {
Ok(())
}
}
impl<E> ValidateJwsHeader<E> for StatusListJwtPrivateClaims {
fn validate_jws_header(
&self,
_env: &E,
_header: &ssi_jws::Header,
) -> ssi_claims_core::ClaimsValidity {
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(transparent)]
pub struct TimeToLiveClaim(pub u64);
impl TimeToLiveClaim {
pub fn unwrap(self) -> u64 {
self.0
}
}
impl Claim for TimeToLiveClaim {
const JWT_CLAIM_NAME: &'static str = "ttl";
}
#[derive(Clone, Serialize, Deserialize)]
pub struct JsonStatusList {
bits: StatusSize,
lst: String,
}
impl JsonStatusList {
pub fn encode(bit_string: &BitString, compression: Compression) -> Self {
let bytes = bit_string.to_compressed_bytes(compression);
Self {
bits: bit_string.status_size(),
lst: BASE64_URL_SAFE.encode(bytes),
}
}
pub fn decode(&self, limit: Option<u64>) -> Result<BitString, DecodeError> {
let bytes = BASE64_URL_SAFE.decode(&self.lst)?;
Ok(BitString::from_compressed_bytes(self.bits, &bytes, limit)?)
}
}
impl Claim for JsonStatusList {
const JWT_CLAIM_NAME: &'static str = "status_list";
}
#[derive(Clone, Serialize, Deserialize)]
pub struct Status {
pub status_list: StatusListReference,
}
impl Claim for Status {
const JWT_CLAIM_NAME: &'static str = "status";
}
impl StatusMapEntrySet for Status {
type Entry<'a> = &'a StatusListReference;
fn get_entry(&self, _purpose: crate::StatusPurpose<&str>) -> Option<Self::Entry<'_>> {
Some(&self.status_list)
}
}
#[derive(Clone, Serialize, Deserialize)]
pub struct StatusListReference {
pub idx: usize,
pub uri: UriBuf,
}
impl StatusMapEntry for StatusListReference {
type Key = usize;
type StatusSize = StatusSize;
fn key(&self) -> Self::Key {
self.idx
}
fn status_list_url(&self) -> &iref::Uri {
&self.uri
}
fn status_size(&self) -> Option<Self::StatusSize> {
None
}
}
#[cfg(test)]
mod tests {
use crate::token_status_list::json::JsonStatusList;
#[test]
fn deserialize_json_status_list() {
assert!(serde_json::from_str::<JsonStatusList>(
r#"{
"bits": 1,
"lst": "eNrbuRgAAhcBXQ"
}"#
)
.is_ok())
}
}