use crate::Result;
use crate::errors::PagedbError;
#[derive(Debug, Clone, Copy)]
pub struct Nonce([u8; 12]);
impl Nonce {
pub const COUNTER_MAX: u64 = (1u64 << 48) - 1;
#[must_use]
pub fn from_parts(file_id6: [u8; 6], counter: u64) -> Self {
let mut out = [0u8; 12];
out[..6].copy_from_slice(&file_id6);
let le = counter.to_le_bytes();
out[6..].copy_from_slice(&le[..6]);
Self(out)
}
#[must_use]
pub fn from_bytes(bytes: [u8; 12]) -> Self {
Self(bytes)
}
#[must_use]
pub fn as_bytes(&self) -> &[u8; 12] {
&self.0
}
#[must_use]
pub fn as_aead_nonce(&self) -> &aes_gcm::Nonce<aes_gcm::aes::cipher::consts::U12> {
aes_gcm::Nonce::from_slice(&self.0)
}
#[must_use]
pub fn as_chacha_nonce(&self) -> &chacha20poly1305::Nonce {
chacha20poly1305::Nonce::from_slice(&self.0)
}
}
pub const DEFAULT_ANCHOR_BUDGET: u64 = 1024;
pub struct MainDbNonceGen {
file_id6: [u8; 6],
next: u64,
durable_anchor: u64,
budget: u64,
}
impl MainDbNonceGen {
#[must_use]
pub fn new(file_id: &[u8; 16], budget: u64) -> Self {
let mut f = [0u8; 6];
f.copy_from_slice(&file_id[..6]);
Self {
file_id6: f,
next: 1,
durable_anchor: 0,
budget,
}
}
#[must_use]
pub fn recover(file_id: &[u8; 16], recovered_anchor: u64, budget: u64) -> Self {
let mut f = [0u8; 6];
f.copy_from_slice(&file_id[..6]);
let next = recovered_anchor.saturating_add(budget).saturating_add(1);
let durable_anchor = next.saturating_sub(1);
Self {
file_id6: f,
next,
durable_anchor,
budget,
}
}
pub fn next_nonce(&mut self) -> Result<Nonce> {
if self.next > Nonce::COUNTER_MAX {
return Err(PagedbError::NonceCounterExhausted);
}
if self.next > self.durable_anchor + self.budget {
return Err(PagedbError::Aborted);
}
let n = Nonce::from_parts(self.file_id6, self.next);
self.next += 1;
Ok(n)
}
#[must_use]
pub fn pending_anchor(&self) -> u64 {
self.next.saturating_sub(1)
}
#[must_use]
pub fn window_remaining(&self) -> u64 {
self.durable_anchor
.saturating_add(self.budget)
.saturating_sub(self.pending_anchor())
}
#[cfg(test)]
#[must_use]
pub fn durable_anchor(&self) -> u64 {
self.durable_anchor
}
pub fn commit_anchor(&mut self, persisted: u64) -> Result<()> {
if persisted < self.durable_anchor || persisted > self.pending_anchor() {
return Err(PagedbError::Aborted);
}
self.durable_anchor = persisted;
Ok(())
}
}
pub struct SegmentNonceGen {
file_id6: [u8; 6],
next: u64,
}
impl SegmentNonceGen {
#[must_use]
pub fn new(segment_id: &[u8; 16]) -> Self {
let mut f = [0u8; 6];
f.copy_from_slice(&segment_id[..6]);
Self {
file_id6: f,
next: 1,
}
}
pub fn next_nonce(&mut self) -> Result<Nonce> {
if self.next > Nonce::COUNTER_MAX {
return Err(PagedbError::NonceCounterExhausted);
}
let n = Nonce::from_parts(self.file_id6, self.next);
self.next += 1;
Ok(n)
}
#[cfg(test)]
#[must_use]
pub fn peek_counter(&self) -> u64 {
self.next
}
#[must_use]
pub fn final_counter(&self) -> u64 {
self.next.saturating_sub(1)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nonce_layout_matches_spec() {
let n = Nonce::from_parts([0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x11], 0x0102_0304_0506);
let bytes = n.as_bytes();
assert_eq!(&bytes[..6], &[0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x11]);
assert_eq!(&bytes[6..], &[0x06, 0x05, 0x04, 0x03, 0x02, 0x01]);
}
#[test]
fn main_db_issues_within_budget() {
let mut g = MainDbNonceGen::new(&[0; 16], 4);
for i in 1..=4 {
let n = g.next_nonce().unwrap();
let mut want = [0u8; 6];
want[0] = u8::try_from(i).unwrap();
assert_eq!(&n.as_bytes()[6..], &want);
}
assert!(matches!(g.next_nonce(), Err(PagedbError::Aborted)));
}
#[test]
fn main_db_commit_anchor_unblocks_issue() {
let mut g = MainDbNonceGen::new(&[0; 16], 4);
for _ in 0..4 {
let _ = g.next_nonce().unwrap();
}
let pending = g.pending_anchor();
assert_eq!(pending, 4);
g.commit_anchor(pending).unwrap();
for _ in 0..4 {
let _ = g.next_nonce().unwrap();
}
assert!(matches!(g.next_nonce(), Err(PagedbError::Aborted)));
}
#[test]
fn window_remaining_tracks_issued_and_committed_nonces() {
let mut g = MainDbNonceGen::new(&[0; 16], 4);
assert_eq!(g.window_remaining(), 4);
let _ = g.next_nonce().unwrap();
let _ = g.next_nonce().unwrap();
assert_eq!(g.window_remaining(), 2);
let pending = g.pending_anchor();
g.commit_anchor(pending).unwrap();
assert_eq!(g.durable_anchor(), 2);
assert_eq!(g.window_remaining(), 4);
for _ in 0..4 {
let _ = g.next_nonce().unwrap();
}
assert_eq!(g.window_remaining(), 0);
assert!(matches!(g.next_nonce(), Err(PagedbError::Aborted)));
}
#[test]
fn main_db_recover_jumps_past_pre_crash_window() {
let g = MainDbNonceGen::recover(&[0; 16], 1000, 1024);
assert_eq!(g.pending_anchor(), 2024);
let mut g = g;
let n = g.next_nonce().unwrap();
let counter_le = &n.as_bytes()[6..];
let mut buf = [0u8; 8];
buf[..6].copy_from_slice(counter_le);
assert_eq!(u64::from_le_bytes(buf), 2025);
}
#[test]
fn segment_counter_progress() {
let mut g = SegmentNonceGen::new(&[1; 16]);
let _ = g.next_nonce().unwrap();
let _ = g.next_nonce().unwrap();
assert_eq!(g.final_counter(), 2);
assert_eq!(g.peek_counter(), 3);
}
}