use crate::{DataOffset, crypto::Crypto, journal::SequenceId};
pub type Offset = u64;
pub type ByteCountU64 = u64;
pub type ByteCountU32 = u32;
pub type ChunkIndex = u32;
#[derive(serde::Serialize, serde::Deserialize, Clone)]
pub struct Sha256Hash(pub [u8; 32]);
impl Sha256Hash {
pub fn from_array(data: [u8; 32]) -> Self {
Self(data)
}
}
pub type KeyPath = String;
impl std::fmt::Debug for Sha256Hash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Sha256Hash")
.field(&format_args!(
"{:x}",
generic_array::GenericArray::from(self.0)
))
.finish()
}
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct KeyMeta {
pub size: ByteCountU64,
pub chunk_size: Option<ByteCountU32>,
pub hash: Sha256Hash,
pub path: KeyPath,
}
impl KeyMeta {
pub fn chunk_count(&self) -> ChunkIndex {
if let Some(chunk_size) = self.chunk_size {
compute_chunk_count(self.size, chunk_size)
} else {
1
}
}
}
pub fn compute_chunk_count(data_size: ByteCountU64, chunk_size: ByteCountU32) -> ChunkIndex {
data_size.div_ceil(chunk_size as u64) as ChunkIndex
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct KeyRename {
pub old_key: KeyPath,
pub new_key: KeyPath,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct ActionKeyInsert {
pub meta: KeyMeta,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct ActionKeyRename {
pub renames: Vec<KeyRename>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct ActionKeyDelete {
pub deleted_keys: Vec<KeyPath>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct KeyIndexEntry {
pub key: KeyPath,
pub sequence_id: SequenceId,
pub file_offset: DataOffset,
pub size: ByteCountU64,
pub chunk_size: Option<ByteCountU32>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct KeyIndex {
pub parent_entry: Option<EntryPointer>,
pub keys: Vec<KeyIndexEntry>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub enum CompressionFormat {
Brotli,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct ActionIndexWrite {
pub size: ByteCountU64,
pub hash: Sha256Hash,
pub compression: Option<CompressionFormat>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub enum BatchItem {
Rename(KeyRename),
Delete(ActionKeyDelete),
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct ActionBatch {
pub renames: Vec<KeyRename>,
pub deleted_keys: Vec<String>,
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub enum JournalAction {
KeyInsert(ActionKeyInsert),
KeyRename(ActionKeyRename),
KeyDelete(ActionKeyDelete),
IndexWrite(ActionIndexWrite),
Batch(ActionBatch),
}
impl JournalAction {
pub fn payload_len(&self, crypto: Option<&Crypto>) -> u64 {
match self {
Self::KeyInsert(key) => {
let padding = crypto.map(|c| c.extra_payload_len()).unwrap_or(0);
key.meta.size + (padding * key.meta.chunk_count() as u64)
}
Self::KeyRename(_) => 0,
Self::KeyDelete(_) => 0,
Self::Batch(_) => 0,
Self::IndexWrite(w) => {
let padding = crypto.map(|c| c.extra_payload_len()).unwrap_or(0);
w.size + padding
}
}
}
#[must_use]
pub fn is_index_write(&self) -> bool {
matches!(self, Self::IndexWrite(..))
}
}
bitflags::bitflags! {
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
#[repr(transparent)]
pub struct JournalEntryHeaderFlags: u32 {
const INCOMPLETE = 0b00000001;
}
}
#[derive(serde::Serialize, serde::Deserialize, Debug)]
pub struct JournalEntryHeader {
pub offset: Offset,
pub sequence_id: SequenceId,
pub action_size: ByteCountU32,
pub flags: JournalEntryHeaderFlags,
}
impl JournalEntryHeader {
pub const SERIALIZED_LEN: usize = 24;
}
#[derive(Debug)]
pub struct JournalEntry {
pub header: JournalEntryHeader,
pub action: JournalAction,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
pub struct EntryPointer {
pub sequence: SequenceId,
pub offset: Offset,
}
bitflags::bitflags! {
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
pub struct SuperblockFlags : u32 {
}
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
#[repr(u32)]
pub enum LogFormatVersion {
V1 = 1,
V2 = 2,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct Superblock {
pub format_version: LogFormatVersion,
pub flags: SuperblockFlags,
pub active_sequence: u64,
pub tail_offset: Offset,
pub last_index_entry: Option<EntryPointer>,
}
impl Superblock {
pub const SERIALIZED_LEN: u64 = 256;
pub const HEADER_COUNT: u64 = 10;
pub const HEADER_SIZE: u64 = Self::SERIALIZED_LEN * Self::HEADER_COUNT;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_entry_header_size() {
let header = JournalEntryHeader {
offset: 0,
sequence_id: SequenceId::first(),
action_size: 0,
flags: JournalEntryHeaderFlags::empty(),
};
let code = bincode::serialize(&header).unwrap();
assert_eq!(code.len(), JournalEntryHeader::SERIALIZED_LEN);
}
}