#![doc = include_str!("../STORAGE.md")]
use std::fmt;
use ::serde::ser::Serialize;
use ::serde::Deserialize;
use vitaminc_aead::frame::__private::SeenKeys;
use vitaminc_aead::frame::{ARRAY, MAX_DEPTH, OBJECT};
use vitaminc_aead::leaf::{self, Form};
use vitaminc_aead::plaintext::Physical;
use vitaminc_aead::tags;
use vitaminc_protected::{Buffer, Controlled};
use crate::serde::{self as transport, from_validated, PathSegment, Profile, Reason};
use crate::write::{write, AsStorage};
pub const MAGIC: [u8; 3] = *b"VCS";
pub const VERSION: u8 = 1;
pub const HEADER: [u8; 4] = [MAGIC[0], MAGIC[1], MAGIC[2], VERSION];
pub const SOME: u8 = tags::STORAGE_SOME;
pub const MAP: u8 = tags::STORAGE_MAP;
const SCALARS: [u8; 17] = [
tags::NULL,
tags::BOOL_FALSE,
tags::BOOL_TRUE,
tags::INT8,
tags::INT16,
tags::INT32,
tags::INT64,
tags::INT128,
tags::UINT8,
tags::UINT16,
tags::UINT32,
tags::UINT64,
tags::UINT128,
tags::FLOAT32,
tags::FLOAT64,
tags::STRING,
tags::BYTES,
];
const KEYS: [u8; 12] = [
tags::INT8,
tags::INT16,
tags::INT32,
tags::INT64,
tags::INT128,
tags::UINT8,
tags::UINT16,
tags::UINT32,
tags::UINT64,
tags::UINT128,
tags::STRING,
tags::BYTES,
];
#[derive(Clone, PartialEq, Eq)]
pub struct Error {
kind: ErrorKind,
inner: transport::Error,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
NotABlob,
UnsupportedVersion,
Malformed,
Mismatch,
Unwritable,
}
impl Error {
fn of(kind: ErrorKind, message: &str) -> Self {
Self {
kind,
inner: transport::Error::new(message),
}
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn message(&self) -> &str {
self.inner.message()
}
pub fn path(&self) -> &[PathSegment] {
self.inner.path()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.inner, f)
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error({:?}, {self})", self.kind)
}
}
impl std::error::Error for Error {}
fn not_a_blob() -> Error {
Error::of(ErrorKind::NotABlob, "not a storage blob")
}
fn unsupported_version() -> Error {
Error::of(
ErrorKind::UnsupportedVersion,
"an unsupported storage version",
)
}
fn malformed() -> Error {
Error::of(ErrorKind::Malformed, "malformed storage blob")
}
fn mismatch(error: transport::Error) -> Error {
match error.reason() {
Reason::Malformed => malformed(),
Reason::UnreadRest => {
Error::of(ErrorKind::Mismatch, "the value did not read the whole blob")
}
Reason::Mismatch => Error {
kind: ErrorKind::Mismatch,
inner: error,
},
}
}
fn unwritable(error: transport::Error) -> Error {
Error {
kind: ErrorKind::Unwritable,
inner: error,
}
}
pub fn from_slice<'de, T: Deserialize<'de>>(bytes: &'de [u8]) -> Result<T, Error> {
let body = body(bytes)?;
validate(body).map_err(|_| malformed())?;
from_validated(body, Profile::Storage).map_err(mismatch)
}
fn body(bytes: &[u8]) -> Result<&[u8], Error> {
let (magic, rest) = bytes.split_first_chunk::<3>().ok_or_else(not_a_blob)?;
if *magic != MAGIC {
return Err(not_a_blob());
}
let (&version, body) = rest.split_first().ok_or_else(not_a_blob)?;
if version != VERSION {
return Err(unsupported_version());
}
Ok(body)
}
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum Invalid {
Malformed,
DuplicateKey,
}
fn validate(body: &[u8]) -> Result<(), Invalid> {
let mut cursor = Cursor { buf: body, pos: 0 };
node(&mut cursor, 0)?;
if cursor.pos != body.len() {
return Err(Invalid::Malformed);
}
Ok(())
}
struct Cursor<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> Cursor<'a> {
fn peek(&self) -> Result<u8, Invalid> {
self.buf.get(self.pos).copied().ok_or(Invalid::Malformed)
}
fn take(&mut self, n: usize) -> Result<&'a [u8], Invalid> {
let end = self.pos.checked_add(n).ok_or(Invalid::Malformed)?;
let bytes = self.buf.get(self.pos..end).ok_or(Invalid::Malformed)?;
self.pos = end;
Ok(bytes)
}
fn count(&mut self) -> Result<usize, Invalid> {
let bytes: [u8; 4] = self.take(4)?.try_into().map_err(|_| Invalid::Malformed)?;
let n = usize::try_from(u32::from_le_bytes(bytes)).map_err(|_| Invalid::Malformed)?;
if n > self.buf.get(self.pos..).map_or(0, <[u8]>::len) {
return Err(Invalid::Malformed);
}
Ok(n)
}
fn key(&mut self) -> Result<&'a str, Invalid> {
let len = self.count()?;
core::str::from_utf8(self.take(len)?).map_err(|_| Invalid::Malformed)
}
fn scalar(&mut self) -> Result<&'a [u8], Invalid> {
let rest = self.buf.get(self.pos..).ok_or(Invalid::Malformed)?;
let (_, used) = leaf::read(rest, Form::Frame).map_err(|_| Invalid::Malformed)?;
self.take(used)
}
fn map_key(&mut self) -> Result<MapKey<'a>, Invalid> {
if !KEYS.contains(&self.peek()?) {
return Err(Invalid::Malformed);
}
self.scalar().map(MapKey)
}
}
fn node(c: &mut Cursor<'_>, depth: usize) -> Result<(), Invalid> {
if depth > MAX_DEPTH {
return Err(Invalid::Malformed);
}
match c.peek()? {
ARRAY => {
c.pos += 1;
for _ in 0..c.count()? {
node(c, depth + 1)?;
}
}
OBJECT => {
c.pos += 1;
let count = c.count()?;
let mut seen = SeenKeys::default();
for _ in 0..count {
if !seen.insert(c.key()?) {
return Err(Invalid::DuplicateKey);
}
node(c, depth + 1)?;
}
}
MAP => {
c.pos += 1;
let count = c.count()?;
let mut keys = Vec::new();
for _ in 0..count {
keys.push(c.map_key()?);
node(c, depth + 1)?;
}
distinct(keys.into_iter())?;
}
SOME => {
c.pos += 1;
node(c, depth + 1)?;
}
tag if SCALARS.contains(&tag) => {
c.scalar()?;
}
_ => return Err(Invalid::Malformed),
}
Ok(())
}
#[derive(Clone, Copy)]
pub(crate) struct MapKey<'a>(pub(crate) &'a [u8]);
#[derive(PartialEq, Eq, PartialOrd, Ord)]
enum Identity<'a> {
Int(bool, u128),
Node(&'a [u8]),
}
impl<'a> MapKey<'a> {
fn identity(self) -> Identity<'a> {
match leaf::read(self.0, Form::Frame) {
Ok(((_, Physical::Int(int)), _)) => {
let (negative, word) = int.sign_and_word();
Identity::Int(negative, word)
}
_ => Identity::Node(self.0),
}
}
}
const INT_IDENTITY: usize = 1 + 16;
pub(crate) fn distinct<'a>(keys: impl ExactSizeIterator<Item = MapKey<'a>>) -> Result<(), Invalid> {
let mut integers = Buffer::with_capacity(keys.len().saturating_mul(INT_IDENTITY));
let mut nodes = Vec::new();
for key in keys {
match key.identity() {
Identity::Int(negative, word) => {
integers.push(u8::from(negative));
integers.extend_from_slice(&word.to_le_bytes());
}
Identity::Node(node) => nodes.push(node),
}
}
let (integers, rest) = integers.risky_inner_mut().as_chunks_mut::<INT_IDENTITY>();
debug_assert!(rest.is_empty(), "every identity is whole");
integers.sort_unstable();
nodes.sort_unstable();
if integers.windows(2).any(|pair| pair[0] == pair[1])
|| nodes.windows(2).any(|pair| pair[0] == pair[1])
{
return Err(Invalid::DuplicateKey);
}
Ok(())
}
pub fn to_vec<T: Serialize + ?Sized>(value: &T) -> Result<Buffer, Error> {
let mut out = Buffer::new();
out.extend_from_slice(&HEADER);
write::<AsStorage, T>(value, &mut out).map_err(unwritable)?;
debug_assert_eq!(
validate(&out.risky_ref()[HEADER.len()..]),
Ok(()),
"the writer wrote a blob the reader refuses"
);
Ok(out)
}
#[cfg(kani)]
mod proofs;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_header_spells_vcs_and_the_version() {
assert_eq!(HEADER, [0x56, 0x43, 0x53, 0x01]);
assert_eq!(SOME, 0xF5);
assert_eq!(MAP, 0xF6);
}
#[test]
fn the_magic_is_no_node_tag() {
for byte in MAGIC {
assert!(!SCALARS.contains(&byte) && ![ARRAY, OBJECT, MAP, SOME].contains(&byte));
}
}
#[test]
fn a_bug_is_reported_as_one() {
let error = mismatch(transport::malformed_frame());
assert_eq!(error, malformed());
}
#[test]
fn wording_does_not_classify_a_refusal() {
let error = mismatch(transport::Error::new("malformed frame"));
assert_eq!(error.kind(), ErrorKind::Mismatch);
}
#[test]
fn map_keys_compare_by_identity() {
let key = |bytes: &'static [u8]| MapKey(bytes).identity();
assert!(key(&[tags::UINT8, 1]) == key(&[tags::INT64, 1, 0, 0, 0, 0, 0, 0, 0]));
assert!(key(&[tags::INT8, 0xFF]) != key(&[tags::UINT8, 0xFF]));
assert!(key(&[tags::STRING, 1, 0, 0, 0, b'a']) != key(&[tags::BYTES, 1, 0, 0, 0, b'a']));
assert!(
distinct([MapKey(&[tags::UINT8, 2]), MapKey(&[tags::UINT8, 1])].into_iter()).is_ok()
);
assert_eq!(
distinct(
[
MapKey(&[tags::UINT8, 1]),
MapKey(&[tags::UINT8, 2]),
MapKey(&[tags::UINT16, 1, 0])
]
.into_iter()
),
Err(Invalid::DuplicateKey)
);
}
}