use memmap2::Mmap;
use std::fs::File;
use std::path::Path;
use anyhow::{Result, Context, anyhow};
use crate::cache::{CACHE_MAGIC, CACHE_VERSION, HEADER_SIZE, CacheHeader, CacheError};
pub struct MmapCache {
mmap: Mmap,
pub event_count: usize,
}
impl MmapCache {
pub fn open(path: &Path) -> Result<Self> {
let file = File::open(path).with_context(|| format!("open cache file {}", path.display()))?;
#[allow(unsafe_code)]
let mmap = unsafe { Mmap::map(&file).with_context(|| format!("mmap cache file {}", path.display()))? };
if mmap.len() < HEADER_SIZE {
return Err(anyhow!(CacheError::UnexpectedEof));
}
let magic: [u8; 4] = mmap[0..4].try_into().unwrap();
if magic != CACHE_MAGIC {
return Err(anyhow!(CacheError::InvalidMagic(magic)));
}
let version = mmap[4];
if version > CACHE_VERSION {
return Err(anyhow!(CacheError::UnsupportedVersion(version)));
}
let row_count = u64::from_le_bytes(mmap[8..16].try_into().unwrap()) as usize;
Ok(Self {
mmap,
event_count: row_count,
})
}
pub fn column_bytes(&self, offset: usize, len: usize) -> &[u8] {
&self.mmap[offset..offset+len]
}
pub fn is_valid(&self) -> bool {
if self.mmap.len() < HEADER_SIZE {
return false;
}
let magic: [u8; 4] = self.mmap[0..4].try_into().unwrap();
if magic != CACHE_MAGIC {
return false;
}
let version = self.mmap[4];
if version > CACHE_VERSION {
return false;
}
let column_count = self.mmap[5] as usize;
let stored_crc = u64::from_le_bytes(self.mmap[24..32].try_into().unwrap());
let offsets_start = HEADER_SIZE;
let offsets_end = offsets_start + column_count * 8;
if self.mmap.len() < offsets_end {
return false;
}
let col_data_start = offsets_end;
if self.mmap.len() < col_data_start {
return false;
}
let col_data = &self.mmap[col_data_start..];
let actual_crc = checksum(col_data);
actual_crc == stored_crc
}
pub fn header(&self) -> Result<CacheHeader> {
if self.mmap.len() < HEADER_SIZE {
return Err(anyhow!(CacheError::UnexpectedEof));
}
let version = self.mmap[4];
let column_count = self.mmap[5];
let row_count = u64::from_le_bytes(self.mmap[8..16].try_into().unwrap());
let created_at_us = u64::from_le_bytes(self.mmap[16..24].try_into().unwrap());
let data_crc64 = u64::from_le_bytes(self.mmap[24..32].try_into().unwrap());
Ok(CacheHeader {
version,
column_count,
row_count,
created_at_us,
data_crc64,
})
}
pub fn as_slice(&self) -> &[u8] {
&self.mmap
}
}
fn checksum(data: &[u8]) -> u64 {
const POLY: u64 = 0xC96C5795D7870F42;
let mut crc: u64 = u64::MAX;
for &byte in data {
crc ^= u64::from(byte) << 56;
for _ in 0..8 {
if crc & (1 << 63) != 0 {
crc = (crc << 1) ^ POLY;
} else {
crc <<= 1;
}
}
}
!crc
}