use super::Backend;
use crate::{
Error::{Io, Other},
Result,
};
use memmap2::Mmap;
use std::{fs, path::Path};
#[derive(Debug)]
pub struct Physical {
data: Mmap,
}
impl Physical {
pub fn new(path: impl AsRef<Path>) -> Result<Physical> {
let file = match fs::File::open(path) {
Ok(file) => file,
Err(error) => return Err(Io(error)),
};
let mmap = match unsafe { Mmap::map(&file) } {
Ok(mmap) => mmap,
Err(error) => {
return Err(Other(format!(
"Failed to memory-map file: {} ({})",
error,
error.kind()
)))
}
};
Ok(Physical { data: mmap })
}
#[allow(clippy::needless_pass_by_value)]
pub fn from_std_file(file: fs::File) -> Result<Physical> {
let mmap = unsafe { Mmap::map(&file) }.map_err(|error| {
Other(format!(
"Failed to memory-map file: {} ({})",
error,
error.kind()
))
})?;
Ok(Physical { data: mmap })
}
}
impl Backend for Physical {
fn data_slice(&self, offset: usize, len: usize) -> Result<&[u8]> {
let Some(offset_end) = offset.checked_add(len) else {
return Err(out_of_bounds_error!());
};
if offset_end > self.data.len() {
return Err(out_of_bounds_error!());
}
Ok(&self.data[offset..offset_end])
}
fn data(&self) -> &[u8] {
self.data.as_ref()
}
fn len(&self) -> usize {
self.data.len()
}
fn into_data(self: Box<Self>) -> Vec<u8> {
self.data.as_ref().to_vec()
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use super::*;
use crate::Error;
#[test]
fn physical() {
let physical = Physical::new(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll"),
)
.unwrap();
assert_eq!(physical.len(), 2255024);
assert_eq!(physical.data()[0], 0x4D);
assert_eq!(physical.data()[1], 0x5A);
assert_eq!(
physical.data_slice(12, 5).unwrap(),
&[0xFF, 0xFF, 0x00, 0x00, 0xB8]
);
if physical
.data_slice(u32::MAX as usize, u32::MAX as usize)
.is_ok()
{
panic!("This should not work!")
}
if physical.data_slice(0, 4 * 1024 * 1024).is_ok() {
panic!("This should not work!")
}
}
#[test]
fn test_physical_invalid_file_path() {
let result = Physical::new(PathBuf::from("/nonexistent/path/to/file.dll"));
assert!(result.is_err());
match result.unwrap_err() {
Io(io_error) => {
assert_eq!(io_error.kind(), std::io::ErrorKind::NotFound);
}
_ => panic!("Expected Io error"),
}
}
#[test]
fn test_physical_empty_file() {
let temp_dir = std::env::temp_dir();
let temp_path = temp_dir.join("empty_test_file.bin");
std::fs::write(&temp_path, b"").unwrap();
let physical = Physical::new(&temp_path).unwrap();
assert_eq!(physical.len(), 0);
assert_eq!(physical.data().len(), 0);
assert!(physical.data_slice(0, 1).is_err());
assert!(physical.data_slice(1, 0).is_err());
let empty_slice: &[u8] = &[];
assert_eq!(physical.data_slice(0, 0).unwrap(), empty_slice);
std::fs::remove_file(&temp_path).unwrap();
}
#[test]
fn test_physical_large_offset_overflow() {
let physical = Physical::new(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll"),
)
.unwrap();
let result = physical.data_slice(usize::MAX, 1);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::OutOfBounds { .. }));
let len = physical.len();
let result = physical.data_slice(len, 1);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::OutOfBounds { .. }));
let result = physical.data_slice(len - 1, 2);
assert!(result.is_err());
assert!(matches!(result.unwrap_err(), Error::OutOfBounds { .. }));
}
#[test]
fn test_physical_boundary_conditions() {
let physical = Physical::new(
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll"),
)
.unwrap();
let len = physical.len();
let result = physical.data_slice(len - 1, 1);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 1);
let result = physical.data_slice(0, len);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), len);
let result = physical.data_slice(len, 0);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 0);
}
#[test]
fn test_physical_into_data() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/samples/WindowsBase.dll");
let physical = Physical::new(&path).unwrap();
let expected_len = physical.len();
let expected_first = physical.data()[0];
let expected_last = physical.data()[expected_len - 1];
let backend: Box<dyn Backend> = Box::new(physical);
let recovered_data = backend.into_data();
assert_eq!(recovered_data.len(), expected_len);
assert_eq!(recovered_data[0], expected_first);
assert_eq!(recovered_data[expected_len - 1], expected_last);
assert_eq!(&recovered_data[0..2], b"MZ");
}
#[test]
fn test_physical_into_data_small_file() {
let temp_dir = std::env::temp_dir();
let temp_path = temp_dir.join("test_physical_into_data.bin");
let test_data = vec![0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF];
std::fs::write(&temp_path, &test_data).unwrap();
let physical = Physical::new(&temp_path).unwrap();
let backend: Box<dyn Backend> = Box::new(physical);
let recovered_data = backend.into_data();
assert_eq!(recovered_data, test_data);
std::fs::remove_file(&temp_path).unwrap();
}
}