#[cfg(feature = "mmap")]
use crate::security::{SecurityLimits, SessionTracker};
#[cfg(feature = "mmap")]
use crate::{Error, Result};
#[cfg(feature = "mmap")]
use memmap2::{Mmap, MmapOptions};
#[cfg(feature = "mmap")]
use std::fs::File;
#[cfg(feature = "mmap")]
use std::path::Path;
#[cfg(feature = "mmap")]
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct MemoryMapConfig {
pub max_map_size: u64,
pub enable_mapping: bool,
pub read_ahead: bool,
pub advisory_locking: bool,
}
impl Default for MemoryMapConfig {
fn default() -> Self {
Self {
max_map_size: 2 * 1024 * 1024 * 1024, enable_mapping: true,
read_ahead: true,
advisory_locking: false,
}
}
}
impl MemoryMapConfig {
pub fn strict() -> Self {
Self {
max_map_size: 256 * 1024 * 1024, enable_mapping: true,
read_ahead: false,
advisory_locking: true,
}
}
pub fn permissive() -> Self {
Self {
max_map_size: 8 * 1024 * 1024 * 1024, enable_mapping: true,
read_ahead: true,
advisory_locking: false,
}
}
pub fn disabled() -> Self {
Self {
max_map_size: 0,
enable_mapping: false,
read_ahead: false,
advisory_locking: false,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct MemoryMapStats {
pub bytes_mapped: u64,
pub active_mappings: usize,
pub failed_mappings: usize,
pub fallback_operations: usize,
}
#[cfg(feature = "mmap")]
pub struct MemoryMappedArchive {
mmap: Mmap,
#[allow(dead_code)] config: MemoryMapConfig,
security_limits: SecurityLimits,
#[allow(dead_code)] session_tracker: Arc<SessionTracker>,
file_size: u64,
stats: MemoryMapStats,
}
#[cfg(feature = "mmap")]
impl std::fmt::Debug for MemoryMappedArchive {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MemoryMappedArchive")
.field("file_size", &self.file_size)
.field("config", &self.config)
.field("security_limits", &self.security_limits)
.field("stats", &self.stats)
.finish()
}
}
#[cfg(feature = "mmap")]
impl MemoryMappedArchive {
pub fn new<P: AsRef<Path>>(
path: P,
config: MemoryMapConfig,
security_limits: SecurityLimits,
session_tracker: Arc<SessionTracker>,
) -> Result<Self> {
if !config.enable_mapping {
return Err(Error::unsupported_feature(
"Memory mapping is disabled in configuration",
));
}
let file = File::open(&path).map_err(|e| {
Error::io_error(format!("Failed to open file for memory mapping: {}", e))
})?;
let file_size = file
.metadata()
.map_err(|e| Error::io_error(format!("Failed to get file metadata: {}", e)))?
.len();
Self::validate_file_size(file_size, &config, &security_limits)?;
let mmap = Self::create_secure_mmap(&file, file_size, &config)?;
let stats = MemoryMapStats {
bytes_mapped: file_size,
active_mappings: 1,
..Default::default()
};
Ok(Self {
mmap,
config,
security_limits,
session_tracker,
file_size,
stats,
})
}
pub fn from_file(
file: File,
config: MemoryMapConfig,
security_limits: SecurityLimits,
session_tracker: Arc<SessionTracker>,
) -> Result<Self> {
if !config.enable_mapping {
return Err(Error::unsupported_feature(
"Memory mapping is disabled in configuration",
));
}
let file_size = file
.metadata()
.map_err(|e| Error::io_error(format!("Failed to get file metadata: {}", e)))?
.len();
Self::validate_file_size(file_size, &config, &security_limits)?;
let mmap = Self::create_secure_mmap(&file, file_size, &config)?;
let stats = MemoryMapStats {
bytes_mapped: file_size,
active_mappings: 1,
..Default::default()
};
Ok(Self {
mmap,
config,
security_limits,
session_tracker,
file_size,
stats,
})
}
fn validate_file_size(
file_size: u64,
config: &MemoryMapConfig,
security_limits: &SecurityLimits,
) -> Result<()> {
if file_size == 0 {
return Err(Error::invalid_format("Cannot memory map empty file"));
}
if file_size > config.max_map_size {
return Err(Error::resource_exhaustion(format!(
"File size {} exceeds memory mapping limit {}",
file_size, config.max_map_size
)));
}
if file_size > security_limits.max_archive_size {
return Err(Error::resource_exhaustion(format!(
"File size {} exceeds security archive size limit {}",
file_size, security_limits.max_archive_size
)));
}
Ok(())
}
fn create_secure_mmap(file: &File, file_size: u64, config: &MemoryMapConfig) -> Result<Mmap> {
let mut mmap_options = MmapOptions::new();
if config.read_ahead {
}
let mmap = unsafe {
mmap_options
.len(file_size as usize)
.map(file)
.map_err(|e| Error::io_error(format!("Failed to create memory mapping: {}", e)))?
};
#[cfg(unix)]
{
Self::apply_unix_optimizations(&mmap, config)?;
}
#[cfg(windows)]
{
Self::apply_windows_optimizations(&mmap, config)?;
}
Ok(mmap)
}
#[cfg(unix)]
fn apply_unix_optimizations(mmap: &Mmap, config: &MemoryMapConfig) -> Result<()> {
let advice = if config.read_ahead {
libc::MADV_SEQUENTIAL | libc::MADV_WILLNEED
} else {
libc::MADV_RANDOM
};
unsafe {
let result = libc::madvise(mmap.as_ptr() as *mut libc::c_void, mmap.len(), advice);
if result != 0 {
log::warn!(
"Failed to apply madvise optimization: {}",
std::io::Error::last_os_error()
);
}
}
Ok(())
}
#[cfg(windows)]
fn apply_windows_optimizations(_mmap: &Mmap, _config: &MemoryMapConfig) -> Result<()> {
Ok(())
}
pub fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
self.validate_read_bounds(offset, buf.len())?;
let start = offset as usize;
let end = start + buf.len();
buf.copy_from_slice(&self.mmap[start..end]);
Ok(())
}
pub fn get_slice(&self, offset: u64, len: usize) -> Result<&[u8]> {
self.validate_read_bounds(offset, len)?;
let start = offset as usize;
let end = start + len;
Ok(&self.mmap[start..end])
}
fn validate_read_bounds(&self, offset: u64, len: usize) -> Result<()> {
let start = offset;
let end = offset.saturating_add(len as u64);
if start >= self.file_size {
return Err(Error::invalid_bounds(format!(
"Read offset {} beyond file size {}",
start, self.file_size
)));
}
if end > self.file_size {
return Err(Error::invalid_bounds(format!(
"Read end {} beyond file size {}",
end, self.file_size
)));
}
if len > self.security_limits.max_decompressed_size as usize {
return Err(Error::resource_exhaustion(format!(
"Read length {} exceeds security limit {}",
len, self.security_limits.max_decompressed_size
)));
}
Ok(())
}
pub fn file_size(&self) -> u64 {
self.file_size
}
pub fn stats(&self) -> &MemoryMapStats {
&self.stats
}
pub fn is_healthy(&self) -> bool {
self.mmap.len() == self.file_size as usize
}
pub fn sync(&self) -> Result<()> {
Ok(())
}
pub fn as_slice(&self) -> &[u8] {
&self.mmap
}
}
#[cfg(feature = "mmap")]
impl Drop for MemoryMappedArchive {
fn drop(&mut self) {
self.stats.active_mappings = 0;
log::debug!("Unmapping memory-mapped archive ({} bytes)", self.file_size);
}
}
#[cfg(feature = "mmap")]
#[derive(Debug)]
pub struct MemoryMapManager {
config: MemoryMapConfig,
security_limits: SecurityLimits,
session_tracker: Arc<SessionTracker>,
global_stats: MemoryMapStats,
}
#[cfg(feature = "mmap")]
impl MemoryMapManager {
pub fn new(
config: MemoryMapConfig,
security_limits: SecurityLimits,
session_tracker: Arc<SessionTracker>,
) -> Self {
Self {
config,
security_limits,
session_tracker,
global_stats: MemoryMapStats::default(),
}
}
pub fn create_mapping<P: AsRef<Path>>(&mut self, path: P) -> Result<MemoryMappedArchive> {
match MemoryMappedArchive::new(
path,
self.config.clone(),
self.security_limits.clone(),
self.session_tracker.clone(),
) {
Ok(mmap) => {
self.global_stats.bytes_mapped += mmap.file_size();
self.global_stats.active_mappings += 1;
Ok(mmap)
}
Err(e) => {
self.global_stats.failed_mappings += 1;
Err(e)
}
}
}
pub fn record_fallback(&mut self) {
self.global_stats.fallback_operations += 1;
}
pub fn global_stats(&self) -> &MemoryMapStats {
&self.global_stats
}
pub fn should_attempt_mapping(&self, file_size: u64) -> bool {
if !self.config.enable_mapping {
return false;
}
if file_size > self.config.max_map_size {
return false;
}
if file_size > self.security_limits.max_archive_size {
return false;
}
let total_attempts = self.global_stats.active_mappings + self.global_stats.failed_mappings;
if total_attempts > 10 {
let failure_rate = self.global_stats.failed_mappings as f64 / total_attempts as f64;
if failure_rate > 0.5 {
log::warn!(
"High memory mapping failure rate ({:.1}%), temporarily disabling",
failure_rate * 100.0
);
return false;
}
}
true
}
}
#[cfg(not(feature = "mmap"))]
#[derive(Debug)]
pub struct MemoryMappedArchive;
#[cfg(not(feature = "mmap"))]
impl MemoryMappedArchive {
pub fn new<P: AsRef<std::path::Path>>(
_path: P,
_config: MemoryMapConfig,
_security_limits: crate::security::SecurityLimits,
_session_tracker: std::sync::Arc<crate::security::SessionTracker>,
) -> crate::Result<Self> {
Err(crate::Error::unsupported_feature(
"Memory mapping support not compiled in - enable 'mmap' feature",
))
}
}
#[cfg(not(feature = "mmap"))]
#[derive(Debug)]
pub struct MemoryMapManager;
#[cfg(not(feature = "mmap"))]
impl MemoryMapManager {
pub fn new(
_config: MemoryMapConfig,
_security_limits: crate::security::SecurityLimits,
_session_tracker: std::sync::Arc<crate::security::SessionTracker>,
) -> Self {
Self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "mmap")]
use crate::security::SecurityLimits;
#[cfg(feature = "mmap")]
use std::io::Write;
#[cfg(feature = "mmap")]
use tempfile::NamedTempFile;
#[test]
fn test_memory_map_config_defaults() {
let config = MemoryMapConfig::default();
assert!(config.enable_mapping);
assert!(config.read_ahead);
assert!(!config.advisory_locking);
assert_eq!(config.max_map_size, 2 * 1024 * 1024 * 1024);
}
#[test]
fn test_memory_map_config_variants() {
let strict = MemoryMapConfig::strict();
let permissive = MemoryMapConfig::permissive();
let disabled = MemoryMapConfig::disabled();
assert!(strict.max_map_size < permissive.max_map_size);
assert!(strict.advisory_locking);
assert!(!strict.read_ahead);
assert!(!disabled.enable_mapping);
assert_eq!(disabled.max_map_size, 0);
}
#[test]
fn test_memory_map_stats_default() {
let stats = MemoryMapStats::default();
assert_eq!(stats.bytes_mapped, 0);
assert_eq!(stats.active_mappings, 0);
assert_eq!(stats.failed_mappings, 0);
assert_eq!(stats.fallback_operations, 0);
}
#[cfg(feature = "mmap")]
#[test]
fn test_memory_mapped_archive_creation() -> Result<()> {
let mut temp_file = NamedTempFile::new().unwrap();
let test_data = b"Hello, memory mapped world! This is test data for validation.";
temp_file.write_all(test_data).unwrap();
temp_file.flush().unwrap();
let config = MemoryMapConfig::default();
let security_limits = SecurityLimits::default();
let session_tracker = Arc::new(crate::security::SessionTracker::new());
let mmap_archive =
MemoryMappedArchive::new(temp_file.path(), config, security_limits, session_tracker)?;
assert_eq!(mmap_archive.file_size(), test_data.len() as u64);
assert!(mmap_archive.is_healthy());
assert_eq!(mmap_archive.stats().bytes_mapped, test_data.len() as u64);
assert_eq!(mmap_archive.stats().active_mappings, 1);
Ok(())
}
#[cfg(feature = "mmap")]
#[test]
fn test_memory_mapped_archive_read_at() -> Result<()> {
let mut temp_file = NamedTempFile::new().unwrap();
let test_data = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
temp_file.write_all(test_data).unwrap();
temp_file.flush().unwrap();
let config = MemoryMapConfig::default();
let security_limits = SecurityLimits::default();
let session_tracker = Arc::new(crate::security::SessionTracker::new());
let mmap_archive =
MemoryMappedArchive::new(temp_file.path(), config, security_limits, session_tracker)?;
let mut buf = [0u8; 5];
mmap_archive.read_at(0, &mut buf)?;
assert_eq!(&buf, b"01234");
mmap_archive.read_at(10, &mut buf)?;
assert_eq!(&buf, b"ABCDE");
mmap_archive.read_at(30, &mut buf)?;
assert_eq!(&buf, b"UVWXY");
Ok(())
}
#[cfg(feature = "mmap")]
#[test]
fn test_memory_mapped_archive_bounds_checking() -> Result<()> {
let mut temp_file = NamedTempFile::new().unwrap();
let test_data = b"Short data";
temp_file.write_all(test_data).unwrap();
temp_file.flush().unwrap();
let config = MemoryMapConfig::default();
let security_limits = SecurityLimits::default();
let session_tracker = Arc::new(crate::security::SessionTracker::new());
let mmap_archive =
MemoryMappedArchive::new(temp_file.path(), config, security_limits, session_tracker)?;
let mut buf = [0u8; 5];
let result = mmap_archive.read_at(test_data.len() as u64, &mut buf);
assert!(result.is_err());
let result = mmap_archive.read_at(8, &mut buf);
assert!(result.is_err());
Ok(())
}
#[cfg(feature = "mmap")]
#[test]
fn test_memory_mapped_archive_get_slice() -> Result<()> {
let mut temp_file = NamedTempFile::new().unwrap();
let test_data = b"Memory mapped slice test data";
temp_file.write_all(test_data).unwrap();
temp_file.flush().unwrap();
let config = MemoryMapConfig::default();
let security_limits = SecurityLimits::default();
let session_tracker = Arc::new(crate::security::SessionTracker::new());
let mmap_archive =
MemoryMappedArchive::new(temp_file.path(), config, security_limits, session_tracker)?;
let slice = mmap_archive.get_slice(0, 6)?;
assert_eq!(slice, b"Memory");
let slice = mmap_archive.get_slice(7, 6)?;
assert_eq!(slice, b"mapped");
let slice = mmap_archive.get_slice(14, 5)?;
assert_eq!(slice, b"slice");
Ok(())
}
#[cfg(feature = "mmap")]
#[test]
fn test_file_size_validation() {
let config = MemoryMapConfig::strict(); let security_limits = SecurityLimits::strict();
let result =
MemoryMappedArchive::validate_file_size(100 * 1024 * 1024, &config, &security_limits);
assert!(result.is_ok());
let result =
MemoryMappedArchive::validate_file_size(300 * 1024 * 1024, &config, &security_limits);
assert!(result.is_err());
let large_config = MemoryMapConfig::permissive(); let result = MemoryMappedArchive::validate_file_size(
2 * 1024 * 1024 * 1024,
&large_config,
&security_limits,
);
assert!(result.is_err());
let result = MemoryMappedArchive::validate_file_size(0, &config, &security_limits);
assert!(result.is_err());
}
#[cfg(feature = "mmap")]
#[test]
fn test_memory_map_manager() -> Result<()> {
let config = MemoryMapConfig::default();
let security_limits = SecurityLimits::default();
let session_tracker = Arc::new(crate::security::SessionTracker::new());
let mut manager = MemoryMapManager::new(config, security_limits, session_tracker);
assert!(manager.should_attempt_mapping(1024 * 1024)); assert!(!manager.should_attempt_mapping(10 * 1024 * 1024 * 1024));
manager.record_fallback();
assert_eq!(manager.global_stats().fallback_operations, 1);
Ok(())
}
#[test]
fn test_disabled_feature_stubs() {
#[cfg(not(feature = "mmap"))]
{
let config = MemoryMapConfig::default();
let security_limits = crate::security::SecurityLimits::default();
let session_tracker = std::sync::Arc::new(crate::security::SessionTracker::new());
let result = MemoryMappedArchive::new(
"/nonexistent/path",
config.clone(),
security_limits.clone(),
session_tracker.clone(),
);
assert!(result.is_err());
let _manager = MemoryMapManager::new(config, security_limits, session_tracker);
}
}
}