use std::convert::TryInto;
use std::mem;
#[cfg(not(target_arch = "wasm32"))]
use std::time::{SystemTime, UNIX_EPOCH};
use crate::storage::checksum::ChecksumAlgorithm;
use crate::storage::compression::CompressionAlgorithm;
use crate::storage::format::{FileVersion, FormatError, HEADER_SIZE, MAGIC};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FileFlags(pub u32);
impl FileFlags {
pub const ENCRYPTED: u32 = 1 << 1;
pub const VALUE_SEPARATED: u32 = 1 << 2;
pub fn bits(&self) -> u32 {
self.0
}
pub fn is_encrypted(&self) -> bool {
self.0 & Self::ENCRYPTED != 0
}
pub fn is_value_separated(&self) -> bool {
self.0 & Self::VALUE_SEPARATED != 0
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FileHeader {
pub magic: [u8; 4],
pub version: FileVersion,
pub checksum_algorithm: ChecksumAlgorithm,
pub compression_algorithm: CompressionAlgorithm,
pub flags: FileFlags,
pub created_at: u64,
pub modified_at: u64,
pub schema_version: u64,
pub reserved: [u8; 24],
}
const _: () = assert!(mem::size_of::<FileHeader>() == HEADER_SIZE);
impl FileHeader {
pub const SIZE: usize = HEADER_SIZE;
pub fn new(version: FileVersion, flags: FileFlags) -> Self {
let now = now_micros();
Self {
magic: MAGIC,
version,
checksum_algorithm: ChecksumAlgorithm::Crc32,
compression_algorithm: CompressionAlgorithm::Snappy,
flags,
created_at: now,
modified_at: now,
schema_version: 0,
reserved: [0u8; 24],
}
}
pub fn from_bytes(bytes: &[u8; HEADER_SIZE]) -> Result<Self, FormatError> {
let mut magic = [0u8; 4];
magic.copy_from_slice(&bytes[0..4]);
let version = FileVersion {
major: u16::from_le_bytes([bytes[4], bytes[5]]),
minor: u16::from_le_bytes([bytes[6], bytes[7]]),
patch: u16::from_le_bytes([bytes[8], bytes[9]]),
};
let checksum_algorithm = match bytes[10] {
0 => ChecksumAlgorithm::Crc32,
1 => ChecksumAlgorithm::Xxh64,
other => return Err(FormatError::UnsupportedChecksum { algorithm: other }),
};
let compression_algorithm = match bytes[11] {
0 => CompressionAlgorithm::None,
1 => CompressionAlgorithm::Snappy,
2 => CompressionAlgorithm::Zstd,
3 => CompressionAlgorithm::Lz4,
other => return Err(FormatError::UnsupportedCompression { algorithm: other }),
};
let flags = FileFlags(u32::from_le_bytes(
bytes[12..16].try_into().expect("fixed slice length"),
));
let created_at = u64::from_le_bytes(bytes[16..24].try_into().expect("fixed slice length"));
let modified_at = u64::from_le_bytes(bytes[24..32].try_into().expect("fixed slice length"));
let schema_version =
u64::from_le_bytes(bytes[32..40].try_into().expect("fixed slice length"));
let mut reserved = [0u8; 24];
reserved.copy_from_slice(&bytes[40..64]);
let header = Self {
magic,
version,
checksum_algorithm,
compression_algorithm,
flags,
created_at,
modified_at,
schema_version,
reserved,
};
header.validate_magic()?;
Ok(header)
}
pub fn to_bytes(&self) -> [u8; HEADER_SIZE] {
let mut bytes = [0u8; HEADER_SIZE];
bytes[0..4].copy_from_slice(&self.magic);
bytes[4..6].copy_from_slice(&self.version.major.to_le_bytes());
bytes[6..8].copy_from_slice(&self.version.minor.to_le_bytes());
bytes[8..10].copy_from_slice(&self.version.patch.to_le_bytes());
bytes[10] = self.checksum_algorithm as u8;
bytes[11] = self.compression_algorithm as u8;
bytes[12..16].copy_from_slice(&self.flags.bits().to_le_bytes());
bytes[16..24].copy_from_slice(&self.created_at.to_le_bytes());
bytes[24..32].copy_from_slice(&self.modified_at.to_le_bytes());
bytes[32..40].copy_from_slice(&self.schema_version.to_le_bytes());
bytes[40..64].copy_from_slice(&self.reserved);
bytes
}
pub fn validate_magic(&self) -> Result<(), FormatError> {
if self.magic == MAGIC {
Ok(())
} else {
Err(FormatError::InvalidMagic { found: self.magic })
}
}
pub fn check_compatibility(&self, reader_version: &FileVersion) -> Result<(), FormatError> {
if self.version > *reader_version {
Err(FormatError::IncompatibleVersion {
file: self.version,
reader: *reader_version,
})
} else {
Ok(())
}
}
}
#[cfg(not(target_arch = "wasm32"))]
fn now_micros() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64
}
#[cfg(target_arch = "wasm32")]
fn now_micros() -> u64 {
#[cfg(feature = "wasm-indexeddb")]
{
(js_sys::Date::now() * 1000.0) as u64
}
#[cfg(not(feature = "wasm-indexeddb"))]
{
0
}
}