ssi_status/impl/token_status_list/
json.rs1use std::borrow::Cow;
3
4use base64::prelude::{Engine, BASE64_URL_SAFE};
5use flate2::Compression;
6use iref::UriBuf;
7use serde::{Deserialize, Serialize};
8use ssi_claims_core::ValidateClaims;
9use ssi_jws::ValidateJwsHeader;
10use ssi_jwt::{
11 match_claim_type, AnyClaims, Claim, ClaimSet, InvalidClaimValue, IssuedAt, Issuer, JWTClaims,
12 Subject,
13};
14
15use crate::{
16 token_status_list::{BitString, StatusSize},
17 StatusMapEntry, StatusMapEntrySet,
18};
19
20use super::{DecodeError, StatusList};
21
22pub type StatusListJwt = JWTClaims<StatusListJwtPrivateClaims>;
26
27pub fn decode_status_list_jwt(mut claims: impl ClaimSet) -> Result<StatusList, DecodeError> {
28 let _ = claims
29 .try_remove::<Issuer>()
30 .map_err(DecodeError::claim)?
31 .ok_or(DecodeError::MissingIssuer)?;
32
33 let _ = claims
34 .try_remove::<Subject>()
35 .map_err(DecodeError::claim)?
36 .ok_or(DecodeError::MissingSubject)?;
37
38 let _ = claims
39 .try_remove::<IssuedAt>()
40 .map_err(DecodeError::claim)?
41 .ok_or(DecodeError::MissingSubject)?;
42 let ttl = claims
43 .try_remove()
44 .map_err(DecodeError::claim)?
45 .map(TimeToLiveClaim::unwrap);
46
47 let bit_string = claims
48 .try_remove::<JsonStatusList>()
49 .map_err(DecodeError::claim)?
50 .ok_or(DecodeError::MissingStatusList)?
51 .decode(None)?;
52
53 Ok(StatusList::new(bit_string, ttl))
54}
55
56#[derive(Serialize, Deserialize)]
63pub struct StatusListJwtPrivateClaims {
64 #[serde(rename = "ttl")]
70 pub time_to_live: Option<TimeToLiveClaim>,
71
72 pub status_list: Option<JsonStatusList>,
74
75 #[serde(flatten)]
77 pub other_claims: AnyClaims,
78}
79
80impl ClaimSet for StatusListJwtPrivateClaims {
81 fn contains<C: Claim>(&self) -> bool {
82 match_claim_type! {
83 match C {
84 TimeToLiveClaim => self.time_to_live.is_some(),
85 JsonStatusList => self.status_list.is_some(),
86 _ => ClaimSet::contains::<C>(&self.other_claims)
87 }
88 }
89 }
90
91 fn try_get<C: Claim>(&self) -> Result<Option<Cow<C>>, InvalidClaimValue> {
92 match_claim_type! {
93 match C {
94 TimeToLiveClaim => {
95 Ok(self.time_to_live.as_ref().map(Cow::Borrowed))
96 },
97 JsonStatusList => {
98 Ok(self.status_list.as_ref().map(Cow::Borrowed))
99 },
100 _ => {
101 self.other_claims.try_get()
102 }
103 }
104 }
105 }
106
107 fn try_set<C: Claim>(&mut self, claim: C) -> Result<Result<(), C>, InvalidClaimValue> {
108 match_claim_type! {
109 match claim: C {
110 TimeToLiveClaim => {
111 self.time_to_live = Some(claim);
112 Ok(Ok(()))
113 },
114 JsonStatusList => {
115 self.status_list = Some(claim);
116 Ok(Ok(()))
117 },
118 _ => {
119 self.other_claims.try_set(claim)
120 }
121 }
122 }
123 }
124
125 fn try_remove<C: Claim>(&mut self) -> Result<Option<C>, InvalidClaimValue> {
126 match_claim_type! {
127 match C {
128 TimeToLiveClaim => {
129 Ok(self.time_to_live.take())
130 },
131 JsonStatusList => {
132 Ok(self.status_list.take())
133 },
134 _ => {
135 self.other_claims.try_remove()
136 }
137 }
138 }
139 }
140}
141
142impl<E, P> ValidateClaims<E, P> for StatusListJwtPrivateClaims {
143 fn validate_claims(&self, _env: &E, _proof: &P) -> ssi_claims_core::ClaimsValidity {
144 Ok(())
145 }
146}
147
148impl<E> ValidateJwsHeader<E> for StatusListJwtPrivateClaims {
149 fn validate_jws_header(
150 &self,
151 _env: &E,
152 _header: &ssi_jws::Header,
153 ) -> ssi_claims_core::ClaimsValidity {
154 Ok(())
155 }
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
160#[serde(transparent)]
161pub struct TimeToLiveClaim(pub u64);
162
163impl TimeToLiveClaim {
164 pub fn unwrap(self) -> u64 {
165 self.0
166 }
167}
168
169impl Claim for TimeToLiveClaim {
170 const JWT_CLAIM_NAME: &'static str = "ttl";
171}
172
173#[derive(Clone, Serialize, Deserialize)]
177pub struct JsonStatusList {
178 bits: StatusSize,
180
181 lst: String,
183}
184
185impl JsonStatusList {
186 pub fn encode(bit_string: &BitString, compression: Compression) -> Self {
187 let bytes = bit_string.to_compressed_bytes(compression);
188 Self {
189 bits: bit_string.status_size(),
190 lst: BASE64_URL_SAFE.encode(bytes),
191 }
192 }
193
194 pub fn decode(&self, limit: Option<u64>) -> Result<BitString, DecodeError> {
195 let bytes = BASE64_URL_SAFE.decode(&self.lst)?;
196 Ok(BitString::from_compressed_bytes(self.bits, &bytes, limit)?)
197 }
198}
199
200impl Claim for JsonStatusList {
201 const JWT_CLAIM_NAME: &'static str = "status_list";
202}
203
204#[derive(Clone, Serialize, Deserialize)]
206pub struct Status {
207 pub status_list: StatusListReference,
208}
209
210impl Claim for Status {
211 const JWT_CLAIM_NAME: &'static str = "status";
212}
213
214impl StatusMapEntrySet for Status {
215 type Entry<'a> = &'a StatusListReference;
216
217 fn get_entry(&self, _purpose: crate::StatusPurpose<&str>) -> Option<Self::Entry<'_>> {
218 Some(&self.status_list)
219 }
220}
221
222#[derive(Clone, Serialize, Deserialize)]
223pub struct StatusListReference {
224 pub idx: usize,
227
228 pub uri: UriBuf,
231}
232
233impl StatusMapEntry for StatusListReference {
234 type Key = usize;
235 type StatusSize = StatusSize;
236
237 fn key(&self) -> Self::Key {
238 self.idx
239 }
240
241 fn status_list_url(&self) -> &iref::Uri {
242 &self.uri
243 }
244
245 fn status_size(&self) -> Option<Self::StatusSize> {
246 None
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use crate::token_status_list::json::JsonStatusList;
253
254 #[test]
255 fn deserialize_json_status_list() {
256 assert!(serde_json::from_str::<JsonStatusList>(
257 r#"{
258 "bits": 1,
259 "lst": "eNrbuRgAAhcBXQ"
260 }"#
261 )
262 .is_ok())
263 }
264}