1use std::collections::VecDeque;
28
29use serde::{Deserialize, Serialize};
30
31use crate::misc::CryptError;
32use crate::ratchets::stacked::ratchet::StackedRatchet;
33use crate::ratchets::Ratchet;
34use std::ops::RangeInclusive;
35
36#[cfg(debug_assertions)]
40pub const MAX_RATCHETS_IN_MEMORY: usize = 6;
41#[cfg(not(debug_assertions))]
42pub const MAX_RATCHETS_IN_MEMORY: usize = 32;
43
44pub const STATIC_AUX_VERSION: u32 = 0;
46
47#[derive(Serialize, Deserialize)]
50pub struct Toolset<R: Ratchet> {
51 pub cid: u64,
53 most_recent_ratchet_version: u32,
54 oldest_ratchet_version: u32,
55 #[serde(bound = "")]
56 map: VecDeque<R>,
57 #[serde(bound = "")]
65 static_auxiliary_ratchet: R,
66}
67
68impl<R: Ratchet> Clone for Toolset<R> {
70 fn clone(&self) -> Self {
71 Self {
72 cid: self.cid,
73 most_recent_ratchet_version: self.most_recent_ratchet_version,
74 oldest_ratchet_version: self.oldest_ratchet_version,
75 map: self.map.clone(),
76 static_auxiliary_ratchet: self.static_auxiliary_ratchet.clone(),
77 }
78 }
79}
80
81#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, Debug)]
82pub enum ToolsetUpdateStatus {
83 Committed {
85 new_version: u32,
86 },
87 CommittedNeedsSynchronization {
90 new_version: u32,
91 oldest_version: u32,
92 },
93}
94
95impl<R: Ratchet> Toolset<R> {
96 pub fn new(cid: u64, ratchet: R) -> Self {
99 let mut map = VecDeque::with_capacity(MAX_RATCHETS_IN_MEMORY);
100 map.push_front(ratchet.clone());
101 Toolset {
102 cid,
103 most_recent_ratchet_version: 0,
104 oldest_ratchet_version: 0,
105 map,
106 static_auxiliary_ratchet: ratchet,
107 }
108 }
109
110 pub fn new_debug(
111 cid: u64,
112 ratchet: R,
113 most_recent_ratchet_version: u32,
114 oldest_ratchet_version: u32,
115 ) -> Self {
116 let mut map = VecDeque::with_capacity(MAX_RATCHETS_IN_MEMORY);
117 map.push_front(ratchet.clone());
118 Toolset {
119 cid,
120 most_recent_ratchet_version,
121 oldest_ratchet_version,
122 map,
123 static_auxiliary_ratchet: ratchet,
124 }
125 }
126
127 pub fn update_from(&mut self, new_ratchet: R) -> Option<ToolsetUpdateStatus> {
129 let latest_hr_version = self.get_most_recent_ratchet_version();
130
131 if new_ratchet.get_cid() != self.cid {
132 log::error!(target: "citadel", "The supplied hyper ratchet does not belong to the expected CID (expected: {}, obtained: {})", self.cid, new_ratchet.get_cid());
133 return None;
134 }
135
136 if latest_hr_version != new_ratchet.version().wrapping_sub(1) {
137 log::error!(target: "citadel", "The supplied hyper ratchet is not precedent to the entropy_bank update object (expected: {}, obtained: {})", latest_hr_version + 1, new_ratchet.version());
138 return None;
139 }
140
141 let update_status = self.append_ratchet(new_ratchet);
142 let cur_version = match &update_status {
143 ToolsetUpdateStatus::Committed { new_version }
144 | ToolsetUpdateStatus::CommittedNeedsSynchronization { new_version, .. } => {
145 *new_version
146 }
147 };
148
149 self.most_recent_ratchet_version = cur_version;
150
151 let prev_version = self.most_recent_ratchet_version.wrapping_sub(1);
152 log::trace!(target: "citadel", "[{}] Upgraded {} to {} for cid={}. Adjusted index of current: {}. Adjusted index of (current - 1): {} || OLDEST: {} || LEN: {}", MAX_RATCHETS_IN_MEMORY, prev_version, cur_version, self.cid, self.get_adjusted_index(cur_version), self.get_adjusted_index(prev_version), self.get_oldest_ratchet_version(), self.map.len());
153 Some(update_status)
154 }
155
156 #[allow(unused_results)]
157 fn append_ratchet(&mut self, ratchet: R) -> ToolsetUpdateStatus {
161 let new_version = ratchet.version();
163 self.map.push_front(ratchet);
165 if self.map.len() >= MAX_RATCHETS_IN_MEMORY {
166 let oldest_version = self.get_oldest_ratchet_version();
167 log::trace!(target: "citadel", "[Toolset Update] Needs Truncation. Oldest version: {}", oldest_version);
168 ToolsetUpdateStatus::CommittedNeedsSynchronization {
169 new_version,
170 oldest_version,
171 }
172 } else {
173 ToolsetUpdateStatus::Committed { new_version }
174 }
175 }
176
177 #[allow(unused_results)]
183 pub fn deregister_oldest_ratchet(&mut self, version: u32) -> Result<(), CryptError> {
184 if self.map.len() < MAX_RATCHETS_IN_MEMORY {
185 return Err(CryptError::RekeyUpdateError(
186 "Cannot call for deregistration unless the map len is maxed out".to_string(),
187 ));
188 }
189
190 let oldest = self.get_oldest_ratchet_version();
191 if oldest != version {
192 Err(CryptError::RekeyUpdateError(format!(
193 "Unable to deregister. Provided version: {version}, expected version: {oldest}",
194 )))
195 } else {
196 self.map.pop_back().ok_or(CryptError::OutOfBoundsError)?;
197 self.oldest_ratchet_version = self.oldest_ratchet_version.wrapping_add(1);
198 log::trace!(target: "citadel", "[Toolset] Deregistered version {} for cid={}. New oldest: {} | LEN: {}", version, self.cid, self.oldest_ratchet_version, self.len());
199 Ok(())
200 }
201 }
202
203 #[allow(clippy::len_without_is_empty)]
205 pub fn len(&self) -> usize {
206 self.map.len()
207 }
208
209 pub fn get_most_recent_ratchet(&self) -> Option<&R> {
211 self.map.front()
212 }
213
214 pub fn get_oldest_ratchet(&self) -> Option<&R> {
216 self.map.back()
217 }
218
219 pub fn get_oldest_ratchet_version(&self) -> u32 {
221 self.oldest_ratchet_version
222 }
223
224 pub fn get_most_recent_ratchet_version(&self) -> u32 {
226 self.most_recent_ratchet_version
227 }
228
229 pub fn get_static_auxiliary_ratchet(&self) -> &R {
239 &self.static_auxiliary_ratchet
240 }
241
242 #[inline]
245 fn get_adjusted_index(&self, version: u32) -> usize {
246 self.most_recent_ratchet_version.wrapping_sub(version) as usize
247 }
248
249 pub fn get_ratchet(&self, version: u32) -> Option<&R> {
251 let idx = self.get_adjusted_index(version);
252
253 let res = self.map.get(idx);
254 if res.is_none() {
255 log::error!(target: "citadel", "Attempted to get ratchet v{} for cid={}, but does not exist! len: {}. Oldest: {}. Newest: {}", version, self.cid, self.map.len(), self.oldest_ratchet_version, self.most_recent_ratchet_version);
256 }
257
258 res
259 }
260
261 pub fn get_ratchets(&self, versions: RangeInclusive<u32>) -> Option<Vec<&R>> {
263 let mut ret = Vec::with_capacity((*versions.end() - *versions.start() + 1) as usize);
264 for version in versions {
265 if let Some(entropy_bank) = self.get_ratchet(version) {
266 ret.push(entropy_bank);
267 } else {
268 return None;
269 }
270 }
271
272 Some(ret)
273 }
274
275 pub fn serialize_to_vec(&self) -> Result<Vec<u8>, CryptError<String>> {
277 bincode::serialize(self).map_err(|err| CryptError::RekeyUpdateError(err.to_string()))
278 }
279
280 pub fn deserialize_from_bytes<T: AsRef<[u8]>>(input: T) -> Result<Self, CryptError<String>> {
282 bincode::deserialize(input.as_ref())
283 .map_err(|err| CryptError::RekeyUpdateError(err.to_string()))
284 }
285
286 pub fn verify_init_state(&self) -> Option<()> {
288 self.static_auxiliary_ratchet.reset_ara();
289 Some(())
290 }
291}
292
293pub type StaticAuxRatchet = StackedRatchet;
296impl<R: Ratchet> From<(R, R)> for Toolset<R> {
297 fn from(entropy_bank: (R, R)) -> Self {
298 let most_recent_ratchet_version = entropy_bank.1.version();
299 let oldest_ratchet_version = most_recent_ratchet_version; let mut map = VecDeque::with_capacity(MAX_RATCHETS_IN_MEMORY);
301 map.insert(0, entropy_bank.1);
302 Self {
303 cid: entropy_bank.0.get_cid(),
304 oldest_ratchet_version,
305 most_recent_ratchet_version,
306 map,
307 static_auxiliary_ratchet: entropy_bank.0,
308 }
309 }
310}