use crate::error::{Mf4Error, Result};
use crate::io::{ByteSlice, ByteSource};
use memmap2::Mmap;
use std::fs::File;
use std::path::Path;
pub struct MmapSource {
mmap: Mmap,
len: u64,
}
impl MmapSource {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self> {
let file = File::open(path.as_ref())?;
let metadata = file.metadata()?;
let len = metadata.len();
if len == 0 {
return Err(Mf4Error::MmapFailed("File is empty".to_string()));
}
#[allow(unsafe_code)]
let mmap = unsafe { Mmap::map(&file).map_err(|e| Mf4Error::MmapFailed(e.to_string()))? };
Ok(MmapSource { mmap, len })
}
pub fn as_slice(&self) -> &[u8] {
&self.mmap
}
}
impl ByteSource for MmapSource {
fn len(&self) -> u64 {
self.len
}
fn read_bytes(&self, offset: u64, len: usize) -> Result<ByteSlice<'_>> {
let start = offset as usize;
let end = start
.checked_add(len)
.ok_or_else(|| Mf4Error::truncated(offset, len, 0))?;
if end > self.mmap.len() {
return Err(Mf4Error::truncated(
offset,
len,
self.mmap.len().saturating_sub(start),
));
}
Ok(ByteSlice::borrowed(&self.mmap[start..end]))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Write;
use tempfile::NamedTempFile;
#[test]
fn test_mmap_source_basic() {
let mut file = NamedTempFile::new().unwrap();
file.write_all(b"Hello, MF4 World!").unwrap();
file.flush().unwrap();
let source = MmapSource::open(file.path()).unwrap();
assert_eq!(source.len(), 17);
let slice = source.read_bytes(0, 5).unwrap();
assert_eq!(&*slice, b"Hello");
let slice = source.read_bytes(7, 3).unwrap();
assert_eq!(&*slice, b"MF4");
}
#[test]
fn test_mmap_source_out_of_bounds() {
let mut file = NamedTempFile::new().unwrap();
file.write_all(b"Short").unwrap();
file.flush().unwrap();
let source = MmapSource::open(file.path()).unwrap();
let result = source.read_bytes(0, 100);
assert!(result.is_err());
let result = source.read_bytes(100, 1);
assert!(result.is_err());
}
#[test]
fn test_mmap_source_empty_file() {
let file = NamedTempFile::new().unwrap();
let result = MmapSource::open(file.path());
assert!(result.is_err());
}
#[test]
fn test_mmap_zero_copy() {
let mut file = NamedTempFile::new().unwrap();
file.write_all(b"Test data for zero-copy verification")
.unwrap();
file.flush().unwrap();
let source = MmapSource::open(file.path()).unwrap();
let slice1 = source.read_bytes(0, 10).unwrap();
let slice2 = source.read_bytes(5, 10).unwrap();
assert_eq!(&*slice1, b"Test data ");
assert_eq!(&*slice2, b"data for z");
assert!(matches!(slice1, ByteSlice::Borrowed(_)));
assert!(matches!(slice2, ByteSlice::Borrowed(_)));
}
}