identity_credential/revocation/status_list_2021/
status_list.rs1use flate2::read::GzDecoder;
5use flate2::write::GzEncoder;
6use flate2::Compression;
7use identity_core::convert::Base;
8use identity_core::convert::BaseEncoding;
9use std::io::Write;
10use thiserror::Error;
11
12const MINIMUM_LIST_SIZE: usize = 16 * 1024 * 8;
13
14#[derive(Debug, Error, PartialEq, Eq, Clone, strum::IntoStaticStr)]
16pub enum StatusListError {
17 #[error("The requested entry is not in the list.")]
19 IndexOutOfBounds,
20 #[error("\"{0}\" is not a valid encoded status list.")]
22 InvalidEncoding(String),
23 #[error("A StatusList2021 must have at least {MINIMUM_LIST_SIZE} entries.")]
25 InvalidListSize,
26}
27
28#[derive(Debug, Clone, Eq, PartialEq, Hash)]
30pub struct StatusList2021(Box<[u8]>);
31
32impl Default for StatusList2021 {
33 fn default() -> Self {
34 StatusList2021::new(MINIMUM_LIST_SIZE).unwrap()
35 }
36}
37
38impl StatusList2021 {
39 pub fn new(num_entries: usize) -> Result<Self, StatusListError> {
45 if num_entries < MINIMUM_LIST_SIZE {
46 return Err(StatusListError::InvalidListSize);
47 }
48
49 let size = num_entries / 8 + (num_entries % 8 != 0) as usize;
50 let store = vec![0; size];
51
52 Ok(StatusList2021(store.into_boxed_slice()))
53 }
54
55 #[allow(clippy::len_without_is_empty)]
57 pub const fn len(&self) -> usize {
58 self.0.len() * 8
59 }
60
61 const fn get_unchecked(&self, index: usize) -> bool {
65 let (i, offset) = Self::entry_index_to_store_index(index);
66 self.0[i] & (0b1000_0000 >> offset) != 0
67 }
68
69 fn set_unchecked(&mut self, index: usize, value: bool) {
74 let (i, offset) = Self::entry_index_to_store_index(index);
75 if value {
76 self.0[i] |= 0b1000_0000 >> offset
77 } else {
78 self.0[i] &= 0b0111_1111 >> offset
79 }
80 }
81
82 pub fn get(&self, index: usize) -> Result<bool, StatusListError> {
84 (index < self.len())
85 .then_some(self.get_unchecked(index))
86 .ok_or(StatusListError::IndexOutOfBounds)
87 }
88
89 pub fn set(&mut self, index: usize, value: bool) -> Result<(), StatusListError> {
91 if index < self.len() {
92 self.set_unchecked(index, value);
93 Ok(())
94 } else {
95 Err(StatusListError::IndexOutOfBounds)
96 }
97 }
98
99 pub fn try_from_encoded_str(s: &str) -> Result<Self, StatusListError> {
102 let compressed_status_list =
103 BaseEncoding::decode(s, Base::Base64).or(Err(StatusListError::InvalidEncoding(s.to_owned())))?;
104 let status_list = {
105 use std::io::Read;
106
107 let mut decompressor = GzDecoder::new(&compressed_status_list[..]);
108 let mut status_list = vec![];
109 decompressor
110 .read_to_end(&mut status_list)
111 .or(Err(StatusListError::InvalidEncoding(s.to_owned())))?;
112
113 StatusList2021(status_list.into_boxed_slice())
114 };
115
116 Ok(status_list)
117 }
118
119 pub fn into_encoded_str(self) -> String {
122 let compressed_status_list = {
123 let mut compressor = GzEncoder::new(vec![], Compression::best());
124 compressor.write_all(&self.0).unwrap();
125 compressor.finish().unwrap()
126 };
127
128 BaseEncoding::encode(&compressed_status_list[..], Base::Base64)
129 }
130
131 const fn entry_index_to_store_index(index: usize) -> (usize, usize) {
133 (index / 8, index % 8)
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn default_status_list() {
143 let mut status_list = StatusList2021::default();
144 status_list.set(131071, true).unwrap();
145 assert!(status_list.get(131071).unwrap());
146 assert_eq!(status_list.set(131072, true), Err(StatusListError::IndexOutOfBounds));
147 }
148
149 #[test]
150 fn status_list_too_short_fails() {
151 assert_eq!(StatusList2021::new(100), Err(StatusListError::InvalidListSize));
152 }
153
154 #[test]
155 fn status_list_entry_access() {
156 let mut status_list = StatusList2021::default();
157 status_list.set(42, true).unwrap();
158 assert!(status_list.get(42).unwrap());
159
160 status_list.set(42, false).unwrap();
161 assert_eq!(status_list, StatusList2021::default());
162 }
163
164 #[test]
165 fn status_list_encode_decode() {
166 let mut status_list = StatusList2021::default();
167 status_list.set(42, true).unwrap();
168 status_list.set(420, true).unwrap();
169 status_list.set(4200, true).unwrap();
170 let encoded = status_list.clone().into_encoded_str();
171 let decoded = StatusList2021::try_from_encoded_str(&encoded).unwrap();
172 assert_eq!(decoded, status_list);
173 }
174}