use super::encoding::Encoding;
use crate::repr::error::StorageError;
use gnitz_wire::{read_u64_le, write_u64_le};
use StorageError::Corrupt;
pub(crate) const SHARD_MAGIC: u64 = 0x31305F5A54494E47;
pub(crate) const SHARD_EPOCH: u64 = 27;
pub(crate) const SHARD_VERSION: u64 = SHARD_EPOCH ^ gnitz_wire::SYS_SCHEMA_DIGEST;
pub(crate) const DIR_ENTRY_SIZE: usize = 16;
pub(crate) const ALIGNMENT: usize = 64;
pub(crate) const OFF_MAGIC: usize = 0;
pub(crate) const OFF_VERSION: usize = 8;
pub(crate) const OFF_ROW_COUNT: usize = 16;
pub(crate) const OFF_DESC_CHECKSUM: usize = 24;
pub(crate) const OFF_FILE_NPC: usize = 32;
pub(crate) const OFF_FLAGS: usize = 40;
pub(crate) const OFF_BODY_CHECKSUM: usize = 48;
pub(crate) const OFF_RETRACTIONS: usize = 56;
pub(crate) const HEADER_SIZE: usize = OFF_RETRACTIONS + 8;
pub(crate) const SHARD_FLAG_SKELETON: u64 = 1;
#[derive(Clone, Copy)]
pub(crate) struct ShardHeader {
pub row_count: usize,
pub retractions: usize,
pub file_npc: usize,
pub skeleton: bool,
pub body_checksum: u64,
}
impl ShardHeader {
pub(crate) fn read(data: &[u8]) -> Result<Self, StorageError> {
if data.len() < HEADER_SIZE {
return Err(Corrupt("shorter than the header"));
}
if read_u64_le(data, OFF_MAGIC) != SHARD_MAGIC {
return Err(Corrupt("magic"));
}
if read_u64_le(data, OFF_VERSION) != SHARD_VERSION {
return Err(Corrupt("version"));
}
let file_npc = usize::try_from(read_u64_le(data, OFF_FILE_NPC))
.ok()
.filter(|&n| n <= gnitz_wire::MAX_COLUMNS)
.ok_or(Corrupt("payload arity"))?;
let row_count = match read_u64_le(data, OFF_ROW_COUNT) {
0 => return Err(Corrupt("no rows")),
n => n as usize,
};
Ok(ShardHeader {
row_count,
retractions: read_u64_le(data, OFF_RETRACTIONS) as usize,
file_npc,
skeleton: read_u64_le(data, OFF_FLAGS) & SHARD_FLAG_SKELETON != 0,
body_checksum: read_u64_le(data, OFF_BODY_CHECKSUM),
})
}
pub(crate) fn write(&self, header: &mut [u8]) {
write_u64_le(header, OFF_MAGIC, SHARD_MAGIC);
write_u64_le(header, OFF_VERSION, SHARD_VERSION);
write_u64_le(header, OFF_ROW_COUNT, self.row_count as u64);
write_u64_le(header, OFF_RETRACTIONS, self.retractions as u64);
write_u64_le(header, OFF_FILE_NPC, self.file_npc as u64);
write_u64_le(header, OFF_FLAGS, if self.skeleton { SHARD_FLAG_SKELETON } else { 0 });
write_u64_le(header, OFF_BODY_CHECKSUM, self.body_checksum);
}
}
pub(crate) const fn dir_entry_off(i: usize) -> usize {
HEADER_SIZE + i * DIR_ENTRY_SIZE
}
pub(crate) struct DirEntry {
pub size: usize,
pub encoding: u8,
}
impl DirEntry {
pub(crate) fn read(image: &[u8], i: usize) -> Self {
let d = dir_entry_off(i);
DirEntry {
size: read_u64_le(image, d) as usize,
encoding: image[d + 8],
}
}
pub(crate) fn write(&self, image: &mut [u8], i: usize) {
let d = dir_entry_off(i);
write_u64_le(image, d, self.size as u64);
image[d + 8] = self.encoding;
}
}
pub(crate) const fn region_start(end: usize) -> usize {
end.next_multiple_of(ALIGNMENT)
}
pub(crate) struct Span {
pub off: usize,
pub size: usize,
pub encoding: Encoding,
}
impl Span {
pub(crate) fn bytes<'a>(&self, image: &'a [u8]) -> &'a [u8] {
&image[self.off..self.off + self.size]
}
}
pub(crate) fn region_spans(image: &[u8], file_npc: usize) -> Result<Vec<Span>, StorageError> {
let file_size = image.len();
let mut end = desc_len(file_npc);
let spans = (0..=gnitz_wire::num_regions(file_npc))
.map(|i| {
let DirEntry { size, encoding } = DirEntry::read(image, i);
let encoding = Encoding::from_wire(encoding).ok_or(Corrupt("encoding"))?;
let off = region_start(end);
if off > file_size || size > file_size - off {
return Err(Corrupt("region past the end"));
}
end = off + size;
Ok(Span { off, size, encoding })
})
.collect::<Result<Vec<_>, _>>()?;
if end != file_size {
return Err(Corrupt("directory does not span the file"));
}
Ok(spans)
}
#[cfg(test)]
pub(crate) fn spans_of(image: &[u8]) -> Vec<Span> {
region_spans(image, ShardHeader::read(image).unwrap().file_npc).unwrap()
}
pub(crate) const fn desc_len(file_npc: usize) -> usize {
dir_entry_off(gnitz_wire::num_regions(file_npc) + 1)
}
pub(crate) fn desc_digest(path: &str, prefix: &[u8]) -> u64 {
let basename = path.rsplit('/').next().unwrap_or(path);
gnitz_wire::digest_with_hole(basename.as_bytes(), prefix, OFF_DESC_CHECKSUM)
}