use std::collections::VecDeque;
use serde::{Deserialize, Serialize};
use crate::misc::CryptError;
use crate::ratchets::stacked::ratchet::StackedRatchet;
use crate::ratchets::Ratchet;
use std::ops::RangeInclusive;
#[cfg(debug_assertions)]
pub const MAX_RATCHETS_IN_MEMORY: usize = 6;
#[cfg(not(debug_assertions))]
pub const MAX_RATCHETS_IN_MEMORY: usize = 32;
pub const STATIC_AUX_VERSION: u32 = 0;
#[derive(Serialize, Deserialize)]
pub struct Toolset<R: Ratchet> {
pub cid: u64,
most_recent_ratchet_version: u32,
oldest_ratchet_version: u32,
#[serde(bound = "")]
map: VecDeque<R>,
#[serde(bound = "")]
static_auxiliary_ratchet: R,
}
impl<R: Ratchet> Clone for Toolset<R> {
fn clone(&self) -> Self {
Self {
cid: self.cid,
most_recent_ratchet_version: self.most_recent_ratchet_version,
oldest_ratchet_version: self.oldest_ratchet_version,
map: self.map.clone(),
static_auxiliary_ratchet: self.static_auxiliary_ratchet.clone(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Copy, Eq, PartialEq, Debug)]
pub enum ToolsetUpdateStatus {
Committed {
new_version: u32,
},
CommittedNeedsSynchronization {
new_version: u32,
oldest_version: u32,
},
}
impl<R: Ratchet> Toolset<R> {
pub fn new(cid: u64, ratchet: R) -> Self {
let mut map = VecDeque::with_capacity(MAX_RATCHETS_IN_MEMORY);
map.push_front(ratchet.clone());
Toolset {
cid,
most_recent_ratchet_version: 0,
oldest_ratchet_version: 0,
map,
static_auxiliary_ratchet: ratchet,
}
}
pub fn new_debug(
cid: u64,
ratchet: R,
most_recent_ratchet_version: u32,
oldest_ratchet_version: u32,
) -> Self {
let mut map = VecDeque::with_capacity(MAX_RATCHETS_IN_MEMORY);
map.push_front(ratchet.clone());
Toolset {
cid,
most_recent_ratchet_version,
oldest_ratchet_version,
map,
static_auxiliary_ratchet: ratchet,
}
}
pub fn update_from(&mut self, new_ratchet: R) -> Option<ToolsetUpdateStatus> {
let latest_hr_version = self.get_most_recent_ratchet_version();
if new_ratchet.get_cid() != self.cid {
log::error!(target: "citadel", "The supplied hyper ratchet does not belong to the expected CID (expected: {}, obtained: {})", self.cid, new_ratchet.get_cid());
return None;
}
if latest_hr_version != new_ratchet.version().wrapping_sub(1) {
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());
return None;
}
let update_status = self.append_ratchet(new_ratchet);
let cur_version = match &update_status {
ToolsetUpdateStatus::Committed { new_version }
| ToolsetUpdateStatus::CommittedNeedsSynchronization { new_version, .. } => {
*new_version
}
};
self.most_recent_ratchet_version = cur_version;
let prev_version = self.most_recent_ratchet_version.wrapping_sub(1);
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());
Some(update_status)
}
#[allow(unused_results)]
fn append_ratchet(&mut self, ratchet: R) -> ToolsetUpdateStatus {
let new_version = ratchet.version();
self.map.push_front(ratchet);
if self.map.len() >= MAX_RATCHETS_IN_MEMORY {
let oldest_version = self.get_oldest_ratchet_version();
log::trace!(target: "citadel", "[Toolset Update] Needs Truncation. Oldest version: {}", oldest_version);
ToolsetUpdateStatus::CommittedNeedsSynchronization {
new_version,
oldest_version,
}
} else {
ToolsetUpdateStatus::Committed { new_version }
}
}
#[allow(unused_results)]
pub fn deregister_oldest_ratchet(&mut self, version: u32) -> Result<(), CryptError> {
if self.map.len() < MAX_RATCHETS_IN_MEMORY {
return Err(CryptError::RekeyUpdateError(
"Cannot call for deregistration unless the map len is maxed out".to_string(),
));
}
let oldest = self.get_oldest_ratchet_version();
if oldest != version {
Err(CryptError::RekeyUpdateError(format!(
"Unable to deregister. Provided version: {version}, expected version: {oldest}",
)))
} else {
self.map.pop_back().ok_or(CryptError::OutOfBoundsError)?;
self.oldest_ratchet_version = self.oldest_ratchet_version.wrapping_add(1);
log::trace!(target: "citadel", "[Toolset] Deregistered version {} for cid={}. New oldest: {} | LEN: {}", version, self.cid, self.oldest_ratchet_version, self.len());
Ok(())
}
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.map.len()
}
pub fn get_most_recent_ratchet(&self) -> Option<&R> {
self.map.front()
}
pub fn get_oldest_ratchet(&self) -> Option<&R> {
self.map.back()
}
pub fn get_oldest_ratchet_version(&self) -> u32 {
self.oldest_ratchet_version
}
pub fn get_most_recent_ratchet_version(&self) -> u32 {
self.most_recent_ratchet_version
}
pub fn get_static_auxiliary_ratchet(&self) -> &R {
&self.static_auxiliary_ratchet
}
#[inline]
fn get_adjusted_index(&self, version: u32) -> usize {
self.most_recent_ratchet_version.wrapping_sub(version) as usize
}
pub fn get_ratchet(&self, version: u32) -> Option<&R> {
let idx = self.get_adjusted_index(version);
let res = self.map.get(idx);
if res.is_none() {
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);
}
res
}
pub fn get_ratchets(&self, versions: RangeInclusive<u32>) -> Option<Vec<&R>> {
let mut ret = Vec::with_capacity((*versions.end() - *versions.start() + 1) as usize);
for version in versions {
if let Some(entropy_bank) = self.get_ratchet(version) {
ret.push(entropy_bank);
} else {
return None;
}
}
Some(ret)
}
pub fn serialize_to_vec(&self) -> Result<Vec<u8>, CryptError<String>> {
bincode::serialize(self).map_err(|err| CryptError::RekeyUpdateError(err.to_string()))
}
pub fn deserialize_from_bytes<T: AsRef<[u8]>>(input: T) -> Result<Self, CryptError<String>> {
bincode::deserialize(input.as_ref())
.map_err(|err| CryptError::RekeyUpdateError(err.to_string()))
}
pub fn verify_init_state(&self) -> Option<()> {
self.static_auxiliary_ratchet.reset_ara();
Some(())
}
}
pub type StaticAuxRatchet = StackedRatchet;
impl<R: Ratchet> From<(R, R)> for Toolset<R> {
fn from(entropy_bank: (R, R)) -> Self {
let most_recent_ratchet_version = entropy_bank.1.version();
let oldest_ratchet_version = most_recent_ratchet_version; let mut map = VecDeque::with_capacity(MAX_RATCHETS_IN_MEMORY);
map.insert(0, entropy_bank.1);
Self {
cid: entropy_bank.0.get_cid(),
oldest_ratchet_version,
most_recent_ratchet_version,
map,
static_auxiliary_ratchet: entropy_bank.0,
}
}
}