use journal_core::file::journal_hash_data;
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
mod hash;
mod header;
mod io;
mod validation;
mod walk;
const HEADER_MIN_SIZE: usize = 208;
const OBJECT_HEADER_SIZE: u64 = 16;
const HASH_ITEM_SIZE: u64 = 16;
const DATA_OBJECT_HEADER_SIZE: u64 = 64;
const COMPACT_DATA_OBJECT_HEADER_SIZE: u64 = 72;
const FIELD_OBJECT_HEADER_SIZE: u64 = 40;
const ENTRY_OBJECT_HEADER_SIZE: u64 = 64;
const OFFSET_ARRAY_OBJECT_HEADER_SIZE: u64 = 24;
const REGULAR_ENTRY_ITEM_SIZE: u64 = 16;
const COMPACT_ENTRY_ITEM_SIZE: u64 = 4;
const REGULAR_OFFSET_ARRAY_ITEM_SIZE: u64 = 8;
const COMPACT_OFFSET_ARRAY_ITEM_SIZE: u64 = 4;
const JOURNAL_COMPACT_SIZE_MAX: u64 = u32::MAX as u64;
const MAX_UNCOMPRESSED_DATA_OBJECT_SIZE: usize = 768 * 1024 * 1024;
const OBJECT_TYPE_DATA: u8 = 1;
const OBJECT_TYPE_FIELD: u8 = 2;
const OBJECT_TYPE_ENTRY: u8 = 3;
const OBJECT_TYPE_DATA_HASH_TABLE: u8 = 4;
const OBJECT_TYPE_FIELD_HASH_TABLE: u8 = 5;
const OBJECT_TYPE_ENTRY_ARRAY: u8 = 6;
const OBJECT_TYPE_TAG: u8 = 7;
const OBJECT_COMPRESSED_XZ: u8 = 1 << 0;
const OBJECT_COMPRESSED_LZ4: u8 = 1 << 1;
const OBJECT_COMPRESSED_ZSTD: u8 = 1 << 2;
const OBJECT_COMPRESSED_MASK: u8 =
OBJECT_COMPRESSED_XZ | OBJECT_COMPRESSED_LZ4 | OBJECT_COMPRESSED_ZSTD;
const INCOMPATIBLE_COMPRESSED_XZ: u32 = 1 << 0;
const INCOMPATIBLE_COMPRESSED_LZ4: u32 = 1 << 1;
const INCOMPATIBLE_KEYED_HASH: u32 = 1 << 2;
const INCOMPATIBLE_COMPRESSED_ZSTD: u32 = 1 << 3;
const INCOMPATIBLE_COMPACT: u32 = 1 << 4;
const COMPATIBLE_SEALED: u32 = 1 << 0;
const COMPATIBLE_TAIL_ENTRY_BOOT_ID: u32 = 1 << 1;
const COMPATIBLE_SEALED_CONTINUOUS: u32 = 1 << 2;
const COMPATIBLE_SUPPORTED_MASK: u32 =
COMPATIBLE_SEALED | COMPATIBLE_TAIL_ENTRY_BOOT_ID | COMPATIBLE_SEALED_CONTINUOUS;
const TAG_OBJECT_SIZE: u64 = OBJECT_HEADER_SIZE + 8 + 8 + 32;
#[derive(Clone)]
struct Header {
compatible_flags: u32,
incompatible_flags: u32,
state: u8,
file_id: [u8; 16],
tail_entry_boot_id: [u8; 16],
header_size: u64,
arena_size: u64,
data_hash_table_offset: u64,
data_hash_table_size: u64,
field_hash_table_offset: u64,
field_hash_table_size: u64,
tail_object_offset: u64,
n_objects: u64,
n_entries: u64,
tail_entry_seqnum: u64,
head_entry_seqnum: u64,
entry_array_offset: u64,
head_entry_realtime: u64,
tail_entry_realtime: u64,
tail_entry_monotonic: u64,
n_data: u64,
n_fields: u64,
n_tags: u64,
n_entry_arrays: u64,
tail_entry_offset: u64,
}
#[derive(Clone, Copy)]
struct ObjectHeader {
typ: u8,
flags: u8,
size: u64,
}
#[derive(Clone)]
struct DataObject {
hash: u64,
next_hash_offset: u64,
next_field_offset: u64,
entry_offset: u64,
entry_array_offset: u64,
n_entries: u64,
}
#[derive(Clone)]
struct EntryObject {
seqnum: u64,
realtime: u64,
monotonic: u64,
boot_id: [u8; 16],
items: Vec<u64>,
}
#[derive(Clone)]
struct EntryArray {
next: u64,
items: Vec<u64>,
}
#[derive(Default)]
struct ObjectWalkState {
entry_seqnum: u64,
entry_seqnum_set: bool,
entry_monotonic: u64,
entry_monotonic_set: bool,
entry_boot_id: [u8; 16],
entry_realtime: u64,
entry_realtime_set: bool,
last_tag_realtime: u64,
}
pub(super) fn verify_object_graph(data: &[u8]) -> Result<(), String> {
GraphVerifier::new(data).verify()
}
struct GraphVerifier<'a> {
data: &'a [u8],
header: Header,
compact: bool,
spans: HashMap<u64, ObjectHeader>,
order: Vec<u64>,
data_objects: HashMap<u64, DataObject>,
entry_objects: HashMap<u64, EntryObject>,
entry_arrays: HashMap<u64, EntryArray>,
counts: [u64; 8],
main_entry_array_found: bool,
}
impl<'a> GraphVerifier<'a> {
fn new(data: &'a [u8]) -> Self {
Self {
data,
header: Header::empty(),
compact: false,
spans: HashMap::new(),
order: Vec::new(),
data_objects: HashMap::new(),
entry_objects: HashMap::new(),
entry_arrays: HashMap::new(),
counts: [0; 8],
main_entry_array_found: false,
}
}
fn verify(mut self) -> Result<(), String> {
self.read_header()?;
self.walk_objects()?;
self.validate_header_counts()?;
self.validate_main_entry_array_presence()?;
self.validate_tail_metadata()?;
self.validate_global_entry_array()?;
self.validate_data_hash_table()
}
}
impl Header {
fn empty() -> Self {
Self {
compatible_flags: 0,
incompatible_flags: 0,
state: 0,
file_id: [0; 16],
tail_entry_boot_id: [0; 16],
header_size: 0,
arena_size: 0,
data_hash_table_offset: 0,
data_hash_table_size: 0,
field_hash_table_offset: 0,
field_hash_table_size: 0,
tail_object_offset: 0,
n_objects: 0,
n_entries: 0,
tail_entry_seqnum: 0,
head_entry_seqnum: 0,
entry_array_offset: 0,
head_entry_realtime: 0,
tail_entry_realtime: 0,
tail_entry_monotonic: 0,
n_data: 0,
n_fields: 0,
n_tags: 0,
n_entry_arrays: 0,
tail_entry_offset: 0,
}
}
}