1#[cfg(feature = "mmap")]
13use crate::security::{SecurityLimits, SessionTracker};
14#[cfg(feature = "mmap")]
15use crate::{Error, Result};
16#[cfg(feature = "mmap")]
17use memmap2::{Mmap, MmapOptions};
18#[cfg(feature = "mmap")]
19use std::fs::File;
20#[cfg(feature = "mmap")]
21use std::path::Path;
22#[cfg(feature = "mmap")]
23use std::sync::Arc;
24
25#[derive(Debug, Clone)]
27pub struct MemoryMapConfig {
28 pub max_map_size: u64,
30 pub enable_mapping: bool,
32 pub read_ahead: bool,
34 pub advisory_locking: bool,
36}
37
38impl Default for MemoryMapConfig {
39 fn default() -> Self {
40 Self {
41 max_map_size: 2 * 1024 * 1024 * 1024, enable_mapping: true,
43 read_ahead: true,
44 advisory_locking: false,
45 }
46 }
47}
48
49impl MemoryMapConfig {
50 pub fn strict() -> Self {
52 Self {
53 max_map_size: 256 * 1024 * 1024, enable_mapping: true,
55 read_ahead: false,
56 advisory_locking: true,
57 }
58 }
59
60 pub fn permissive() -> Self {
62 Self {
63 max_map_size: 8 * 1024 * 1024 * 1024, enable_mapping: true,
65 read_ahead: true,
66 advisory_locking: false,
67 }
68 }
69
70 pub fn disabled() -> Self {
72 Self {
73 max_map_size: 0,
74 enable_mapping: false,
75 read_ahead: false,
76 advisory_locking: false,
77 }
78 }
79}
80
81#[derive(Debug, Clone, Default)]
83pub struct MemoryMapStats {
84 pub bytes_mapped: u64,
86 pub active_mappings: usize,
88 pub failed_mappings: usize,
90 pub fallback_operations: usize,
92}
93
94#[cfg(feature = "mmap")]
96pub struct MemoryMappedArchive {
97 mmap: Mmap,
99 #[allow(dead_code)] config: MemoryMapConfig,
102 security_limits: SecurityLimits,
104 #[allow(dead_code)] session_tracker: Arc<SessionTracker>,
107 file_size: u64,
109 stats: MemoryMapStats,
111}
112
113#[cfg(feature = "mmap")]
114impl std::fmt::Debug for MemoryMappedArchive {
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 f.debug_struct("MemoryMappedArchive")
117 .field("file_size", &self.file_size)
118 .field("config", &self.config)
119 .field("security_limits", &self.security_limits)
120 .field("stats", &self.stats)
121 .finish()
122 }
123}
124
125#[cfg(feature = "mmap")]
126impl MemoryMappedArchive {
127 pub fn new<P: AsRef<Path>>(
129 path: P,
130 config: MemoryMapConfig,
131 security_limits: SecurityLimits,
132 session_tracker: Arc<SessionTracker>,
133 ) -> Result<Self> {
134 if !config.enable_mapping {
136 return Err(Error::unsupported_feature(
137 "Memory mapping is disabled in configuration",
138 ));
139 }
140
141 let file = File::open(&path).map_err(|e| {
143 Error::io_error(format!("Failed to open file for memory mapping: {}", e))
144 })?;
145
146 let file_size = file
148 .metadata()
149 .map_err(|e| Error::io_error(format!("Failed to get file metadata: {}", e)))?
150 .len();
151
152 Self::validate_file_size(file_size, &config, &security_limits)?;
154
155 let mmap = Self::create_secure_mmap(&file, file_size, &config)?;
157
158 let stats = MemoryMapStats {
160 bytes_mapped: file_size,
161 active_mappings: 1,
162 ..Default::default()
163 };
164
165 Ok(Self {
166 mmap,
167 config,
168 security_limits,
169 session_tracker,
170 file_size,
171 stats,
172 })
173 }
174
175 pub fn from_file(
177 file: File,
178 config: MemoryMapConfig,
179 security_limits: SecurityLimits,
180 session_tracker: Arc<SessionTracker>,
181 ) -> Result<Self> {
182 if !config.enable_mapping {
183 return Err(Error::unsupported_feature(
184 "Memory mapping is disabled in configuration",
185 ));
186 }
187
188 let file_size = file
189 .metadata()
190 .map_err(|e| Error::io_error(format!("Failed to get file metadata: {}", e)))?
191 .len();
192
193 Self::validate_file_size(file_size, &config, &security_limits)?;
194
195 let mmap = Self::create_secure_mmap(&file, file_size, &config)?;
196
197 let stats = MemoryMapStats {
198 bytes_mapped: file_size,
199 active_mappings: 1,
200 ..Default::default()
201 };
202
203 Ok(Self {
204 mmap,
205 config,
206 security_limits,
207 session_tracker,
208 file_size,
209 stats,
210 })
211 }
212
213 fn validate_file_size(
215 file_size: u64,
216 config: &MemoryMapConfig,
217 security_limits: &SecurityLimits,
218 ) -> Result<()> {
219 if file_size == 0 {
220 return Err(Error::invalid_format("Cannot memory map empty file"));
221 }
222
223 if file_size > config.max_map_size {
224 return Err(Error::resource_exhaustion(format!(
225 "File size {} exceeds memory mapping limit {}",
226 file_size, config.max_map_size
227 )));
228 }
229
230 if file_size > security_limits.max_archive_size {
231 return Err(Error::resource_exhaustion(format!(
232 "File size {} exceeds security archive size limit {}",
233 file_size, security_limits.max_archive_size
234 )));
235 }
236
237 Ok(())
238 }
239
240 fn create_secure_mmap(file: &File, file_size: u64, config: &MemoryMapConfig) -> Result<Mmap> {
242 let mut mmap_options = MmapOptions::new();
243
244 if config.read_ahead {
246 }
248
249 let mmap = unsafe {
251 mmap_options
252 .len(file_size as usize)
253 .map(file)
254 .map_err(|e| Error::io_error(format!("Failed to create memory mapping: {}", e)))?
255 };
256
257 #[cfg(unix)]
259 {
260 Self::apply_unix_optimizations(&mmap, config)?;
261 }
262
263 #[cfg(windows)]
264 {
265 Self::apply_windows_optimizations(&mmap, config)?;
266 }
267
268 Ok(mmap)
269 }
270
271 #[cfg(unix)]
273 fn apply_unix_optimizations(mmap: &Mmap, config: &MemoryMapConfig) -> Result<()> {
274 let advice = if config.read_ahead {
277 libc::MADV_SEQUENTIAL | libc::MADV_WILLNEED
278 } else {
279 libc::MADV_RANDOM
280 };
281
282 unsafe {
283 let result = libc::madvise(mmap.as_ptr() as *mut libc::c_void, mmap.len(), advice);
284
285 if result != 0 {
286 log::warn!(
287 "Failed to apply madvise optimization: {}",
288 std::io::Error::last_os_error()
289 );
290 }
292 }
293
294 Ok(())
295 }
296
297 #[cfg(windows)]
299 fn apply_windows_optimizations(_mmap: &Mmap, _config: &MemoryMapConfig) -> Result<()> {
300 Ok(())
303 }
304
305 pub fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
307 self.validate_read_bounds(offset, buf.len())?;
308
309 let start = offset as usize;
310 let end = start + buf.len();
311
312 buf.copy_from_slice(&self.mmap[start..end]);
314
315 Ok(())
316 }
317
318 pub fn get_slice(&self, offset: u64, len: usize) -> Result<&[u8]> {
320 self.validate_read_bounds(offset, len)?;
321
322 let start = offset as usize;
323 let end = start + len;
324
325 Ok(&self.mmap[start..end])
326 }
327
328 fn validate_read_bounds(&self, offset: u64, len: usize) -> Result<()> {
330 let start = offset;
331 let end = offset.saturating_add(len as u64);
332
333 if start >= self.file_size {
334 return Err(Error::invalid_bounds(format!(
335 "Read offset {} beyond file size {}",
336 start, self.file_size
337 )));
338 }
339
340 if end > self.file_size {
341 return Err(Error::invalid_bounds(format!(
342 "Read end {} beyond file size {}",
343 end, self.file_size
344 )));
345 }
346
347 if len > self.security_limits.max_decompressed_size as usize {
348 return Err(Error::resource_exhaustion(format!(
349 "Read length {} exceeds security limit {}",
350 len, self.security_limits.max_decompressed_size
351 )));
352 }
353
354 Ok(())
355 }
356
357 pub fn file_size(&self) -> u64 {
359 self.file_size
360 }
361
362 pub fn stats(&self) -> &MemoryMapStats {
364 &self.stats
365 }
366
367 pub fn is_healthy(&self) -> bool {
369 self.mmap.len() == self.file_size as usize
370 }
371
372 pub fn sync(&self) -> Result<()> {
374 Ok(())
377 }
378
379 pub fn as_slice(&self) -> &[u8] {
381 &self.mmap
382 }
383}
384
385#[cfg(feature = "mmap")]
386impl Drop for MemoryMappedArchive {
387 fn drop(&mut self) {
388 self.stats.active_mappings = 0;
390
391 log::debug!("Unmapping memory-mapped archive ({} bytes)", self.file_size);
393 }
394}
395
396#[cfg(feature = "mmap")]
398#[derive(Debug)]
399pub struct MemoryMapManager {
400 config: MemoryMapConfig,
401 security_limits: SecurityLimits,
402 session_tracker: Arc<SessionTracker>,
403 global_stats: MemoryMapStats,
404}
405
406#[cfg(feature = "mmap")]
407impl MemoryMapManager {
408 pub fn new(
410 config: MemoryMapConfig,
411 security_limits: SecurityLimits,
412 session_tracker: Arc<SessionTracker>,
413 ) -> Self {
414 Self {
415 config,
416 security_limits,
417 session_tracker,
418 global_stats: MemoryMapStats::default(),
419 }
420 }
421
422 pub fn create_mapping<P: AsRef<Path>>(&mut self, path: P) -> Result<MemoryMappedArchive> {
424 match MemoryMappedArchive::new(
425 path,
426 self.config.clone(),
427 self.security_limits.clone(),
428 self.session_tracker.clone(),
429 ) {
430 Ok(mmap) => {
431 self.global_stats.bytes_mapped += mmap.file_size();
432 self.global_stats.active_mappings += 1;
433 Ok(mmap)
434 }
435 Err(e) => {
436 self.global_stats.failed_mappings += 1;
437 Err(e)
438 }
439 }
440 }
441
442 pub fn record_fallback(&mut self) {
444 self.global_stats.fallback_operations += 1;
445 }
446
447 pub fn global_stats(&self) -> &MemoryMapStats {
449 &self.global_stats
450 }
451
452 pub fn should_attempt_mapping(&self, file_size: u64) -> bool {
454 if !self.config.enable_mapping {
455 return false;
456 }
457
458 if file_size > self.config.max_map_size {
459 return false;
460 }
461
462 if file_size > self.security_limits.max_archive_size {
463 return false;
464 }
465
466 let total_attempts = self.global_stats.active_mappings + self.global_stats.failed_mappings;
469 if total_attempts > 10 {
470 let failure_rate = self.global_stats.failed_mappings as f64 / total_attempts as f64;
471 if failure_rate > 0.5 {
472 log::warn!(
473 "High memory mapping failure rate ({:.1}%), temporarily disabling",
474 failure_rate * 100.0
475 );
476 return false;
477 }
478 }
479
480 true
481 }
482}
483
484#[cfg(not(feature = "mmap"))]
490#[derive(Debug)]
491pub struct MemoryMappedArchive;
492
493#[cfg(not(feature = "mmap"))]
494impl MemoryMappedArchive {
495 pub fn new<P: AsRef<std::path::Path>>(
500 _path: P,
501 _config: MemoryMapConfig,
502 _security_limits: crate::security::SecurityLimits,
503 _session_tracker: std::sync::Arc<crate::security::SessionTracker>,
504 ) -> crate::Result<Self> {
505 Err(crate::Error::unsupported_feature(
506 "Memory mapping support not compiled in - enable 'mmap' feature",
507 ))
508 }
509}
510
511#[cfg(not(feature = "mmap"))]
516#[derive(Debug)]
517pub struct MemoryMapManager;
518
519#[cfg(not(feature = "mmap"))]
520impl MemoryMapManager {
521 pub fn new(
523 _config: MemoryMapConfig,
524 _security_limits: crate::security::SecurityLimits,
525 _session_tracker: std::sync::Arc<crate::security::SessionTracker>,
526 ) -> Self {
527 Self
528 }
529}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534 #[cfg(feature = "mmap")]
535 use crate::security::SecurityLimits;
536 #[cfg(feature = "mmap")]
537 use std::io::Write;
538 #[cfg(feature = "mmap")]
539 use tempfile::NamedTempFile;
540
541 #[test]
542 fn test_memory_map_config_defaults() {
543 let config = MemoryMapConfig::default();
544 assert!(config.enable_mapping);
545 assert!(config.read_ahead);
546 assert!(!config.advisory_locking);
547 assert_eq!(config.max_map_size, 2 * 1024 * 1024 * 1024);
548 }
549
550 #[test]
551 fn test_memory_map_config_variants() {
552 let strict = MemoryMapConfig::strict();
553 let permissive = MemoryMapConfig::permissive();
554 let disabled = MemoryMapConfig::disabled();
555
556 assert!(strict.max_map_size < permissive.max_map_size);
557 assert!(strict.advisory_locking);
558 assert!(!strict.read_ahead);
559
560 assert!(!disabled.enable_mapping);
561 assert_eq!(disabled.max_map_size, 0);
562 }
563
564 #[test]
565 fn test_memory_map_stats_default() {
566 let stats = MemoryMapStats::default();
567 assert_eq!(stats.bytes_mapped, 0);
568 assert_eq!(stats.active_mappings, 0);
569 assert_eq!(stats.failed_mappings, 0);
570 assert_eq!(stats.fallback_operations, 0);
571 }
572
573 #[cfg(feature = "mmap")]
574 #[test]
575 fn test_memory_mapped_archive_creation() -> Result<()> {
576 let mut temp_file = NamedTempFile::new().unwrap();
577 let test_data = b"Hello, memory mapped world! This is test data for validation.";
578 temp_file.write_all(test_data).unwrap();
579 temp_file.flush().unwrap();
580
581 let config = MemoryMapConfig::default();
582 let security_limits = SecurityLimits::default();
583 let session_tracker = Arc::new(crate::security::SessionTracker::new());
584
585 let mmap_archive =
586 MemoryMappedArchive::new(temp_file.path(), config, security_limits, session_tracker)?;
587
588 assert_eq!(mmap_archive.file_size(), test_data.len() as u64);
589 assert!(mmap_archive.is_healthy());
590 assert_eq!(mmap_archive.stats().bytes_mapped, test_data.len() as u64);
591 assert_eq!(mmap_archive.stats().active_mappings, 1);
592
593 Ok(())
594 }
595
596 #[cfg(feature = "mmap")]
597 #[test]
598 fn test_memory_mapped_archive_read_at() -> Result<()> {
599 let mut temp_file = NamedTempFile::new().unwrap();
600 let test_data = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
601 temp_file.write_all(test_data).unwrap();
602 temp_file.flush().unwrap();
603
604 let config = MemoryMapConfig::default();
605 let security_limits = SecurityLimits::default();
606 let session_tracker = Arc::new(crate::security::SessionTracker::new());
607
608 let mmap_archive =
609 MemoryMappedArchive::new(temp_file.path(), config, security_limits, session_tracker)?;
610
611 let mut buf = [0u8; 5];
613 mmap_archive.read_at(0, &mut buf)?;
614 assert_eq!(&buf, b"01234");
615
616 mmap_archive.read_at(10, &mut buf)?;
617 assert_eq!(&buf, b"ABCDE");
618
619 mmap_archive.read_at(30, &mut buf)?;
620 assert_eq!(&buf, b"UVWXY");
621
622 Ok(())
623 }
624
625 #[cfg(feature = "mmap")]
626 #[test]
627 fn test_memory_mapped_archive_bounds_checking() -> Result<()> {
628 let mut temp_file = NamedTempFile::new().unwrap();
629 let test_data = b"Short data";
630 temp_file.write_all(test_data).unwrap();
631 temp_file.flush().unwrap();
632
633 let config = MemoryMapConfig::default();
634 let security_limits = SecurityLimits::default();
635 let session_tracker = Arc::new(crate::security::SessionTracker::new());
636
637 let mmap_archive =
638 MemoryMappedArchive::new(temp_file.path(), config, security_limits, session_tracker)?;
639
640 let mut buf = [0u8; 5];
642 let result = mmap_archive.read_at(test_data.len() as u64, &mut buf);
643 assert!(result.is_err());
644
645 let result = mmap_archive.read_at(8, &mut buf);
647 assert!(result.is_err());
648
649 Ok(())
650 }
651
652 #[cfg(feature = "mmap")]
653 #[test]
654 fn test_memory_mapped_archive_get_slice() -> Result<()> {
655 let mut temp_file = NamedTempFile::new().unwrap();
656 let test_data = b"Memory mapped slice test data";
657 temp_file.write_all(test_data).unwrap();
658 temp_file.flush().unwrap();
659
660 let config = MemoryMapConfig::default();
661 let security_limits = SecurityLimits::default();
662 let session_tracker = Arc::new(crate::security::SessionTracker::new());
663
664 let mmap_archive =
665 MemoryMappedArchive::new(temp_file.path(), config, security_limits, session_tracker)?;
666
667 let slice = mmap_archive.get_slice(0, 6)?;
669 assert_eq!(slice, b"Memory");
670
671 let slice = mmap_archive.get_slice(7, 6)?;
672 assert_eq!(slice, b"mapped");
673
674 let slice = mmap_archive.get_slice(14, 5)?;
675 assert_eq!(slice, b"slice");
676
677 Ok(())
678 }
679
680 #[cfg(feature = "mmap")]
681 #[test]
682 fn test_file_size_validation() {
683 let config = MemoryMapConfig::strict(); let security_limits = SecurityLimits::strict(); let result =
688 MemoryMappedArchive::validate_file_size(100 * 1024 * 1024, &config, &security_limits);
689 assert!(result.is_ok());
690
691 let result =
693 MemoryMappedArchive::validate_file_size(300 * 1024 * 1024, &config, &security_limits);
694 assert!(result.is_err());
695
696 let large_config = MemoryMapConfig::permissive(); let result = MemoryMappedArchive::validate_file_size(
699 2 * 1024 * 1024 * 1024,
700 &large_config,
701 &security_limits,
702 );
703 assert!(result.is_err());
704
705 let result = MemoryMappedArchive::validate_file_size(0, &config, &security_limits);
707 assert!(result.is_err());
708 }
709
710 #[cfg(feature = "mmap")]
711 #[test]
712 fn test_memory_map_manager() -> Result<()> {
713 let config = MemoryMapConfig::default();
714 let security_limits = SecurityLimits::default();
715 let session_tracker = Arc::new(crate::security::SessionTracker::new());
716
717 let mut manager = MemoryMapManager::new(config, security_limits, session_tracker);
718
719 assert!(manager.should_attempt_mapping(1024 * 1024)); assert!(!manager.should_attempt_mapping(10 * 1024 * 1024 * 1024)); manager.record_fallback();
725 assert_eq!(manager.global_stats().fallback_operations, 1);
726
727 Ok(())
728 }
729
730 #[test]
731 fn test_disabled_feature_stubs() {
732 #[cfg(not(feature = "mmap"))]
733 {
734 let config = MemoryMapConfig::default();
735 let security_limits = crate::security::SecurityLimits::default();
736 let session_tracker = std::sync::Arc::new(crate::security::SessionTracker::new());
737
738 let result = MemoryMappedArchive::new(
739 "/nonexistent/path",
740 config.clone(),
741 security_limits.clone(),
742 session_tracker.clone(),
743 );
744 assert!(result.is_err());
745
746 let _manager = MemoryMapManager::new(config, security_limits, session_tracker);
747 }
748 }
749}