use crate::error::ShardexError;
use bytemuck::{Pod, Zeroable};
use memmap2::{Mmap, MmapMut, MmapOptions};
use std::fs::{File, OpenOptions};
use std::path::Path;
#[derive(Debug)]
pub struct MemoryMappedFile {
mmap: MmapVariant,
file: Option<File>,
len: usize,
}
#[derive(Debug)]
enum MmapVariant {
ReadOnly(Mmap),
ReadWrite(MmapMut),
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct StandardHeader {
pub magic: [u8; 4],
pub version: u32,
pub header_size: u32,
pub data_offset: u64,
pub checksum: u32,
pub created_at: u64,
pub modified_at: u64,
pub reserved: [u8; 32],
}
pub type FileHeader = StandardHeader;
unsafe impl Pod for StandardHeader {}
unsafe impl Zeroable for StandardHeader {}
impl MemoryMappedFile {
pub fn create<P: AsRef<Path>>(path: P, size: usize) -> Result<Self, ShardexError> {
let path = path.as_ref();
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
ShardexError::MemoryMapping(format!(
"Failed to create parent directories for {}: {}",
path.display(),
e
))
})?;
}
let file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.truncate(true)
.open(path)
.map_err(|e| ShardexError::MemoryMapping(format!("Failed to create file {}: {}", path.display(), e)))?;
file.set_len(size as u64).map_err(|e| {
ShardexError::MemoryMapping(format!("Failed to set file size for {}: {}", path.display(), e))
})?;
let mmap = unsafe {
MmapOptions::new().map_mut(&file).map_err(|e| {
ShardexError::MemoryMapping(format!("Failed to create memory mapping for {}: {}", path.display(), e))
})?
};
Ok(Self {
mmap: MmapVariant::ReadWrite(mmap),
file: Some(file),
len: size,
})
}
pub fn open_read_only<P: AsRef<Path>>(path: P) -> Result<Self, ShardexError> {
let path = path.as_ref();
let file = File::open(path)
.map_err(|e| ShardexError::MemoryMapping(format!("Failed to open file {}: {}", path.display(), e)))?;
let len = file
.metadata()
.map_err(|e| {
ShardexError::MemoryMapping(format!("Failed to get file metadata for {}: {}", path.display(), e))
})?
.len() as usize;
let mmap = unsafe {
MmapOptions::new().map(&file).map_err(|e| {
ShardexError::MemoryMapping(format!(
"Failed to create read-only memory mapping for {}: {}",
path.display(),
e
))
})?
};
Ok(Self {
mmap: MmapVariant::ReadOnly(mmap),
file: Some(file),
len,
})
}
pub fn open_read_write<P: AsRef<Path>>(path: P) -> Result<Self, ShardexError> {
let path = path.as_ref();
let file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.map_err(|e| {
ShardexError::MemoryMapping(format!("Failed to open file {} for read-write: {}", path.display(), e))
})?;
let len = file
.metadata()
.map_err(|e| {
ShardexError::MemoryMapping(format!("Failed to get file metadata for {}: {}", path.display(), e))
})?
.len() as usize;
let mmap = unsafe {
MmapOptions::new().map_mut(&file).map_err(|e| {
ShardexError::MemoryMapping(format!(
"Failed to create read-write memory mapping for {}: {}",
path.display(),
e
))
})?
};
Ok(Self {
mmap: MmapVariant::ReadWrite(mmap),
file: Some(file),
len,
})
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn as_slice(&self) -> &[u8] {
match &self.mmap {
MmapVariant::ReadOnly(mmap) => mmap.as_ref(),
MmapVariant::ReadWrite(mmap) => mmap.as_ref(),
}
}
pub fn as_mut_slice(&mut self) -> Result<&mut [u8], ShardexError> {
match &mut self.mmap {
MmapVariant::ReadOnly(_) => Err(ShardexError::MemoryMapping(
"Cannot get mutable reference to read-only mapping".to_string(),
)),
MmapVariant::ReadWrite(mmap) => Ok(mmap.as_mut()),
}
}
pub fn read_at<T: Pod>(&self, offset: usize) -> Result<T, ShardexError> {
let size = std::mem::size_of::<T>();
if offset + size > self.len {
return Err(ShardexError::MemoryMapping(format!(
"Read at offset {} with size {} exceeds file length {}",
offset, size, self.len
)));
}
if offset % std::mem::align_of::<T>() != 0 {
return Err(ShardexError::MemoryMapping(format!(
"Offset {} is not properly aligned for type {} (requires {} byte alignment)",
offset,
std::any::type_name::<T>(),
std::mem::align_of::<T>()
)));
}
let bytes = &self.as_slice()[offset..offset + size];
Ok(bytemuck::pod_read_unaligned(bytes))
}
pub fn write_at<T: Pod>(&mut self, offset: usize, value: &T) -> Result<(), ShardexError> {
let size = std::mem::size_of::<T>();
if offset + size > self.len {
return Err(ShardexError::MemoryMapping(format!(
"Write at offset {} with size {} exceeds file length {}",
offset, size, self.len
)));
}
if offset % std::mem::align_of::<T>() != 0 {
return Err(ShardexError::MemoryMapping(format!(
"Offset {} is not properly aligned for type {} (requires {} byte alignment)",
offset,
std::any::type_name::<T>(),
std::mem::align_of::<T>()
)));
}
let bytes = bytemuck::bytes_of(value);
let mut_slice = self.as_mut_slice()?;
mut_slice[offset..offset + size].copy_from_slice(bytes);
Ok(())
}
pub fn read_slice_at<T: Pod>(&self, offset: usize, count: usize) -> Result<&[T], ShardexError> {
let size = std::mem::size_of::<T>() * count;
if offset + size > self.len {
return Err(ShardexError::MemoryMapping(format!(
"Read slice at offset {} with size {} exceeds file length {}",
offset, size, self.len
)));
}
if offset % std::mem::align_of::<T>() != 0 {
return Err(ShardexError::MemoryMapping(format!(
"Offset {} is not properly aligned for type {} (requires {} byte alignment)",
offset,
std::any::type_name::<T>(),
std::mem::align_of::<T>()
)));
}
let bytes = &self.as_slice()[offset..offset + size];
Ok(bytemuck::cast_slice(bytes))
}
pub fn write_slice_at<T: Pod>(&mut self, offset: usize, values: &[T]) -> Result<(), ShardexError> {
let size = std::mem::size_of_val(values);
if offset + size > self.len {
return Err(ShardexError::MemoryMapping(format!(
"Write slice at offset {} with size {} exceeds file length {}",
offset, size, self.len
)));
}
if offset % std::mem::align_of::<T>() != 0 {
return Err(ShardexError::MemoryMapping(format!(
"Offset {} is not properly aligned for type {} (requires {} byte alignment)",
offset,
std::any::type_name::<T>(),
std::mem::align_of::<T>()
)));
}
let bytes = bytemuck::cast_slice(values);
let mut_slice = self.as_mut_slice()?;
mut_slice[offset..offset + size].copy_from_slice(bytes);
Ok(())
}
pub fn resize(&mut self, new_size: usize) -> Result<(), ShardexError> {
let file = self
.file
.as_ref()
.ok_or_else(|| ShardexError::MemoryMapping("No file handle available for resize".to_string()))?;
if matches!(self.mmap, MmapVariant::ReadOnly(_)) {
return Err(ShardexError::MemoryMapping(
"Cannot resize read-only memory mapping".to_string(),
));
}
self.mmap = MmapVariant::ReadWrite(
MmapMut::map_anon(0)
.map_err(|e| ShardexError::MemoryMapping(format!("Failed to create temporary mapping: {}", e)))?,
);
file.set_len(new_size as u64)
.map_err(|e| ShardexError::MemoryMapping(format!("Failed to resize file: {}", e)))?;
let new_mmap = unsafe {
MmapOptions::new()
.map_mut(file)
.map_err(|e| ShardexError::MemoryMapping(format!("Failed to create new memory mapping: {}", e)))?
};
self.mmap = MmapVariant::ReadWrite(new_mmap);
self.len = new_size;
Ok(())
}
pub fn sync(&self) -> Result<(), ShardexError> {
match &self.mmap {
MmapVariant::ReadOnly(_) => Ok(()), MmapVariant::ReadWrite(mmap) => {
mmap.flush()
.map_err(|e| ShardexError::MemoryMapping(format!("Failed to sync memory mapping: {}", e)))?;
Ok(())
}
}
}
pub fn is_read_only(&self) -> bool {
matches!(self.mmap, MmapVariant::ReadOnly(_))
}
}
impl StandardHeader {
pub const SIZE: usize = std::mem::size_of::<StandardHeader>();
fn current_timestamp() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_micros() as u64
}
pub fn new(magic: &[u8; 4], version: u32, data_offset: u64, data: &[u8]) -> Self {
let now = Self::current_timestamp();
let mut header = Self {
magic: *magic,
version,
header_size: Self::SIZE as u32,
data_offset,
checksum: 0, created_at: now,
modified_at: now,
reserved: [0; 32],
};
header.update_checksum(data);
header
}
pub fn new_without_checksum(magic: &[u8; 4], version: u32, data_offset: u64) -> Self {
let now = Self::current_timestamp();
Self {
magic: *magic,
version,
header_size: Self::SIZE as u32,
data_offset,
checksum: 0,
created_at: now,
modified_at: now,
reserved: [0; 32],
}
}
pub fn new_with_timestamps(
magic: &[u8; 4],
version: u32,
data_offset: u64,
created_at: u64,
modified_at: u64,
data: &[u8],
) -> Self {
let mut header = Self {
magic: *magic,
version,
header_size: Self::SIZE as u32,
data_offset,
checksum: 0,
created_at,
modified_at,
reserved: [0; 32],
};
header.update_checksum(data);
header
}
pub fn validate_magic(&self, expected_magic: &[u8; 4]) -> Result<(), ShardexError> {
if &self.magic != expected_magic {
return Err(ShardexError::Corruption(format!(
"Magic bytes mismatch: expected {:?}, found {:?}",
expected_magic, self.magic
)));
}
Ok(())
}
pub fn validate_version(&self, min_version: u32, max_version: u32) -> Result<(), ShardexError> {
if self.version < min_version {
return Err(ShardexError::Corruption(format!(
"Version {} is too old (minimum supported: {})",
self.version, min_version
)));
}
if self.version > max_version {
return Err(ShardexError::Corruption(format!(
"Version {} is too new (maximum supported: {})",
self.version, max_version
)));
}
Ok(())
}
pub fn is_version(&self, expected_version: u32) -> bool {
self.version == expected_version
}
pub fn is_compatible(&self, min_version: u32, max_version: u32) -> bool {
self.version >= min_version && self.version <= max_version
}
pub fn validate_structure(&self) -> Result<(), ShardexError> {
if self.header_size != Self::SIZE as u32 {
return Err(ShardexError::Corruption(format!(
"Invalid header size: expected {}, found {}",
Self::SIZE,
self.header_size
)));
}
if self.data_offset < Self::SIZE as u64 {
return Err(ShardexError::Corruption(format!(
"Invalid data offset: {} is less than header size {}",
self.data_offset,
Self::SIZE
)));
}
let now = Self::current_timestamp();
if self.created_at > now {
return Err(ShardexError::Corruption(format!(
"Creation timestamp {} is in the future",
self.created_at
)));
}
if self.modified_at > now {
return Err(ShardexError::Corruption(format!(
"Modification timestamp {} is in the future",
self.modified_at
)));
}
if self.created_at > self.modified_at {
return Err(ShardexError::Corruption(format!(
"Creation timestamp {} is after modification timestamp {}",
self.created_at, self.modified_at
)));
}
if self.reserved != [0; 32] {
return Err(ShardexError::Corruption("Reserved bytes are not zero".to_string()));
}
Ok(())
}
pub fn validate_checksum(&self, data: &[u8]) -> Result<(), ShardexError> {
let expected_checksum = Self::calculate_checksum_with_header(self, data);
if self.checksum != expected_checksum {
return Err(ShardexError::Corruption(format!(
"Checksum mismatch: expected {}, found {}",
expected_checksum, self.checksum
)));
}
Ok(())
}
pub fn validate_complete(
&self,
expected_magic: &[u8; 4],
min_version: u32,
max_version: u32,
data: &[u8],
) -> Result<(), ShardexError> {
self.validate_magic(expected_magic)?;
self.validate_version(min_version, max_version)?;
self.validate_structure()?;
self.validate_checksum(data)?;
Ok(())
}
pub fn update_for_modification(&mut self, data: &[u8]) {
self.modified_at = Self::current_timestamp();
self.update_checksum(data);
}
pub fn update_checksum(&mut self, data: &[u8]) {
self.checksum = Self::calculate_checksum_with_header(self, data);
}
fn calculate_checksum_with_header(header: &Self, data: &[u8]) -> u32 {
let mut header_copy = *header;
header_copy.checksum = 0;
header_copy.created_at = 0;
header_copy.modified_at = 0;
header_copy.reserved = [0; 32];
let header_bytes = bytemuck::bytes_of(&header_copy);
let mut crc = 0xFFFFFFFF;
crc = Self::crc32_update(crc, header_bytes);
crc = Self::crc32_update(crc, data);
crc ^ 0xFFFFFFFF
}
pub fn crc32_hash(data: &[u8]) -> u32 {
let mut crc = 0xFFFFFFFF;
crc = Self::crc32_update(crc, data);
crc ^ 0xFFFFFFFF
}
pub fn crc32_update(mut crc: u32, data: &[u8]) -> u32 {
const CRC32_TABLE: [u32; 256] = generate_crc32_table();
for &byte in data {
let table_index = ((crc ^ u32::from(byte)) & 0xFF) as usize;
crc = (crc >> 8) ^ CRC32_TABLE[table_index];
}
crc
}
}
const fn generate_crc32_table() -> [u32; 256] {
let mut table = [0u32; 256];
let mut i = 0;
while i < 256 {
let mut crc = i as u32;
let mut j = 0;
while j < 8 {
if crc & 1 != 0 {
crc = (crc >> 1) ^ 0xEDB88320;
} else {
crc >>= 1;
}
j += 1;
}
table[i] = crc;
i += 1;
}
table
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::magic;
use tempfile::{NamedTempFile, TempDir};
#[test]
fn test_create_memory_mapped_file() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.dat");
let mmf = MemoryMappedFile::create(&file_path, 1024).unwrap();
assert_eq!(mmf.len(), 1024);
assert!(!mmf.is_empty());
assert!(!mmf.is_read_only());
}
#[test]
fn test_read_write_operations() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("rw_test.dat");
let mut mmf = MemoryMappedFile::create(&file_path, 1024).unwrap();
let value: u64 = 0x1234567890ABCDEF;
mmf.write_at(0, &value).unwrap();
let read_value: u64 = mmf.read_at(0).unwrap();
assert_eq!(value, read_value);
}
#[test]
fn test_slice_operations() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("slice_test.dat");
let mut mmf = MemoryMappedFile::create(&file_path, 1024).unwrap();
let values: [u32; 5] = [1, 2, 3, 4, 5];
mmf.write_slice_at(0, &values).unwrap();
let read_values: &[u32] = mmf.read_slice_at(0, 5).unwrap();
assert_eq!(&values[..], read_values);
}
#[test]
fn test_bounds_checking() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("bounds_test.dat");
let mut mmf = MemoryMappedFile::create(&file_path, 64).unwrap();
let result: Result<u64, _> = mmf.read_at(60);
assert!(result.is_err());
let value: u64 = 42;
let result = mmf.write_at(60, &value);
assert!(result.is_err());
}
#[test]
fn test_alignment_checking() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("align_test.dat");
let mut mmf = MemoryMappedFile::create(&file_path, 1024).unwrap();
let result: Result<u64, _> = mmf.read_at(1);
assert!(result.is_err());
let value: u64 = 42;
let result = mmf.write_at(1, &value);
assert!(result.is_err());
}
#[test]
fn test_file_resize() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("resize_test.dat");
let mut mmf = MemoryMappedFile::create(&file_path, 512).unwrap();
assert_eq!(mmf.len(), 512);
let value: u64 = 0xDEADBEEF;
mmf.write_at(0, &value).unwrap();
mmf.resize(1024).unwrap();
assert_eq!(mmf.len(), 1024);
let read_value: u64 = mmf.read_at(0).unwrap();
assert_eq!(value, read_value);
}
#[test]
fn test_read_only_mapping() {
let temp_file = NamedTempFile::new().unwrap();
let file_path = temp_file.path();
{
let mut mmf = MemoryMappedFile::create(file_path, 64).unwrap();
let value: u32 = 42;
mmf.write_at(0, &value).unwrap();
mmf.sync().unwrap();
}
let mmf = MemoryMappedFile::open_read_only(file_path).unwrap();
assert!(mmf.is_read_only());
let value: u32 = mmf.read_at(0).unwrap();
assert_eq!(value, 42);
}
#[test]
fn test_standard_header_creation() {
let magic = magic::TEST_GENERIC;
let data = b"Hello, World!";
let header = StandardHeader::new(magic, 1, StandardHeader::SIZE as u64, data);
assert_eq!(header.magic, *magic);
assert_eq!(header.version, 1);
assert_eq!(header.header_size, StandardHeader::SIZE as u32);
assert_eq!(header.data_offset, StandardHeader::SIZE as u64);
assert_ne!(header.checksum, 0); assert!(header.created_at > 0);
assert!(header.modified_at > 0);
assert_eq!(header.reserved, [0; 32]);
}
#[test]
fn test_standard_header_magic_validation() {
let header = StandardHeader::new(magic::TEST_GENERIC, 1, StandardHeader::SIZE as u64, b"data");
assert!(header.validate_magic(magic::TEST_GENERIC).is_ok());
assert!(header.validate_magic(magic::TEST_FAILURE).is_err());
}
#[test]
fn test_standard_header_checksum_validation() {
let data = b"Hello, World!";
let header = StandardHeader::new(magic::TEST_GENERIC, 1, StandardHeader::SIZE as u64, data);
assert!(header.validate_checksum(data).is_ok());
assert!(header.validate_checksum(b"Different data").is_err());
}
#[test]
fn test_standard_header_update_checksum() {
let mut header = StandardHeader::new_without_checksum(magic::TEST_GENERIC, 1, StandardHeader::SIZE as u64);
assert_eq!(header.checksum, 0);
let data = b"Some data";
header.update_checksum(data);
assert_ne!(header.checksum, 0);
assert!(header.validate_checksum(data).is_ok());
}
#[test]
fn test_standard_header_bytemuck() {
let header = StandardHeader::new(magic::TEST_GENERIC, 1, StandardHeader::SIZE as u64, b"data");
let bytes = bytemuck::bytes_of(&header);
assert_eq!(bytes.len(), StandardHeader::SIZE);
let header_restored = bytemuck::from_bytes::<StandardHeader>(bytes);
assert_eq!(header.magic, header_restored.magic);
assert_eq!(header.version, header_restored.version);
assert_eq!(header.checksum, header_restored.checksum);
assert_eq!(header.header_size, header_restored.header_size);
assert_eq!(header.data_offset, header_restored.data_offset);
assert_eq!(header.created_at, header_restored.created_at);
assert_eq!(header.modified_at, header_restored.modified_at);
assert_eq!(header.reserved, header_restored.reserved);
}
#[test]
fn test_memory_mapped_file_with_header() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("header_test.dat");
let test_data = vec![1u32, 2, 3, 4, 5];
let data_bytes = bytemuck::cast_slice(&test_data);
{
let mut mmf = MemoryMappedFile::create(&file_path, 1024).unwrap();
let header = StandardHeader::new(magic::TEST_SHARD, 1, StandardHeader::SIZE as u64, data_bytes);
mmf.write_at(0, &header).unwrap();
mmf.write_slice_at(StandardHeader::SIZE, &test_data)
.unwrap();
mmf.sync().unwrap();
}
{
let mmf = MemoryMappedFile::open_read_only(&file_path).unwrap();
let header: StandardHeader = mmf.read_at(0).unwrap();
header.validate_magic(magic::TEST_SHARD).unwrap();
let read_data: &[u32] = mmf
.read_slice_at(StandardHeader::SIZE, test_data.len())
.unwrap();
header
.validate_checksum(bytemuck::cast_slice(read_data))
.unwrap();
assert_eq!(read_data, &test_data[..]);
}
}
#[test]
fn test_parent_directory_creation() {
let temp_dir = TempDir::new().unwrap();
let nested_path = temp_dir.path().join("nested").join("dirs").join("test.dat");
let mmf = MemoryMappedFile::create(&nested_path, 64).unwrap();
assert_eq!(mmf.len(), 64);
assert!(nested_path.exists());
}
#[test]
fn test_zero_size_file() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("empty.dat");
let mmf = MemoryMappedFile::create(&file_path, 0).unwrap();
assert_eq!(mmf.len(), 0);
assert!(mmf.is_empty());
}
#[test]
fn test_standard_header_version_validation() {
let header = StandardHeader::new(magic::TEST_GENERIC, 5, StandardHeader::SIZE as u64, b"data");
assert!(header.validate_version(1, 10).is_ok());
assert!(header.validate_version(5, 5).is_ok());
assert!(header.validate_version(6, 10).is_err());
assert!(header.validate_version(1, 4).is_err());
assert!(header.is_version(5));
assert!(!header.is_version(4));
assert!(header.is_compatible(1, 10));
assert!(!header.is_compatible(6, 10));
}
#[test]
fn test_standard_header_structure_validation() {
let mut header = StandardHeader::new(magic::TEST_GENERIC, 1, StandardHeader::SIZE as u64, b"data");
assert!(header.validate_structure().is_ok());
header.header_size = 50;
assert!(header.validate_structure().is_err());
header.header_size = StandardHeader::SIZE as u32;
header.data_offset = 10;
assert!(header.validate_structure().is_err());
header.data_offset = StandardHeader::SIZE as u64;
header.created_at = 2000;
header.modified_at = 1000;
assert!(header.validate_structure().is_err());
header.created_at = 1000;
header.modified_at = 2000;
header.reserved[0] = 1;
assert!(header.validate_structure().is_err());
header.reserved[0] = 0;
assert!(header.validate_structure().is_ok());
}
#[test]
fn test_standard_header_complete_validation() {
let data = b"test data";
let header = StandardHeader::new(magic::TEST_GENERIC, 2, StandardHeader::SIZE as u64, data);
assert!(header
.validate_complete(magic::TEST_GENERIC, 1, 5, data)
.is_ok());
assert!(header
.validate_complete(magic::TEST_FAILURE, 1, 5, data)
.is_err());
assert!(header
.validate_complete(magic::TEST_GENERIC, 3, 5, data)
.is_err());
assert!(header
.validate_complete(magic::TEST_GENERIC, 1, 1, data)
.is_err());
assert!(header
.validate_complete(magic::TEST_GENERIC, 1, 5, b"wrong data")
.is_err());
}
#[test]
fn test_standard_header_update_for_modification() {
let initial_data = b"initial data";
let mut header = StandardHeader::new_with_timestamps(
magic::TEST_GENERIC,
1,
StandardHeader::SIZE as u64,
1000,
1000,
initial_data,
);
let new_data = b"modified data";
header.update_for_modification(new_data);
assert!(header.modified_at > 1000);
assert_eq!(header.created_at, 1000);
assert!(header.validate_checksum(new_data).is_ok());
assert!(header.validate_checksum(initial_data).is_err());
}
#[test]
fn test_standard_header_with_timestamps() {
let created = 1000;
let modified = 2000;
let data = b"test data";
let header = StandardHeader::new_with_timestamps(
magic::TEST_GENERIC,
1,
StandardHeader::SIZE as u64,
created,
modified,
data,
);
assert_eq!(header.created_at, created);
assert_eq!(header.modified_at, modified);
assert!(header.validate_checksum(data).is_ok());
}
#[test]
fn test_standard_header_size_constants() {
assert_eq!(StandardHeader::SIZE, 80);
let header = StandardHeader::new(magic::TEST_GENERIC, 1, StandardHeader::SIZE as u64, b"data");
let bytes = bytemuck::bytes_of(&header);
assert_eq!(bytes.len(), 80);
}
#[test]
fn test_file_header_compatibility() {
let header: FileHeader = StandardHeader::new(magic::TEST_GENERIC, 1, StandardHeader::SIZE as u64, b"data");
assert_eq!(header.magic, *magic::TEST_GENERIC);
assert_eq!(header.version, 1);
assert!(header.validate_magic(magic::TEST_GENERIC).is_ok());
assert!(header.validate_checksum(b"data").is_ok());
}
#[test]
fn test_crc32_consistency() {
let data1 = b"Hello, World!";
let data2 = b"Hello, World!";
let data3 = b"Different data";
let checksum1 = StandardHeader::crc32_hash(data1);
let checksum2 = StandardHeader::crc32_hash(data2);
let checksum3 = StandardHeader::crc32_hash(data3);
assert_eq!(checksum1, checksum2);
assert_ne!(checksum1, checksum3);
let header1 =
StandardHeader::new_with_timestamps(magic::TEST_GENERIC, 1, StandardHeader::SIZE as u64, 1000, 1000, data1);
let header2 =
StandardHeader::new_with_timestamps(magic::TEST_GENERIC, 1, StandardHeader::SIZE as u64, 1000, 1000, data2);
assert_eq!(header1.checksum, header2.checksum);
let header3 =
StandardHeader::new_with_timestamps(magic::TEST_GENERIC, 1, StandardHeader::SIZE as u64, 1000, 1000, data3);
assert_ne!(header1.checksum, header3.checksum);
}
#[test]
fn test_sync_operations() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("sync_test.dat");
let mut mmf = MemoryMappedFile::create(&file_path, 64).unwrap();
let value: u64 = 0x123456789ABCDEF0;
mmf.write_at(0, &value).unwrap();
mmf.sync().unwrap();
}
}