use alloc::{boxed::Box, string::String, vec::Vec};
use core::{fmt, ops::Deref};
use buggy::Bug;
use serde::{Deserialize, Serialize};
use crate::{Address, CmdId, Command, PolicyId, Prior};
pub mod linear;
pub const QUEUE_CAPACITY: usize = 512;
#[derive(Debug, Default)]
pub struct TraversalQueue {
entries: heapless::Vec<Location, QUEUE_CAPACITY>,
}
impl TraversalQueue {
pub const fn new() -> Self {
Self {
entries: heapless::Vec::new(),
}
}
pub fn clear(&mut self) {
self.entries.clear();
}
pub fn push(&mut self, loc: Location) -> Result<(), StorageError> {
if let Some(prev) = self.entries.iter_mut().find(|x| x.same_segment(loc)) {
prev.max_cut = prev.max_cut.max(loc.max_cut);
return Ok(());
}
self.entries
.push(loc)
.map_err(|_| StorageError::TraversalQueueOverflow(QUEUE_CAPACITY))
}
pub fn pop(&mut self) -> Option<Location> {
let (i, _) = self
.entries
.iter()
.enumerate()
.max_by_key(|&(_, &loc)| loc)?;
Some(self.entries.swap_remove(i))
}
}
pub struct TraversalBuffer {
queue: TraversalQueue,
}
impl TraversalBuffer {
pub const fn new() -> Self {
Self {
queue: TraversalQueue::new(),
}
}
pub fn get(&mut self) -> &mut TraversalQueue {
self.queue.clear();
&mut self.queue
}
}
impl Default for TraversalBuffer {
fn default() -> Self {
Self::new()
}
}
pub struct TraversalBuffers {
pub primary: TraversalBuffer,
pub secondary: TraversalBuffer,
}
impl TraversalBuffers {
pub const fn new() -> Self {
Self {
primary: TraversalBuffer::new(),
secondary: TraversalBuffer::new(),
}
}
}
impl Default for TraversalBuffers {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "low-mem-usage")]
pub const MAX_COMMAND_LENGTH: usize = 400;
#[cfg(not(feature = "low-mem-usage"))]
pub const MAX_COMMAND_LENGTH: usize = 2048;
aranya_crypto::custom_id! {
pub struct GraphId;
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[repr(transparent)]
pub struct SegmentIndex(pub usize);
impl fmt::Display for SegmentIndex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[repr(transparent)]
pub struct MaxCut(pub usize);
impl fmt::Display for MaxCut {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl MaxCut {
#[must_use]
pub fn checked_add(self, other: usize) -> Option<Self> {
self.0.checked_add(other).map(Self)
}
#[must_use]
pub fn decremented(self) -> Option<Self> {
self.0.checked_sub(1).map(Self)
}
#[must_use]
pub fn distance_from(self, other: Self) -> Option<usize> {
self.0.checked_sub(other.0)
}
}
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Location {
pub max_cut: MaxCut,
pub segment: SegmentIndex,
}
impl From<(SegmentIndex, MaxCut)> for Location {
fn from((segment, max_cut): (SegmentIndex, MaxCut)) -> Self {
Self::new(segment, max_cut)
}
}
impl AsRef<Self> for Location {
fn as_ref(&self) -> &Self {
self
}
}
impl Location {
pub fn new(segment: SegmentIndex, max_cut: MaxCut) -> Self {
Self { max_cut, segment }
}
pub fn same_segment(self, other: Self) -> bool {
self.segment == other.segment
}
}
impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}:{}", self.segment, self.max_cut)
}
}
#[derive(Debug, PartialEq, Eq, thiserror::Error)]
pub enum StorageError {
#[error("storage already exists")]
StorageExists,
#[error("no such storage")]
NoSuchStorage,
#[error("segment index {} is out of bounds", .0.segment)]
SegmentOutOfBounds(Location),
#[error("max cut {} is out of bounds in segment {}", .0.max_cut, .0.segment)]
CommandOutOfBounds(Location),
#[error("IO error")]
IoError,
#[error("policy mismatch")]
PolicyMismatch,
#[error("cannot write an empty perspective")]
EmptyPerspective,
#[error("traversal queue overflow (capacity {0})")]
TraversalQueueOverflow(usize),
#[error("command's parents do not match the perspective head")]
PerspectiveHeadMismatch,
#[error(transparent)]
Bug(#[from] Bug),
}
pub trait StorageProvider {
type Perspective: Perspective + Revertable;
type Segment: Segment;
type Storage: Storage<
Segment = Self::Segment,
Perspective = Self::Perspective,
FactIndex = <Self::Segment as Segment>::FactIndex,
>;
fn new_perspective(&mut self, policy_id: PolicyId) -> Self::Perspective;
fn new_storage(
&mut self,
init: Self::Perspective,
) -> Result<(GraphId, &mut Self::Storage), StorageError>;
fn get_storage(&mut self, graph: GraphId) -> Result<&mut Self::Storage, StorageError>;
fn remove_storage(&mut self, graph: GraphId) -> Result<(), StorageError>;
fn list_graph_ids(
&mut self,
) -> Result<impl Iterator<Item = Result<GraphId, StorageError>>, StorageError>;
}
pub trait Storage {
type Perspective: Perspective + Revertable;
type FactPerspective: FactPerspective;
type Segment: Segment<FactIndex = Self::FactIndex>;
type FactIndex: FactIndex;
fn get_location(
&self,
address: Address,
buffer: &mut TraversalBuffer,
) -> Result<Option<Location>, StorageError> {
self.get_location_from(self.get_head()?, address, buffer)
}
fn get_location_from(
&self,
start: Location,
address: Address,
buffer: &mut TraversalBuffer,
) -> Result<Option<Location>, StorageError> {
if start.max_cut < address.max_cut {
return Ok(None);
}
let queue = buffer.get();
queue.push(start)?;
while let Some(loc) = queue.pop() {
debug_assert!(
loc.max_cut >= address.max_cut,
"Invariant: we only enqueue locations with at least the target max cut"
);
let segment = self.get_segment(loc)?;
if let Some(found) = segment.get_by_address(address) {
return Ok(Some(found));
}
if let Some(&skip) = segment
.skip_list()
.iter()
.find(|skip| skip.max_cut >= address.max_cut)
{
queue.push(skip)?;
} else {
for prior in segment.prior() {
if prior.max_cut >= address.max_cut {
queue.push(prior)?;
}
}
}
}
Ok(None)
}
fn get_command_address(&self, location: Location) -> Result<Address, StorageError> {
let segment = self.get_segment(location)?;
let command = segment
.get_command(location)
.ok_or(StorageError::CommandOutOfBounds(location))?;
let address = command.address()?;
Ok(address)
}
fn get_linear_perspective(&self, parent: Location) -> Result<Self::Perspective, StorageError>;
fn get_fact_perspective(&self, first: Location) -> Result<Self::FactPerspective, StorageError>;
fn new_merge_perspective(
&self,
left: Location,
right: Location,
last_common_ancestor: Location,
policy_id: PolicyId,
braid: Self::FactIndex,
) -> Result<Self::Perspective, StorageError>;
fn get_segment(&self, location: Location) -> Result<Self::Segment, StorageError>;
fn get_head(&self) -> Result<Location, StorageError>;
fn get_head_address(&self) -> Result<Address, StorageError> {
self.get_command_address(self.get_head()?)
}
fn commit(&mut self, segment: Self::Segment) -> Result<(), StorageError>;
fn write(&mut self, perspective: Self::Perspective) -> Result<Self::Segment, StorageError>;
fn write_facts(
&mut self,
fact_perspective: Self::FactPerspective,
) -> Result<Self::FactIndex, StorageError>;
fn is_ancestor(
&self,
search_location: Location,
segment: &Self::Segment,
buffer: &mut TraversalBuffer,
) -> Result<bool, StorageError> {
let queue = buffer.get();
if let Some(&skip) = segment
.skip_list()
.iter()
.find(|skip| skip.max_cut >= search_location.max_cut)
{
queue.push(skip)?;
} else {
for prior in segment.prior() {
if prior.max_cut >= search_location.max_cut {
queue.push(prior)?;
}
}
}
while let Some(loc) = queue.pop() {
debug_assert!(
loc.max_cut >= search_location.max_cut,
"Invariant: we only enqueue locations with at least the target max cut"
);
let segment = self.get_segment(loc)?;
if segment.get_command(search_location).is_some() {
return Ok(true);
}
if let Some(&skip) = segment
.skip_list()
.iter()
.find(|skip| skip.max_cut >= search_location.max_cut)
{
queue.push(skip)?;
} else {
for prior in segment.prior() {
if prior.max_cut >= search_location.max_cut {
queue.push(prior)?;
}
}
}
}
Ok(false)
}
}
pub trait Segment {
type FactIndex: FactIndex;
type Command<'a>: Command
where
Self: 'a;
fn index(&self) -> SegmentIndex;
fn head_id(&self) -> CmdId;
fn policy(&self) -> PolicyId;
fn prior(&self) -> Prior<Location>;
fn get_command(&self, location: Location) -> Option<Self::Command<'_>>;
fn facts(&self) -> Result<Self::FactIndex, StorageError>;
fn shortest_max_cut(&self) -> MaxCut;
fn longest_max_cut(&self) -> Result<MaxCut, StorageError>;
fn skip_list(&self) -> &[Location];
fn get_from(&self, location: Location) -> Vec<Self::Command<'_>> {
let segment = location.segment;
core::iter::successors(Some(location.max_cut), |max_cut| max_cut.checked_add(1))
.map_while(|max_cut| self.get_command(Location { max_cut, segment }))
.collect()
}
fn get_by_address(&self, address: Address) -> Option<Location> {
let loc = Location::new(self.index(), address.max_cut);
let cmd = self.get_command(loc)?;
if cmd.id() != address.id {
return None;
}
Some(loc)
}
fn first_location(&self) -> Location {
Location {
max_cut: self.shortest_max_cut(),
segment: self.index(),
}
}
fn head_location(&self) -> Result<Location, StorageError> {
Ok(Location {
max_cut: self.longest_max_cut()?,
segment: self.index(),
})
}
fn head_address(&self) -> Result<Address, StorageError> {
Ok(Address {
id: self.head_id(),
max_cut: self.longest_max_cut()?,
})
}
#[must_use]
fn previous(&self, mut location: Location) -> Option<Location> {
debug_assert_eq!(location.segment, self.index());
if location.max_cut <= self.shortest_max_cut() {
return None;
}
location.max_cut = location.max_cut.decremented()?;
Some(location)
}
}
pub trait FactIndex: Query {}
pub trait Perspective: FactPerspective {
fn policy(&self) -> PolicyId;
fn add_command(&mut self, command: &impl Command) -> Result<usize, StorageError>;
fn includes(&self, id: CmdId) -> bool;
fn head_address(&self) -> Result<Prior<Address>, Bug>;
}
pub trait FactPerspective: QueryMut {}
pub trait Revertable {
fn checkpoint(&self) -> Checkpoint;
fn revert(&mut self, checkpoint: Checkpoint) -> Result<(), Bug>;
}
pub struct Checkpoint {
pub index: usize,
}
pub trait Query {
fn query(&self, name: &str, keys: &[Box<[u8]>]) -> Result<Option<Box<[u8]>>, StorageError>;
type QueryIterator: Iterator<Item = Result<Fact, StorageError>>;
fn query_prefix(
&self,
name: &str,
prefix: &[Box<[u8]>],
) -> Result<Self::QueryIterator, StorageError>;
}
#[derive(Debug, PartialEq, Eq)]
pub struct Fact {
pub key: Keys,
pub value: Box<[u8]>,
}
pub trait QueryMut: Query {
fn insert(&mut self, name: String, keys: Keys, value: Box<[u8]>);
fn delete(&mut self, name: String, keys: Keys);
}
#[cfg(all(test, feature = "graphviz"))]
pub(crate) trait FactIndexExtra {
fn name(&self) -> String;
fn prior(&self) -> Result<Option<Self>, StorageError>
where
Self: Sized;
}
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Keys(Box<[Box<[u8]>]>);
impl Deref for Keys {
type Target = [Box<[u8]>];
fn deref(&self) -> &[Box<[u8]>] {
self.0.as_ref()
}
}
impl AsRef<[Box<[u8]>]> for Keys {
fn as_ref(&self) -> &[Box<[u8]>] {
self.0.as_ref()
}
}
impl core::borrow::Borrow<[Box<[u8]>]> for Keys {
fn borrow(&self) -> &[Box<[u8]>] {
self.0.as_ref()
}
}
impl From<&[&[u8]]> for Keys {
fn from(value: &[&[u8]]) -> Self {
value.iter().copied().collect()
}
}
impl Keys {
fn starts_with(&self, prefix: &[Box<[u8]>]) -> bool {
self.as_ref().starts_with(prefix)
}
}
impl<B: Into<Box<[u8]>>> FromIterator<B> for Keys {
fn from_iter<T: IntoIterator<Item = B>>(iter: T) -> Self {
Self(iter.into_iter().map(Into::into).collect())
}
}
#[cfg(test)]
mod queue_tests {
use super::*;
fn loc(seg: usize, mc: usize) -> Location {
Location::new(SegmentIndex(seg), MaxCut(mc))
}
#[test]
fn test_queue_overflow_returns_error() {
let mut queue = TraversalQueue::new();
for i in 0..QUEUE_CAPACITY {
queue.push(loc(i, i)).unwrap();
}
let result = queue
.push(loc(999, 999))
.expect_err("expected push_queue to fail");
assert_eq!(result, StorageError::TraversalQueueOverflow(QUEUE_CAPACITY));
}
}