1use core::fmt;
5use core::str::FromStr;
6
7use chrono::{DateTime, Utc};
8use indexmap::IndexMap;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use serde_json::Value;
11
12use crate::jose::{JwsError, JwsHeader, Signer, base64url};
13use crate::vc::{Jwt, JwtClaims};
14
15#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
35#[non_exhaustive]
36pub struct Status {
37 #[serde(default, skip_serializing_if = "Option::is_none")]
39 pub status_list: Option<StatusListRef>,
40 #[serde(flatten)]
42 pub additional: IndexMap<String, Value>,
43}
44
45impl Status {
46 pub fn new() -> Self {
48 Self::default()
49 }
50
51 pub fn status_list(reference: StatusListRef) -> Self {
53 Self {
54 status_list: Some(reference),
55 additional: IndexMap::new(),
56 }
57 }
58
59 #[must_use]
61 pub fn with_mechanism(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
62 self.additional.insert(name.into(), value.into());
63 self
64 }
65
66 pub fn is_empty(&self) -> bool {
68 self.status_list.is_none() && self.additional.is_empty()
69 }
70
71 pub fn mechanisms(&self) -> impl Iterator<Item = StatusMechanism<'_>> {
73 self.status_list
74 .iter()
75 .map(StatusMechanism::StatusList)
76 .chain(
77 self.additional
78 .iter()
79 .map(|(name, value)| StatusMechanism::Other { name, value }),
80 )
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86#[non_exhaustive]
87pub enum StatusMechanism<'a> {
88 StatusList(&'a StatusListRef),
90 Other {
92 name: &'a str,
94 value: &'a Value,
96 },
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
102#[non_exhaustive]
103pub struct StatusListRef {
104 pub idx: u64,
106 pub uri: String,
108}
109
110impl StatusListRef {
111 pub fn new(uri: impl Into<String>, idx: u64) -> Self {
113 Self {
114 idx,
115 uri: uri.into(),
116 }
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
145pub struct StatusType(pub u8);
146
147impl StatusType {
148 pub const VALID: Self = Self(0);
150 pub const INVALID: Self = Self(1);
152 pub const SUSPENDED: Self = Self(2);
154
155 pub const fn value(self) -> u8 {
157 self.0
158 }
159
160 pub const fn is_valid(self) -> bool {
162 self.0 == 0
163 }
164
165 pub const fn name(self) -> Option<&'static str> {
167 match self.0 {
168 0 => Some("VALID"),
169 1 => Some("INVALID"),
170 2 => Some("SUSPENDED"),
171 _ => None,
172 }
173 }
174
175 pub fn parse(s: &str) -> Result<Self, StatusListError> {
183 match s {
184 "VALID" => Ok(Self::VALID),
185 "INVALID" => Ok(Self::INVALID),
186 "SUSPENDED" => Ok(Self::SUSPENDED),
187 digits if !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit()) => digits
188 .parse()
189 .map(Self)
190 .map_err(|_| StatusListError::UnknownStatusType(s.to_owned())),
191 _ => Err(StatusListError::UnknownStatusType(s.to_owned())),
192 }
193 }
194}
195
196impl fmt::Display for StatusType {
197 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198 match self.name() {
199 Some(name) => write!(f, "{name} ({:#04x})", self.0),
200 None => write!(f, "{:#04x}", self.0),
201 }
202 }
203}
204
205impl From<u8> for StatusType {
206 fn from(value: u8) -> Self {
207 Self(value)
208 }
209}
210
211impl FromStr for StatusType {
212 type Err = StatusListError;
213
214 fn from_str(s: &str) -> Result<Self, Self::Err> {
215 Self::parse(s)
216 }
217}
218
219impl Serialize for StatusType {
220 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
221 match self.name() {
222 Some(name) => serializer.serialize_str(name),
223 None => serializer.serialize_u8(self.0),
224 }
225 }
226}
227
228impl<'de> Deserialize<'de> for StatusType {
229 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
230 struct StatusTypeVisitor;
231
232 impl serde::de::Visitor<'_> for StatusTypeVisitor {
233 type Value = StatusType;
234
235 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236 f.write_str("VALID, INVALID, SUSPENDED or a number in 0..=255")
237 }
238
239 fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<StatusType, E> {
240 StatusType::parse(s).map_err(E::custom)
241 }
242
243 fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<StatusType, E> {
244 u8::try_from(value)
245 .map(StatusType)
246 .map_err(|_| E::invalid_value(serde::de::Unexpected::Unsigned(value), &self))
247 }
248
249 fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<StatusType, E> {
250 u8::try_from(value)
251 .map(StatusType)
252 .map_err(|_| E::invalid_value(serde::de::Unexpected::Signed(value), &self))
253 }
254 }
255
256 deserializer.deserialize_any(StatusTypeVisitor)
257 }
258}
259
260#[derive(Debug, thiserror::Error)]
262#[non_exhaustive]
263pub enum StatusListError {
264 #[error("`bits` is {0}, not 1, 2, 4 or 8")]
266 InvalidBits(u8),
267 #[error("status value {value} does not fit in {bits} bits")]
269 InvalidStatusValue {
270 value: u8,
272 bits: u8,
274 },
275 #[error("index {idx} is out of range of a status list of {len} entries")]
277 IndexOutOfRange {
278 idx: u64,
280 len: usize,
282 },
283 #[error("invalid status list: {0}")]
285 Compression(String),
286 #[error("the status list decompresses to more than {limit} bytes")]
288 TooLarge {
289 limit: usize,
291 },
292 #[error("invalid JWS: {0}")]
294 Jws(#[from] JwsError),
295 #[error("{0:?} is not VALID, INVALID, SUSPENDED or a number in 0..=255")]
298 UnknownStatusType(String),
299 #[error("header `typ` is {0:?}, not `statuslist+jwt`")]
301 UnexpectedTyp(Option<String>),
302 #[error("the payload is not a status list token: {0}")]
304 Payload(String),
305 #[error("status list token `sub` {sub:?} is not the referenced {uri:?}")]
307 SubjectMismatch {
308 sub: String,
310 uri: String,
312 },
313 #[error("status list token expired at {} (now is {})", expired_at.to_rfc3339(), now.to_rfc3339())]
315 Expired {
316 expired_at: DateTime<Utc>,
318 now: DateTime<Utc>,
320 },
321}
322
323#[derive(Debug, Clone, PartialEq, Eq)]
349pub struct StatusList {
350 bits: u8,
351 bytes: Vec<u8>,
352 aggregation_uri: Option<String>,
353}
354
355impl StatusList {
356 pub const MAX_BYTES: usize = 1 << 24;
359
360 pub fn new(bits: u8, size: usize) -> Result<Self, StatusListError> {
366 check_bits(bits)?;
367 let bytes = vec![0; (size * usize::from(bits)).div_ceil(8)];
368 Ok(Self {
369 bits,
370 bytes,
371 aggregation_uri: None,
372 })
373 }
374
375 pub fn from_bytes(bits: u8, bytes: Vec<u8>) -> Result<Self, StatusListError> {
381 check_bits(bits)?;
382 Ok(Self {
383 bits,
384 bytes,
385 aggregation_uri: None,
386 })
387 }
388
389 #[must_use]
391 pub fn with_aggregation_uri(mut self, uri: impl Into<String>) -> Self {
392 self.aggregation_uri = Some(uri.into());
393 self
394 }
395
396 pub const fn bits(&self) -> u8 {
398 self.bits
399 }
400
401 pub fn as_bytes(&self) -> &[u8] {
403 &self.bytes
404 }
405
406 pub fn aggregation_uri(&self) -> Option<&str> {
408 self.aggregation_uri.as_deref()
409 }
410
411 pub fn len(&self) -> usize {
413 self.bytes.len() * 8 / usize::from(self.bits)
414 }
415
416 pub fn is_empty(&self) -> bool {
418 self.bytes.is_empty()
419 }
420
421 pub fn get(&self, index: u64) -> Option<StatusType> {
423 let (byte, shift) = self.position(index)?;
424 let mask = self.mask();
425 Some(StatusType((self.bytes.get(byte)? >> shift) & mask))
426 }
427
428 pub fn set(&mut self, index: u64, status: StatusType) -> Result<(), StatusListError> {
435 let mask = self.mask();
436 if status.0 & !mask != 0 {
437 return Err(StatusListError::InvalidStatusValue {
438 value: status.0,
439 bits: self.bits,
440 });
441 }
442 let len = self.len();
443 let out_of_range = || StatusListError::IndexOutOfRange { idx: index, len };
444 let (byte, shift) = self.position(index).ok_or_else(out_of_range)?;
445 let slot = self.bytes.get_mut(byte).ok_or_else(out_of_range)?;
446 *slot = (*slot & !(mask << shift)) | (status.0 << shift);
447 Ok(())
448 }
449
450 fn position(&self, index: u64) -> Option<(usize, u8)> {
452 let bits = u64::from(self.bits);
453 let bit = index.checked_mul(bits)?;
454 let byte = usize::try_from(bit / 8).ok()?;
455 let shift = u8::try_from(bit % 8).ok()?;
456 (byte < self.bytes.len()).then_some((byte, shift))
457 }
458
459 const fn mask(&self) -> u8 {
461 if self.bits == 8 {
462 0xff
463 } else {
464 (1 << self.bits) - 1
465 }
466 }
467
468 pub fn to_lst(&self) -> String {
470 let compressed = miniz_oxide::deflate::compress_to_vec_zlib(&self.bytes, 10);
471 base64url::encode(&compressed)
472 }
473
474 pub fn from_lst(bits: u8, lst: &str, max_bytes: usize) -> Result<Self, StatusListError> {
481 check_bits(bits)?;
482 let compressed = base64url::decode(lst)
483 .map_err(|e| StatusListError::Compression(format!("`lst` is not base64url: {e}")))?;
484 let bytes = miniz_oxide::inflate::decompress_to_vec_zlib_with_limit(&compressed, max_bytes)
485 .map_err(|e| match e.status {
486 miniz_oxide::inflate::TINFLStatus::HasMoreOutput => {
487 StatusListError::TooLarge { limit: max_bytes }
488 }
489 status => {
490 StatusListError::Compression(format!("`lst` is not ZLIB data: {status:?}"))
491 }
492 })?;
493 Ok(Self {
494 bits,
495 bytes,
496 aggregation_uri: None,
497 })
498 }
499}
500
501fn check_bits(bits: u8) -> Result<(), StatusListError> {
503 match bits {
504 1 | 2 | 4 | 8 => Ok(()),
505 _ => Err(StatusListError::InvalidBits(bits)),
506 }
507}
508
509#[derive(Serialize, Deserialize)]
511struct StatusListJson {
512 bits: u8,
513 lst: String,
514 #[serde(default, skip_serializing_if = "Option::is_none")]
515 aggregation_uri: Option<String>,
516}
517
518impl Serialize for StatusList {
519 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
520 StatusListJson {
521 bits: self.bits,
522 lst: self.to_lst(),
523 aggregation_uri: self.aggregation_uri.clone(),
524 }
525 .serialize(serializer)
526 }
527}
528
529impl<'de> Deserialize<'de> for StatusList {
530 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
532 let json = StatusListJson::deserialize(deserializer)?;
533 let mut list = Self::from_lst(json.bits, &json.lst, Self::MAX_BYTES)
534 .map_err(serde::de::Error::custom)?;
535 list.aggregation_uri = json.aggregation_uri;
536 Ok(list)
537 }
538}
539
540#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
568#[non_exhaustive]
569pub struct StatusListToken {
570 pub sub: String,
572 #[serde(deserialize_with = "crate::vc::required_numeric_date")]
574 pub iat: i64,
575 #[serde(
577 default,
578 skip_serializing_if = "Option::is_none",
579 deserialize_with = "crate::vc::numeric_date"
580 )]
581 pub exp: Option<i64>,
582 #[serde(default, skip_serializing_if = "Option::is_none")]
584 pub ttl: Option<u64>,
585 pub status_list: StatusList,
587 #[serde(flatten)]
589 pub additional: IndexMap<String, Value>,
590}
591
592impl StatusListToken {
593 pub const TYP: &'static str = "statuslist+jwt";
595
596 pub const MEDIA_TYPE: &'static str = "application/statuslist+jwt";
598
599 pub fn new(sub: impl Into<String>, iat: DateTime<Utc>, status_list: StatusList) -> Self {
601 Self {
602 sub: sub.into(),
603 iat: JwtClaims::numeric_date(iat),
604 exp: None,
605 ttl: None,
606 status_list,
607 additional: IndexMap::new(),
608 }
609 }
610
611 #[must_use]
613 pub fn with_expires_at(mut self, time: DateTime<Utc>) -> Self {
614 self.exp = Some(JwtClaims::numeric_date(time));
615 self
616 }
617
618 #[must_use]
620 pub fn with_ttl(mut self, seconds: u64) -> Self {
621 self.ttl = Some(seconds);
622 self
623 }
624
625 #[must_use]
627 pub fn with_claim(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
628 self.additional.insert(name.into(), value.into());
629 self
630 }
631
632 pub fn decode(jwt: &Jwt) -> Result<(JwsHeader, Self), StatusListError> {
640 let jws = jwt.decode()?;
641 if jws.header().typ.as_deref() != Some(Self::TYP) {
642 return Err(StatusListError::UnexpectedTyp(jws.header().typ.clone()));
643 }
644 let token = serde_json::from_slice(jws.payload())
645 .map_err(|e| StatusListError::Payload(e.to_string()))?;
646 Ok((jws.header().clone(), token))
647 }
648
649 pub async fn sign(&self, signer: &dyn Signer) -> Result<Jwt, StatusListError> {
655 let mut header = JwsHeader::new(signer.alg()).with_typ(Self::TYP);
656 header.kid = signer.kid().map(str::to_owned);
657 let payload = serde_json::to_vec(self).expect("a token serializes");
658 let jwt = crate::jose::encode_compact(&header, &payload, signer).await?;
659 Ok(Jwt::new(jwt).expect("a freshly encoded JWS decodes"))
660 }
661
662 pub fn status_of(
671 &self,
672 reference: &StatusListRef,
673 now: DateTime<Utc>,
674 ) -> Result<StatusType, StatusListError> {
675 if self.sub != reference.uri {
676 return Err(StatusListError::SubjectMismatch {
677 sub: self.sub.clone(),
678 uri: reference.uri.clone(),
679 });
680 }
681 if let Some(exp) = self.exp {
682 let expired_at = JwtClaims::timestamp(exp).unwrap_or(DateTime::<Utc>::MIN_UTC);
683 if expired_at <= now {
684 return Err(StatusListError::Expired { expired_at, now });
685 }
686 }
687 self.status_list
688 .get(reference.idx)
689 .ok_or(StatusListError::IndexOutOfRange {
690 idx: reference.idx,
691 len: self.status_list.len(),
692 })
693 }
694}