threatflux_binary_analysis/utils/
mmap.rs

1//! Memory mapping utilities for efficient binary analysis
2//!
3//! This module provides safe memory mapping functionality for reading large binary files
4//! efficiently without loading them entirely into memory.
5
6use crate::{BinaryError, Result};
7use memmap2::{Mmap, MmapOptions};
8use std::fs::File;
9use std::ops::{Deref, Range};
10use std::path::Path;
11
12/// Memory-mapped binary file
13#[derive(Debug)]
14pub struct MappedBinary {
15    _file: File,
16    mmap: Mmap,
17    size: usize,
18}
19
20impl MappedBinary {
21    /// Create a new memory-mapped binary from a file path
22    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
23        let file = File::open(path)
24            .map_err(|e| BinaryError::memory_map(format!("Failed to open file: {}", e)))?;
25
26        let mmap = unsafe {
27            MmapOptions::new().map(&file).map_err(|e| {
28                BinaryError::memory_map(format!("Failed to create memory map: {}", e))
29            })?
30        };
31
32        let size = mmap.len();
33
34        Ok(Self {
35            _file: file,
36            mmap,
37            size,
38        })
39    }
40
41    /// Create a memory-mapped binary from an open file
42    pub fn from_file(file: File) -> Result<Self> {
43        let mmap = unsafe {
44            MmapOptions::new().map(&file).map_err(|e| {
45                BinaryError::memory_map(format!("Failed to create memory map: {}", e))
46            })?
47        };
48
49        let size = mmap.len();
50
51        Ok(Self {
52            _file: file,
53            mmap,
54            size,
55        })
56    }
57
58    /// Get the size of the mapped file
59    pub fn size(&self) -> usize {
60        self.size
61    }
62
63    /// Get a slice of the mapped data
64    pub fn slice(&self, range: Range<usize>) -> crate::types::ByteSliceResult<'_> {
65        if range.end > self.size {
66            return Err(BinaryError::memory_map(
67                "Range exceeds file size".to_string(),
68            ));
69        }
70
71        Ok(&self.mmap[range])
72    }
73
74    /// Get data at a specific offset with a given length
75    pub fn read_at(&self, offset: usize, length: usize) -> crate::types::ByteSliceResult<'_> {
76        if offset + length > self.size {
77            return Err(BinaryError::memory_map(
78                "Read exceeds file size".to_string(),
79            ));
80        }
81
82        Ok(&self.mmap[offset..offset + length])
83    }
84
85    /// Read a specific number of bytes starting from an offset
86    pub fn read_bytes(&self, offset: usize, count: usize) -> Result<Vec<u8>> {
87        let data = self.read_at(offset, count)?;
88        Ok(data.to_vec())
89    }
90
91    /// Read a u8 value at the specified offset
92    pub fn read_u8(&self, offset: usize) -> Result<u8> {
93        if offset >= self.size {
94            return Err(BinaryError::memory_map(
95                "Offset exceeds file size".to_string(),
96            ));
97        }
98        Ok(self.mmap[offset])
99    }
100
101    /// Read a u16 value at the specified offset (little endian)
102    pub fn read_u16_le(&self, offset: usize) -> Result<u16> {
103        let bytes = self.read_at(offset, 2)?;
104        Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
105    }
106
107    /// Read a u16 value at the specified offset (big endian)
108    pub fn read_u16_be(&self, offset: usize) -> Result<u16> {
109        let bytes = self.read_at(offset, 2)?;
110        Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
111    }
112
113    /// Read a u32 value at the specified offset (little endian)
114    pub fn read_u32_le(&self, offset: usize) -> Result<u32> {
115        let bytes = self.read_at(offset, 4)?;
116        Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
117    }
118
119    /// Read a u32 value at the specified offset (big endian)
120    pub fn read_u32_be(&self, offset: usize) -> Result<u32> {
121        let bytes = self.read_at(offset, 4)?;
122        Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
123    }
124
125    /// Read a u64 value at the specified offset (little endian)
126    pub fn read_u64_le(&self, offset: usize) -> Result<u64> {
127        let bytes = self.read_at(offset, 8)?;
128        Ok(u64::from_le_bytes([
129            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
130        ]))
131    }
132
133    /// Read a u64 value at the specified offset (big endian)
134    pub fn read_u64_be(&self, offset: usize) -> Result<u64> {
135        let bytes = self.read_at(offset, 8)?;
136        Ok(u64::from_be_bytes([
137            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
138        ]))
139    }
140
141    /// Read a null-terminated string at the specified offset
142    pub fn read_cstring(&self, offset: usize, max_length: usize) -> Result<String> {
143        let mut end = offset;
144        let limit = (offset + max_length).min(self.size);
145
146        while end < limit && self.mmap[end] != 0 {
147            end += 1;
148        }
149
150        let bytes = &self.mmap[offset..end];
151        String::from_utf8(bytes.to_vec())
152            .map_err(|e| BinaryError::memory_map(format!("Invalid UTF-8 string: {}", e)))
153    }
154
155    /// Find the first occurrence of a pattern in the mapped data
156    pub fn find_pattern(&self, pattern: &[u8]) -> Option<usize> {
157        self.mmap
158            .windows(pattern.len())
159            .position(|window| window == pattern)
160    }
161
162    /// Find all occurrences of a pattern in the mapped data
163    pub fn find_all_patterns(&self, pattern: &[u8]) -> Vec<usize> {
164        let mut positions = Vec::new();
165        let mut start = 0;
166
167        while start + pattern.len() <= self.size {
168            if let Some(pos) = self.mmap[start..]
169                .windows(pattern.len())
170                .position(|window| window == pattern)
171            {
172                positions.push(start + pos);
173                start += pos + 1;
174            } else {
175                break;
176            }
177        }
178
179        positions
180    }
181
182    /// Check if the mapped data starts with a specific magic signature
183    pub fn starts_with(&self, signature: &[u8]) -> bool {
184        if signature.len() > self.size {
185            return false;
186        }
187
188        &self.mmap[..signature.len()] == signature
189    }
190
191    /// Get a hexdump of a specific region
192    pub fn hexdump(&self, offset: usize, length: usize) -> Result<String> {
193        let data = self.read_at(offset, length)?;
194        Ok(format_hexdump(data, offset))
195    }
196
197    /// Create a safe view into a portion of the mapped data
198    pub fn view(&self, range: Range<usize>) -> Result<MappedView<'_>> {
199        if range.end > self.size {
200            return Err(BinaryError::memory_map(
201                "Range exceeds file size".to_string(),
202            ));
203        }
204
205        Ok(MappedView {
206            data: &self.mmap[range.clone()],
207            offset: range.start,
208        })
209    }
210}
211
212impl Deref for MappedBinary {
213    type Target = [u8];
214
215    fn deref(&self) -> &Self::Target {
216        &self.mmap
217    }
218}
219
220/// A view into a portion of a memory-mapped binary
221#[derive(Debug)]
222pub struct MappedView<'a> {
223    data: &'a [u8],
224    offset: usize,
225}
226
227impl<'a> MappedView<'a> {
228    /// Get the offset of this view within the original file
229    pub fn offset(&self) -> usize {
230        self.offset
231    }
232
233    /// Get the size of this view
234    pub fn size(&self) -> usize {
235        self.data.len()
236    }
237
238    /// Convert to a byte vector
239    pub fn to_vec(&self) -> Vec<u8> {
240        self.data.to_vec()
241    }
242}
243
244impl<'a> Deref for MappedView<'a> {
245    type Target = [u8];
246
247    fn deref(&self) -> &Self::Target {
248        self.data
249    }
250}
251
252/// Format data as a hexdump
253fn format_hexdump(data: &[u8], base_offset: usize) -> String {
254    let mut result = String::new();
255
256    for (i, chunk) in data.chunks(16).enumerate() {
257        let offset = base_offset + i * 16;
258        result.push_str(&format!("{:08x}: ", offset));
259
260        // Hex bytes
261        for (j, byte) in chunk.iter().enumerate() {
262            if j == 8 {
263                result.push(' ');
264            }
265            result.push_str(&format!("{:02x} ", byte));
266        }
267
268        // Padding for incomplete lines
269        for j in chunk.len()..16 {
270            if j == 8 {
271                result.push(' ');
272            }
273            result.push_str("   ");
274        }
275
276        // ASCII representation
277        result.push_str(" |");
278        for byte in chunk {
279            if byte.is_ascii_graphic() || *byte == b' ' {
280                result.push(*byte as char);
281            } else {
282                result.push('.');
283            }
284        }
285        result.push_str("|\n");
286    }
287
288    result
289}
290
291/// Memory mapping configuration
292#[derive(Debug, Clone, Default)]
293pub struct MmapConfig {
294    /// Whether to use huge pages if available
295    pub use_huge_pages: bool,
296    /// Whether to populate the mapping (fault pages immediately)
297    pub populate: bool,
298    /// Whether to lock the mapping in memory
299    pub lock_memory: bool,
300}
301
302/// Advanced memory mapping with configuration
303#[derive(Debug)]
304pub struct AdvancedMmap {
305    _file: File,
306    mmap: Mmap,
307    config: MmapConfig,
308}
309
310impl AdvancedMmap {
311    /// Create an advanced memory map with configuration
312    pub fn new<P: AsRef<Path>>(path: P, config: MmapConfig) -> Result<Self> {
313        let file = File::open(path)
314            .map_err(|e| BinaryError::memory_map(format!("Failed to open file: {}", e)))?;
315
316        let mut options = MmapOptions::new();
317
318        if config.populate {
319            options.populate();
320        }
321
322        let mmap = unsafe {
323            options.map(&file).map_err(|e| {
324                BinaryError::memory_map(format!("Failed to create memory map: {}", e))
325            })?
326        };
327
328        Ok(Self {
329            _file: file,
330            mmap,
331            config,
332        })
333    }
334
335    /// Get the underlying mapped data
336    pub fn data(&self) -> &[u8] {
337        &self.mmap
338    }
339
340    /// Get the configuration used
341    pub fn config(&self) -> &MmapConfig {
342        &self.config
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use std::io::Write;
350    use tempfile::NamedTempFile;
351
352    fn create_test_file() -> NamedTempFile {
353        let mut file = NamedTempFile::new().unwrap();
354        file.write_all(b"Hello, World! This is a test file.")
355            .unwrap();
356        file.flush().unwrap();
357        file
358    }
359
360    fn create_binary_test_file() -> NamedTempFile {
361        let mut file = NamedTempFile::new().unwrap();
362        // Create binary data with various byte values including null terminators
363        let data = vec![
364            0x12, 0x34, 0x56, 0x78, // u32 little endian: 0x78563412, big endian: 0x12345678
365            0xAB, 0xCD, 0xEF, 0x01, // u32 continuation
366            0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00, // "Hello\0"
367            0x57, 0x6F, 0x72, 0x6C, 0x64, 0x00, // "World\0"
368            0xFF, 0xFE, 0xFD, 0xFC, // More binary data
369            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 8 bytes for u64 test
370            0x41, 0x42, 0x43, // "ABC"
371            0x80, 0x81, 0x82, 0x83, // Non-ASCII bytes
372        ];
373        file.write_all(&data).unwrap();
374        file.flush().unwrap();
375        file
376    }
377
378    fn create_empty_file() -> NamedTempFile {
379        NamedTempFile::new().unwrap()
380    }
381
382    #[test]
383    fn test_mapped_binary_creation() {
384        let file = create_test_file();
385        let mapped = MappedBinary::new(file.path());
386        assert!(mapped.is_ok());
387
388        let mapped = mapped.unwrap();
389        assert_eq!(mapped.size(), 34);
390    }
391
392    #[test]
393    fn test_mapped_binary_from_file() {
394        let temp_file = create_test_file();
395        let file = File::open(temp_file.path()).unwrap();
396        let mapped = MappedBinary::from_file(file);
397        assert!(mapped.is_ok());
398
399        let mapped = mapped.unwrap();
400        assert_eq!(mapped.size(), 34);
401        assert_eq!(&mapped[0..5], b"Hello");
402    }
403
404    #[test]
405    fn test_mapped_binary_deref() {
406        let file = create_test_file();
407        let mapped = MappedBinary::new(file.path()).unwrap();
408
409        // Test Deref implementation
410        assert_eq!(&mapped[0..5], b"Hello");
411        assert_eq!(mapped.len(), 34);
412    }
413
414    #[test]
415    fn test_slice_method() {
416        let file = create_test_file();
417        let mapped = MappedBinary::new(file.path()).unwrap();
418
419        // Test successful slice
420        let slice = mapped.slice(0..5).unwrap();
421        assert_eq!(slice, b"Hello");
422
423        let slice = mapped.slice(7..12).unwrap();
424        assert_eq!(slice, b"World");
425
426        // Test error case - range exceeds file size
427        let result = mapped.slice(0..100);
428        assert!(result.is_err());
429        assert!(result
430            .unwrap_err()
431            .to_string()
432            .contains("Range exceeds file size"));
433    }
434
435    #[test]
436    fn test_read_operations() {
437        let file = create_test_file();
438        let mapped = MappedBinary::new(file.path()).unwrap();
439
440        // Test read_at
441        let data = mapped.read_at(0, 5).unwrap();
442        assert_eq!(data, b"Hello");
443
444        // Test read_bytes
445        let bytes = mapped.read_bytes(7, 5).unwrap();
446        assert_eq!(bytes, b"World".to_vec());
447
448        // Test read_u8
449        let byte = mapped.read_u8(0).unwrap();
450        assert_eq!(byte, b'H');
451    }
452
453    #[test]
454    fn test_read_operations_errors() {
455        let file = create_test_file();
456        let mapped = MappedBinary::new(file.path()).unwrap();
457
458        // Test read_at with out of bounds
459        let result = mapped.read_at(0, 100);
460        assert!(result.is_err());
461        assert!(result
462            .unwrap_err()
463            .to_string()
464            .contains("Read exceeds file size"));
465
466        // Test read_at with offset out of bounds
467        let result = mapped.read_at(50, 5);
468        assert!(result.is_err());
469
470        // Test read_bytes with out of bounds
471        let result = mapped.read_bytes(0, 100);
472        assert!(result.is_err());
473
474        // Test read_u8 with offset out of bounds
475        let result = mapped.read_u8(100);
476        assert!(result.is_err());
477        assert!(result
478            .unwrap_err()
479            .to_string()
480            .contains("Offset exceeds file size"));
481    }
482
483    #[test]
484    fn test_integer_read_operations() {
485        let file = create_binary_test_file();
486        let mapped = MappedBinary::new(file.path()).unwrap();
487
488        // Test u16 reads (bytes 0-1: 0x12, 0x34)
489        let val_le = mapped.read_u16_le(0).unwrap();
490        assert_eq!(val_le, 0x3412); // Little endian
491
492        let val_be = mapped.read_u16_be(0).unwrap();
493        assert_eq!(val_be, 0x1234); // Big endian
494
495        // Test u32 reads (bytes 0-3: 0x12, 0x34, 0x56, 0x78)
496        let val_le = mapped.read_u32_le(0).unwrap();
497        assert_eq!(val_le, 0x78563412); // Little endian
498
499        let val_be = mapped.read_u32_be(0).unwrap();
500        assert_eq!(val_be, 0x12345678); // Big endian
501
502        // Test u64 reads at position 24 where our 8-byte sequence 0x00..0x07 is located
503        let val_le = mapped.read_u64_le(24).unwrap();
504        assert_eq!(val_le, 0x0706050403020100); // Little endian
505
506        let val_be = mapped.read_u64_be(24).unwrap();
507        assert_eq!(val_be, 0x0001020304050607); // Big endian
508    }
509
510    #[test]
511    fn test_integer_read_operations_errors() {
512        let file = create_binary_test_file();
513        let mapped = MappedBinary::new(file.path()).unwrap();
514        let file_size = mapped.size();
515
516        // Test u16 read errors
517        assert!(mapped.read_u16_le(file_size).is_err());
518        assert!(mapped.read_u16_be(file_size - 1).is_err());
519
520        // Test u32 read errors
521        assert!(mapped.read_u32_le(file_size).is_err());
522        assert!(mapped.read_u32_be(file_size - 3).is_err());
523
524        // Test u64 read errors
525        assert!(mapped.read_u64_le(file_size).is_err());
526        assert!(mapped.read_u64_be(file_size - 7).is_err());
527    }
528
529    #[test]
530    fn test_read_cstring() {
531        let file = create_binary_test_file();
532        let mapped = MappedBinary::new(file.path()).unwrap();
533
534        // Test reading "Hello\0" starting at byte 8
535        let s = mapped.read_cstring(8, 10).unwrap();
536        assert_eq!(s, "Hello");
537
538        // Test reading "World\0" starting at byte 14
539        let s = mapped.read_cstring(14, 10).unwrap();
540        assert_eq!(s, "World");
541
542        // Test with max_length limit
543        let s = mapped.read_cstring(8, 3).unwrap();
544        assert_eq!(s, "Hel");
545
546        // Test reading from offset that would go beyond file
547        let result = mapped.read_cstring(8, 1000);
548        assert!(result.is_ok()); // Should succeed but be limited by file size
549    }
550
551    #[test]
552    fn test_read_cstring_errors() {
553        let mut file = NamedTempFile::new().unwrap();
554        // Create data with invalid UTF-8
555        file.write_all(&[0xFF, 0xFE, 0x00]).unwrap();
556        file.flush().unwrap();
557
558        let mapped = MappedBinary::new(file.path()).unwrap();
559
560        // Test invalid UTF-8 string
561        let result = mapped.read_cstring(0, 10);
562        assert!(result.is_err());
563        assert!(result
564            .unwrap_err()
565            .to_string()
566            .contains("Invalid UTF-8 string"));
567    }
568
569    #[test]
570    fn test_pattern_finding() {
571        let file = create_test_file();
572        let mapped = MappedBinary::new(file.path()).unwrap();
573
574        // Test find_pattern
575        let pos = mapped.find_pattern(b"World");
576        assert_eq!(pos, Some(7));
577
578        let pos = mapped.find_pattern(b"xyz");
579        assert_eq!(pos, None);
580
581        // Test find_pattern with single byte pattern
582        let pos = mapped.find_pattern(b"H");
583        assert_eq!(pos, Some(0));
584    }
585
586    #[test]
587    fn test_find_all_patterns() {
588        let mut file = NamedTempFile::new().unwrap();
589        file.write_all(b"ababcabab").unwrap();
590        file.flush().unwrap();
591
592        let mapped = MappedBinary::new(file.path()).unwrap();
593
594        // Test find_all_patterns with multiple occurrences
595        let positions = mapped.find_all_patterns(b"ab");
596        assert_eq!(positions, vec![0, 2, 5, 7]);
597
598        // Test with single occurrence
599        let positions = mapped.find_all_patterns(b"abc");
600        assert_eq!(positions, vec![2]);
601
602        // Test with no occurrences
603        let positions = mapped.find_all_patterns(b"xyz");
604        assert_eq!(positions, Vec::<usize>::new());
605
606        // Test with single byte pattern
607        let positions = mapped.find_all_patterns(b"a");
608        assert_eq!(positions, vec![0, 2, 5, 7]);
609    }
610
611    #[test]
612    fn test_pattern_edge_cases() {
613        let file = create_test_file();
614        let mapped = MappedBinary::new(file.path()).unwrap();
615
616        // Test pattern longer than file
617        let long_pattern = vec![b'A'; 1000];
618        let result = mapped.find_pattern(&long_pattern);
619        assert_eq!(result, None);
620
621        // Test pattern at end of file
622        let result = mapped.find_pattern(b"file.");
623        assert!(result.is_some());
624    }
625
626    #[test]
627    fn test_starts_with() {
628        let file = create_test_file();
629        let mapped = MappedBinary::new(file.path()).unwrap();
630
631        assert!(mapped.starts_with(b"Hello"));
632        assert!(!mapped.starts_with(b"World"));
633        assert!(mapped.starts_with(b"")); // Empty pattern should match
634
635        // Test with signature longer than file
636        let mut long_signature = vec![0; 100];
637        long_signature[0] = b'H';
638        assert!(!mapped.starts_with(&long_signature));
639    }
640
641    #[test]
642    fn test_hexdump_method() {
643        let file = create_binary_test_file();
644        let mapped = MappedBinary::new(file.path()).unwrap();
645
646        // Test hexdump method
647        let hexdump = mapped.hexdump(0, 8).unwrap();
648        assert!(hexdump.contains("00000000:"));
649        assert!(hexdump.contains("12 34 56 78"));
650
651        // Test error case
652        let result = mapped.hexdump(0, 1000);
653        assert!(result.is_err());
654    }
655
656    #[test]
657    fn test_view_creation() {
658        let file = create_test_file();
659        let mapped = MappedBinary::new(file.path()).unwrap();
660
661        let view = mapped.view(0..5).unwrap();
662        assert_eq!(view.size(), 5);
663        assert_eq!(view.offset(), 0);
664        assert_eq!(&*view, b"Hello");
665
666        // Test error case
667        let result = mapped.view(0..100);
668        assert!(result.is_err());
669        assert!(result
670            .unwrap_err()
671            .to_string()
672            .contains("Range exceeds file size"));
673    }
674
675    #[test]
676    fn test_mapped_view_methods() {
677        let file = create_test_file();
678        let mapped = MappedBinary::new(file.path()).unwrap();
679
680        let view = mapped.view(7..12).unwrap();
681        assert_eq!(view.offset(), 7);
682        assert_eq!(view.size(), 5);
683
684        // Test to_vec method
685        let vec = view.to_vec();
686        assert_eq!(vec, b"World".to_vec());
687
688        // Test Deref implementation
689        assert_eq!(&*view, b"World");
690        assert_eq!(view.len(), 5);
691    }
692
693    #[test]
694    fn test_hexdump_function() {
695        // Test basic hexdump
696        let data = b"Hello, World!";
697        let hexdump = format_hexdump(data, 0);
698        assert!(hexdump.contains("48 65 6c 6c 6f 2c 20 57"));
699        assert!(hexdump.contains("Hello, W"));
700
701        // Test empty data
702        let hexdump = format_hexdump(&[], 0);
703        assert_eq!(hexdump, "");
704
705        // Test data with non-printable characters
706        let data = &[
707            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
708            0x0E, 0x0F,
709        ];
710        let hexdump = format_hexdump(data, 0x1000);
711        assert!(hexdump.contains("00001000:"));
712        assert!(hexdump.contains("00 01 02 03 04 05 06 07"));
713        assert!(hexdump.contains("................"));
714
715        // Test data longer than 16 bytes
716        let data = b"This is a longer string that spans multiple lines";
717        let hexdump = format_hexdump(data, 0);
718        let lines: Vec<&str> = hexdump.lines().collect();
719        assert!(lines.len() > 1); // Should have multiple lines
720
721        // Test data with mixed printable and non-printable
722        let data = &[b'A', b'B', 0xFF, b'C', b'D'];
723        let hexdump = format_hexdump(data, 0);
724        assert!(hexdump.contains("AB.CD"));
725    }
726
727    #[test]
728    fn test_empty_file_handling() {
729        let file = create_empty_file();
730        let mapped = MappedBinary::new(file.path()).unwrap();
731
732        assert_eq!(mapped.size(), 0);
733
734        // Test operations on empty file
735        assert!(mapped.read_u8(0).is_err());
736        assert!(mapped.read_at(0, 1).is_err());
737        assert_eq!(mapped.find_pattern(b"test"), None);
738        assert_eq!(mapped.find_all_patterns(b"test"), Vec::<usize>::new());
739        assert!(mapped.starts_with(b"")); // Empty pattern should match empty file
740        assert!(!mapped.starts_with(b"test"));
741
742        let result = mapped.view(0..1);
743        assert!(result.is_err());
744    }
745
746    #[test]
747    fn test_mmap_config_default() {
748        let config = MmapConfig::default();
749        assert!(!config.use_huge_pages);
750        assert!(!config.populate);
751        assert!(!config.lock_memory);
752    }
753
754    #[test]
755    fn test_mmap_config_debug() {
756        let config = MmapConfig {
757            use_huge_pages: true,
758            populate: false,
759            lock_memory: true,
760        };
761        let debug_str = format!("{:?}", config);
762        assert!(debug_str.contains("use_huge_pages: true"));
763        assert!(debug_str.contains("populate: false"));
764        assert!(debug_str.contains("lock_memory: true"));
765    }
766
767    #[test]
768    fn test_mmap_config_clone() {
769        let config = MmapConfig {
770            use_huge_pages: true,
771            populate: true,
772            lock_memory: false,
773        };
774        let cloned = config.clone();
775        assert!(cloned.use_huge_pages);
776        assert!(cloned.populate);
777        assert!(!cloned.lock_memory);
778    }
779
780    #[test]
781    fn test_advanced_mmap() {
782        let file = create_test_file();
783        let config = MmapConfig::default();
784        let advanced = AdvancedMmap::new(file.path(), config.clone()).unwrap();
785
786        assert_eq!(advanced.data().len(), 34);
787        assert_eq!(advanced.data()[0..5], *b"Hello");
788
789        let returned_config = advanced.config();
790        assert_eq!(returned_config.use_huge_pages, config.use_huge_pages);
791        assert_eq!(returned_config.populate, config.populate);
792        assert_eq!(returned_config.lock_memory, config.lock_memory);
793    }
794
795    #[test]
796    fn test_advanced_mmap_with_populate() {
797        let file = create_test_file();
798        let config = MmapConfig {
799            use_huge_pages: false,
800            populate: true,
801            lock_memory: false,
802        };
803        let advanced = AdvancedMmap::new(file.path(), config).unwrap();
804
805        assert_eq!(advanced.data().len(), 34);
806        assert!(advanced.config().populate);
807    }
808
809    #[test]
810    fn test_file_not_found_error() {
811        let result = MappedBinary::new("/nonexistent/file/path");
812        assert!(result.is_err());
813        let error = result.unwrap_err();
814        assert!(error.to_string().contains("Failed to open file"));
815    }
816
817    #[test]
818    fn test_advanced_mmap_file_not_found_error() {
819        let config = MmapConfig::default();
820        let result = AdvancedMmap::new("/nonexistent/file/path", config);
821        assert!(result.is_err());
822        let error = result.unwrap_err();
823        assert!(error.to_string().contains("Failed to open file"));
824    }
825
826    #[test]
827    fn test_large_data_operations() {
828        let mut file = NamedTempFile::new().unwrap();
829        let large_data = vec![0xAB; 10000]; // 10KB of 0xAB
830        file.write_all(&large_data).unwrap();
831        file.flush().unwrap();
832
833        let mapped = MappedBinary::new(file.path()).unwrap();
834
835        // Test reading from various positions
836        assert_eq!(mapped.read_u8(5000).unwrap(), 0xAB);
837        assert_eq!(mapped.read_bytes(1000, 100).unwrap().len(), 100);
838
839        // Test pattern finding in large data
840        let positions = mapped.find_all_patterns(&[0xAB, 0xAB]);
841        assert!(positions.len() > 1000); // Should find many overlapping patterns
842
843        // Test hexdump with large offset
844        let hexdump = mapped.hexdump(8000, 32).unwrap();
845        assert!(hexdump.contains("00001f40:")); // 8000 in hex
846    }
847
848    #[test]
849    fn test_boundary_conditions() {
850        let file = create_test_file(); // 34 bytes: "Hello, World! This is a test file."
851        let mapped = MappedBinary::new(file.path()).unwrap();
852
853        // Test reading at exact file boundary
854        assert!(mapped.read_u8(33).is_ok()); // Last byte
855        assert!(mapped.read_u8(34).is_err()); // One past end
856
857        // Test reading exactly to the end
858        assert!(mapped.read_at(30, 4).is_ok()); // Last 4 bytes
859        assert!(mapped.read_at(30, 5).is_err()); // One byte too many
860
861        // Test slice at boundary
862        assert!(mapped.slice(0..34).is_ok()); // Entire file
863        assert!(mapped.slice(0..35).is_err()); // One byte too many
864
865        // Test view at boundary
866        assert!(mapped.view(0..34).is_ok()); // Entire file
867        assert!(mapped.view(0..35).is_err()); // One byte too many
868    }
869}