use std::collections::VecDeque;
use serde::{Deserialize, Serialize};
use crate::misc::CryptError;
use crate::stacked_ratchet::{Ratchet, StackedRatchet};
use std::ops::RangeInclusive;
#[cfg(debug_assertions)]
pub const MAX_HYPER_RATCHETS_IN_MEMORY: usize = 6;
#[cfg(not(debug_assertions))]
pub const MAX_HYPER_RATCHETS_IN_MEMORY: usize = 128;
pub const STATIC_AUX_VERSION: u32 = 0;
#[derive(Serialize, Deserialize)]
pub struct Toolset<R: Ratchet> {
pub cid: u64,
most_recent_hyper_ratchet_version: u32,
oldest_hyper_ratchet_version: u32,
#[serde(bound = "")]
map: VecDeque<R>,
#[serde(bound = "")]
static_auxiliary_hyper_ratchet: R,
}
impl<R: Ratchet> Clone for Toolset<R> {
fn clone(&self) -> Self {
Self {
cid: self.cid,
most_recent_hyper_ratchet_version: self.most_recent_hyper_ratchet_version,
oldest_hyper_ratchet_version: self.oldest_hyper_ratchet_version,
map: self.map.clone(),
static_auxiliary_hyper_ratchet: self.static_auxiliary_hyper_ratchet.clone(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Copy)]
pub enum UpdateStatus {
Committed { new_version: u32 },
CommittedNeedsSynchronization { new_version: u32, old_version: u32 },
}
impl<R: Ratchet> Toolset<R> {
pub fn new(cid: u64, hyper_ratchet: R) -> Self {
let mut map = VecDeque::with_capacity(MAX_HYPER_RATCHETS_IN_MEMORY);
map.push_front(hyper_ratchet.clone());
Toolset {
cid,
most_recent_hyper_ratchet_version: 0,
oldest_hyper_ratchet_version: 0,
map,
static_auxiliary_hyper_ratchet: hyper_ratchet,
}
}
pub fn new_debug(
cid: u64,
hyper_ratchet: R,
most_recent_hyper_ratchet_version: u32,
oldest_hyper_ratchet_version: u32,
) -> Self {
let mut map = VecDeque::with_capacity(MAX_HYPER_RATCHETS_IN_MEMORY);
map.push_front(hyper_ratchet.clone());
Toolset {
cid,
most_recent_hyper_ratchet_version,
oldest_hyper_ratchet_version,
map,
static_auxiliary_hyper_ratchet: hyper_ratchet,
}
}
pub fn update_from(&mut self, new_hyper_ratchet: R) -> Option<UpdateStatus> {
let latest_hr_version = self.get_most_recent_hyper_ratchet_version();
if new_hyper_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_hyper_ratchet.get_cid());
return None;
}
if latest_hr_version != new_hyper_ratchet.version().wrapping_sub(1) {
log::error!(target: "citadel", "The supplied hyper ratchet is not precedent to the drill update object (expected: {}, obtained: {})", latest_hr_version + 1, new_hyper_ratchet.version());
return None;
}
let update_status = self.append_hyper_ratchet(new_hyper_ratchet);
let cur_version = match &update_status {
UpdateStatus::Committed { new_version }
| UpdateStatus::CommittedNeedsSynchronization { new_version, .. } => *new_version,
};
self.most_recent_hyper_ratchet_version = cur_version;
let prev_version = self.most_recent_hyper_ratchet_version.wrapping_sub(1);
log::trace!(target: "citadel", "[{}] Upgraded {} to {}. Adjusted index of current: {}. Adjusted index of (current - 1): {} || OLDEST: {} || LEN: {}", MAX_HYPER_RATCHETS_IN_MEMORY, prev_version, cur_version, self.get_adjusted_index(cur_version), self.get_adjusted_index(prev_version), self.get_oldest_hyper_ratchet_version(), self.map.len());
Some(update_status)
}
#[allow(unused_results)]
fn append_hyper_ratchet(&mut self, hyper_ratchet: R) -> UpdateStatus {
let new_version = hyper_ratchet.version();
self.map.push_front(hyper_ratchet);
if self.map.len() > MAX_HYPER_RATCHETS_IN_MEMORY {
let old_version = self.get_oldest_hyper_ratchet_version();
log::trace!(target: "citadel", "[Toolset Update] Needs Truncation. Old version: {}", old_version);
UpdateStatus::CommittedNeedsSynchronization {
new_version,
old_version,
}
} else {
UpdateStatus::Committed { new_version }
}
}
#[allow(unused_results)]
pub fn deregister_oldest_hyper_ratchet(&mut self, version: u32) -> Result<(), CryptError> {
if self.map.len() <= MAX_HYPER_RATCHETS_IN_MEMORY {
return Err(CryptError::DrillUpdateError(
"Cannot call for deregistration unless the map len is maxed out".to_string(),
));
}
let oldest = self.get_oldest_hyper_ratchet_version();
if oldest != version {
Err(CryptError::DrillUpdateError(format!(
"Unable to deregister. Provided version: {}, expected version: {}",
version, oldest
)))
} else {
self.map.pop_back().ok_or(CryptError::OutOfBoundsError)?;
self.oldest_hyper_ratchet_version = self.oldest_hyper_ratchet_version.wrapping_add(1);
log::trace!(target: "citadel", "[Toolset] Deregistered version {}. New oldest: {} | LEN: {}", version, self.oldest_hyper_ratchet_version, self.len());
Ok(())
}
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.map.len()
}
pub fn get_most_recent_hyper_ratchet(&self) -> Option<&R> {
self.map.front()
}
pub fn get_oldest_hyper_ratchet(&self) -> Option<&R> {
self.map.back()
}
pub fn get_oldest_hyper_ratchet_version(&self) -> u32 {
self.oldest_hyper_ratchet_version
}
pub fn get_most_recent_hyper_ratchet_version(&self) -> u32 {
self.most_recent_hyper_ratchet_version
}
pub fn get_static_auxiliary_ratchet(&self) -> &R {
&self.static_auxiliary_hyper_ratchet
}
#[inline]
fn get_adjusted_index(&self, version: u32) -> usize {
self.most_recent_hyper_ratchet_version.wrapping_sub(version) as usize
}
pub fn get_hyper_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{}, but does not exist! len: {}. Oldest: {}. Newest: {}", version, &self.map.len(), self.oldest_hyper_ratchet_version, self.most_recent_hyper_ratchet_version);
}
res
}
pub fn get_hyper_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(drill) = self.get_hyper_ratchet(version) {
ret.push(drill);
} else {
return None;
}
}
Some(ret)
}
pub fn serialize_to_vec(&self) -> Result<Vec<u8>, CryptError<String>> {
bincode2::serialize(self).map_err(|err| CryptError::DrillUpdateError(err.to_string()))
}
pub fn deserialize_from_bytes<T: AsRef<[u8]>>(input: T) -> Result<Self, CryptError<String>> {
bincode2::deserialize(input.as_ref())
.map_err(|err| CryptError::DrillUpdateError(err.to_string()))
}
pub fn verify_init_state(&self) -> Option<()> {
self.static_auxiliary_hyper_ratchet.reset_ara();
Some(())
}
}
pub type StaticAuxRatchet = StackedRatchet;
impl From<(StaticAuxRatchet, StackedRatchet)> for Toolset<StackedRatchet> {
fn from(drill: (StaticAuxRatchet, StackedRatchet)) -> Self {
let most_recent_hyper_ratchet_version = drill.1.version();
let oldest_hyper_ratchet_version = most_recent_hyper_ratchet_version; let mut map = VecDeque::with_capacity(MAX_HYPER_RATCHETS_IN_MEMORY);
map.insert(0, drill.1);
Self {
cid: drill.0.get_cid(),
oldest_hyper_ratchet_version,
most_recent_hyper_ratchet_version,
map,
static_auxiliary_hyper_ratchet: drill.0,
}
}
}