memscope_rs/export/binary/
reader.rs

1//! Binary data reader for parsing allocation records from binary files
2
3use crate::core::types::AllocationInfo;
4use crate::export::binary::error::BinaryExportError;
5use crate::export::binary::format::{
6    AdvancedMetricsHeader, FileHeader, MetricsBitmapFlags, ALLOCATION_RECORD_TYPE, HEADER_SIZE,
7};
8use crate::export::binary::serializable::{primitives, BinarySerializable};
9use crate::export::binary::string_table::StringTable;
10use std::collections::HashMap;
11use std::fs::File;
12#[cfg(test)]
13use std::io::Write;
14use std::io::{BufReader, Read, Seek, SeekFrom};
15use std::path::Path;
16
17/// Binary reader for allocation records using buffered I/O
18pub struct BinaryReader {
19    reader: BufReader<File>,
20    advanced_metrics: Option<AdvancedMetricsData>,
21    string_table: Option<StringTable>,
22    file_version: Option<u32>,
23}
24
25/// Container for advanced metrics data read from binary file
26#[derive(Debug, Clone)]
27pub struct AdvancedMetricsData {
28    pub lifecycle_metrics: HashMap<u64, LifecycleMetric>,
29    pub container_metrics: HashMap<u64, String>, // JSON for now
30    pub type_usage_metrics: HashMap<u64, String>, // JSON for now
31}
32
33/// Lifecycle metric data
34#[derive(Debug, Clone)]
35pub struct LifecycleMetric {
36    pub lifetime_ms: u64,
37    pub lifecycle_tracking: Option<String>, // JSON for now
38}
39
40impl BinaryReader {
41    /// Create new binary reader for the specified file path
42    pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, BinaryExportError> {
43        let file = File::open(path)?;
44        let reader = BufReader::new(file);
45
46        Ok(Self {
47            reader,
48            advanced_metrics: None,
49            string_table: None,
50            file_version: None,
51        })
52    }
53
54    /// Read and validate file header
55    pub fn read_header(&mut self) -> Result<FileHeader, BinaryExportError> {
56        let mut header_bytes = [0u8; HEADER_SIZE];
57        self.reader.read_exact(&mut header_bytes)?;
58
59        let header = FileHeader::from_bytes(&header_bytes);
60
61        // Validate magic bytes
62        if !header.is_valid_magic() {
63            let expected = String::from_utf8_lossy(&header.magic);
64            let actual = String::from_utf8_lossy(b"MEMSCOPE");
65            return Err(BinaryExportError::InvalidMagic {
66                expected: expected.to_string(),
67                actual: actual.to_string(),
68            });
69        }
70
71        // Check version compatibility
72        if !header.is_compatible_version() {
73            return Err(BinaryExportError::UnsupportedVersion(header.version));
74        }
75
76        // Store version for version-specific handling
77        self.file_version = Some(header.get_version());
78
79        // Read string table after header
80        self.read_string_table()?;
81
82        Ok(header)
83    }
84
85    /// Read string table from file if present
86    fn read_string_table(&mut self) -> Result<(), BinaryExportError> {
87        // Read string table marker (4 bytes)
88        let mut marker = [0u8; 4];
89        self.reader.read_exact(&mut marker)?;
90
91        // Read table size (4 bytes)
92        let table_size = primitives::read_u32(&mut self.reader)?;
93
94        if &marker == b"STBL" && table_size > 0 {
95            // Read compression flag (new format)
96            let use_compressed_indices = primitives::read_u8(&mut self.reader)? != 0;
97
98            // Read string table
99            let string_count = primitives::read_u32(&mut self.reader)? as usize;
100            let mut strings = Vec::with_capacity(string_count);
101
102            for _ in 0..string_count {
103                let string = primitives::read_string(&mut self.reader)?;
104                strings.push(string);
105            }
106
107            // Create string table from read strings with compression setting
108            let mut string_table = StringTable::with_compression(use_compressed_indices);
109            for string in strings {
110                string_table.add_string(&string)?;
111            }
112            self.string_table = Some(string_table);
113        } else if &marker == b"NONE" {
114            // No string table present
115            self.string_table = None;
116        } else {
117            return Err(BinaryExportError::CorruptedData(
118                "Invalid string table marker".to_string(),
119            ));
120        }
121
122        Ok(())
123    }
124
125    /// Read single allocation record from current position
126    pub fn read_allocation(&mut self) -> Result<AllocationInfo, BinaryExportError> {
127        let file_version = self.file_version.unwrap_or(1);
128
129        match file_version {
130            1 => self.read_allocation_v1(),
131            2 | 3 => self.read_allocation_v2(), // Version 3 uses same format as v2 with improve.md extensions
132            _ => Err(BinaryExportError::UnsupportedVersion(file_version)),
133        }
134    }
135
136    /// Read allocation record for version 1 format (legacy - basic fields only)
137    fn read_allocation_v1(&mut self) -> Result<AllocationInfo, BinaryExportError> {
138        // Read Type (1 byte)
139        let mut type_byte = [0u8; 1];
140        self.reader.read_exact(&mut type_byte)?;
141
142        if type_byte[0] != ALLOCATION_RECORD_TYPE {
143            return Err(BinaryExportError::CorruptedData(format!(
144                "Invalid record type: {}",
145                type_byte[0]
146            )));
147        }
148
149        // Read Length (4 bytes)
150        let mut length_bytes = [0u8; 4];
151        self.reader.read_exact(&mut length_bytes)?;
152        let _record_length = u32::from_le_bytes(length_bytes);
153
154        // Read basic fields only for v1
155        let ptr = self.read_u64()? as usize;
156        let size = self.read_u64()? as usize;
157        let timestamp_alloc = self.read_u64()?;
158
159        // Read optional timestamp_dealloc
160        let timestamp_dealloc = if self.read_u8()? == 1 {
161            Some(self.read_u64()?)
162        } else {
163            None
164        };
165
166        // Read basic string fields
167        let var_name = self.read_optional_string()?;
168        let type_name = self.read_optional_string()?;
169        let scope_name = self.read_optional_string()?;
170        let thread_id = self.read_string()?;
171
172        // Read stack trace
173        let stack_trace = self.read_optional_string_vec()?;
174
175        // Read basic numeric fields
176        let borrow_count = self.read_u32()? as usize;
177        let is_leaked_byte = self.read_u8()?;
178        let is_leaked = is_leaked_byte != 0;
179
180        // Read optional lifetime_ms
181        let lifetime_flag = self.read_u8()?;
182        let lifetime_ms = if lifetime_flag == 1 {
183            Some(self.read_u64()?)
184        } else {
185            None
186        };
187
188        // For v1, set all advanced fields to None
189        Ok(AllocationInfo {
190            ptr,
191            size,
192            var_name,
193            type_name,
194            scope_name,
195            timestamp_alloc,
196            timestamp_dealloc,
197            thread_id,
198            borrow_count,
199            stack_trace,
200            is_leaked,
201            lifetime_ms,
202            borrow_info: None,
203            clone_info: None,
204            ownership_history_available: false,
205            smart_pointer_info: None,
206            memory_layout: None,
207            generic_info: None,
208            dynamic_type_info: None,
209            runtime_state: None,
210            stack_allocation: None,
211            temporary_object: None,
212            fragmentation_analysis: None,
213            generic_instantiation: None,
214            type_relationships: None,
215            type_usage: None,
216            function_call_tracking: None,
217            lifecycle_tracking: None,
218            access_tracking: None,
219            drop_chain_analysis: None,
220        })
221    }
222
223    /// Read allocation record for version 2 format (current - with advanced metrics)
224    fn read_allocation_v2(&mut self) -> Result<AllocationInfo, BinaryExportError> {
225        // Read Type (1 byte)
226        let mut type_byte = [0u8; 1];
227        self.reader.read_exact(&mut type_byte)?;
228
229        if type_byte[0] != ALLOCATION_RECORD_TYPE {
230            return Err(BinaryExportError::CorruptedData(format!(
231                "Invalid record type: {}",
232                type_byte[0]
233            )));
234        }
235
236        // Read Length (4 bytes)
237        let mut length_bytes = [0u8; 4];
238        self.reader.read_exact(&mut length_bytes)?;
239        let _record_length = u32::from_le_bytes(length_bytes);
240
241        // Read basic fields
242        let ptr = self.read_u64()? as usize;
243        let size = self.read_u64()? as usize;
244        let timestamp_alloc = self.read_u64()?;
245
246        // Read optional timestamp_dealloc
247        let timestamp_dealloc = if self.read_u8()? == 1 {
248            Some(self.read_u64()?)
249        } else {
250            None
251        };
252
253        // Read string fields
254        let var_name = self.read_optional_string()?;
255        let type_name = self.read_optional_string()?;
256        let scope_name = self.read_optional_string()?;
257        let thread_id = self.read_string()?;
258
259        // Read stack trace
260        let stack_trace = self.read_optional_string_vec()?;
261
262        // Read numeric fields
263        let borrow_count = self.read_u32()? as usize;
264        let is_leaked_byte = self.read_u8()?;
265        let is_leaked = is_leaked_byte != 0;
266
267        // Read optional lifetime_ms
268        let lifetime_flag = self.read_u8()?;
269        let lifetime_ms = if lifetime_flag == 1 {
270            Some(self.read_u64()?)
271        } else {
272            None
273        };
274
275        // Read improve.md extensions: borrow_info
276        let borrow_info = if self.read_u8()? == 1 {
277            let immutable_borrows = self.read_u32()? as usize;
278            let mutable_borrows = self.read_u32()? as usize;
279            let max_concurrent_borrows = self.read_u32()? as usize;
280            let last_borrow_timestamp = if self.read_u8()? == 1 {
281                Some(self.read_u64()?)
282            } else {
283                None
284            };
285            Some(crate::core::types::BorrowInfo {
286                immutable_borrows,
287                mutable_borrows,
288                max_concurrent_borrows,
289                last_borrow_timestamp,
290            })
291        } else {
292            None
293        };
294
295        // Read improve.md extensions: clone_info
296        let clone_info = if self.read_u8()? == 1 {
297            let clone_count = self.read_u32()? as usize;
298            let is_clone = self.read_u8()? != 0;
299            let original_ptr = if self.read_u8()? == 1 {
300                Some(self.read_u64()? as usize)
301            } else {
302                None
303            };
304            Some(crate::core::types::CloneInfo {
305                clone_count,
306                is_clone,
307                original_ptr,
308            })
309        } else {
310            None
311        };
312
313        // Read improve.md extensions: ownership_history_available
314        let ownership_history_available = self.read_u8()? != 0;
315
316        // Read advanced fields (v2 only)
317        let smart_pointer_info = self.read_optional_binary_field()?;
318        let memory_layout = self.read_optional_binary_field()?;
319
320        let generic_info = self.read_optional_json_field()?;
321        let dynamic_type_info = self.read_optional_json_field()?;
322        let runtime_state = self.read_optional_json_field()?;
323        let stack_allocation = self.read_optional_json_field()?;
324        let temporary_object = self.read_optional_json_field()?;
325        let fragmentation_analysis = self.read_optional_json_field()?;
326        let generic_instantiation = self.read_optional_json_field()?;
327        let type_relationships = self.read_optional_json_field()?;
328        let type_usage = self.read_optional_json_field()?;
329        let function_call_tracking = self.read_optional_json_field()?;
330        let lifecycle_tracking = self.read_optional_json_field()?;
331        let access_tracking = self.read_optional_json_field()?;
332
333        Ok(AllocationInfo {
334            ptr,
335            size,
336            var_name,
337            type_name,
338            scope_name,
339            timestamp_alloc,
340            timestamp_dealloc,
341            thread_id,
342            borrow_count,
343            stack_trace,
344            is_leaked,
345            lifetime_ms,
346            borrow_info,
347            clone_info,
348            ownership_history_available,
349            smart_pointer_info,
350            memory_layout,
351            generic_info,
352            dynamic_type_info,
353            runtime_state,
354            stack_allocation,
355            temporary_object,
356            fragmentation_analysis,
357            generic_instantiation,
358            type_relationships,
359            type_usage,
360            function_call_tracking,
361            lifecycle_tracking,
362            access_tracking,
363            drop_chain_analysis: None,
364        })
365    }
366
367    /// Read all allocation records from file with improved error handling
368    pub fn read_all(&mut self) -> Result<Vec<AllocationInfo>, BinaryExportError> {
369        let header = self.read_header()?;
370        let mut allocations = Vec::with_capacity(header.total_count as usize);
371
372        // Read allocations with better error handling
373        for i in 0..header.total_count {
374            match self.read_allocation() {
375                Ok(allocation) => allocations.push(allocation),
376                Err(BinaryExportError::Io(ref e))
377                    if e.kind() == std::io::ErrorKind::UnexpectedEof =>
378                {
379                    // Handle partial reads gracefully
380                    tracing::warn!(
381                        "Reached end of file after reading {} of {} allocations",
382                        i,
383                        header.total_count
384                    );
385                    break;
386                }
387                Err(e) => return Err(e),
388            }
389        }
390
391        // Try to read advanced metrics segment if present (optional)
392        if self.try_read_advanced_metrics_segment().is_err() {
393            // Advanced metrics segment not present or corrupted, continue without it
394            tracing::debug!("No advanced metrics segment found or failed to read");
395        }
396
397        tracing::info!("Successfully read {} allocation records", allocations.len());
398        Ok(allocations)
399    }
400
401    /// Try to read advanced metrics segment (backward compatible)
402    fn try_read_advanced_metrics_segment(&mut self) -> Result<(), BinaryExportError> {
403        // Try to read advanced metrics header with partial read handling
404        let mut header_bytes = [0u8; 16];
405        let mut bytes_read = 0;
406
407        // Read as much as possible without failing on partial reads
408        while bytes_read < 16 {
409            match self.reader.read(&mut header_bytes[bytes_read..]) {
410                Ok(0) => {
411                    // End of file reached
412                    if bytes_read == 0 {
413                        // No advanced metrics segment at all
414                        return Err(BinaryExportError::CorruptedData(
415                            "No advanced metrics segment".to_string(),
416                        ));
417                    } else {
418                        // Partial read, file is truncated
419                        tracing::warn!(
420                            "File appears to be truncated, only read {} of 16 header bytes",
421                            bytes_read
422                        );
423                        return Err(BinaryExportError::CorruptedData(
424                            "Truncated advanced metrics header".to_string(),
425                        ));
426                    }
427                }
428                Ok(n) => bytes_read += n,
429                Err(e) => {
430                    tracing::debug!("Failed to read advanced metrics header: {}", e);
431                    return Err(BinaryExportError::Io(e));
432                }
433            }
434        }
435
436        let header = AdvancedMetricsHeader::from_bytes(&header_bytes);
437
438        if header.is_valid_magic() {
439            // Valid advanced metrics segment found
440            self.read_advanced_metrics_data(header)?;
441        } else {
442            // Not an advanced metrics segment, seek back if possible
443            if let Err(e) = self.reader.seek(SeekFrom::Current(-16)) {
444                tracing::debug!("Failed to seek back: {}", e);
445            }
446            return Err(BinaryExportError::CorruptedData(
447                "Invalid advanced metrics magic".to_string(),
448            ));
449        }
450
451        Ok(())
452    }
453
454    /// Read advanced metrics data based on header
455    fn read_advanced_metrics_data(
456        &mut self,
457        header: AdvancedMetricsHeader,
458    ) -> Result<(), BinaryExportError> {
459        let mut lifecycle_metrics = HashMap::new();
460        let mut container_metrics = HashMap::new();
461        let mut type_usage_metrics = HashMap::new();
462
463        // Read lifecycle analysis data if enabled
464        if MetricsBitmapFlags::is_enabled(
465            header.metrics_bitmap,
466            MetricsBitmapFlags::LifecycleAnalysis,
467        ) {
468            lifecycle_metrics = self.read_lifecycle_metrics()?;
469        }
470
471        // Read container analysis data if enabled
472        if MetricsBitmapFlags::is_enabled(
473            header.metrics_bitmap,
474            MetricsBitmapFlags::ContainerAnalysis,
475        ) {
476            container_metrics = self.read_container_metrics()?;
477        }
478
479        // Read type usage statistics if enabled
480        if MetricsBitmapFlags::is_enabled(header.metrics_bitmap, MetricsBitmapFlags::TypeUsageStats)
481        {
482            type_usage_metrics = self.read_type_usage_metrics()?;
483        }
484
485        // Store advanced metrics data
486        self.advanced_metrics = Some(AdvancedMetricsData {
487            lifecycle_metrics,
488            container_metrics,
489            type_usage_metrics,
490        });
491
492        Ok(())
493    }
494
495    /// Read lifecycle metrics from advanced segment
496    fn read_lifecycle_metrics(
497        &mut self,
498    ) -> Result<HashMap<u64, LifecycleMetric>, BinaryExportError> {
499        let count = self.read_u32()? as usize;
500        let mut metrics = HashMap::with_capacity(count);
501
502        for _ in 0..count {
503            let ptr = self.read_u64()?;
504            let lifetime_ms = self.read_u64()?;
505
506            let lifecycle_tracking = if self.read_u8()? == 1 {
507                Some(self.read_string()?)
508            } else {
509                None
510            };
511
512            metrics.insert(
513                ptr,
514                LifecycleMetric {
515                    lifetime_ms,
516                    lifecycle_tracking,
517                },
518            );
519        }
520
521        Ok(metrics)
522    }
523
524    /// Read container metrics from advanced segment
525    fn read_container_metrics(&mut self) -> Result<HashMap<u64, String>, BinaryExportError> {
526        let count = self.read_u32()? as usize;
527        let mut metrics = HashMap::with_capacity(count);
528
529        for _ in 0..count {
530            let ptr = self.read_u64()?;
531            let json_data = self.read_string()?;
532            metrics.insert(ptr, json_data);
533        }
534
535        Ok(metrics)
536    }
537
538    /// Read type usage metrics from advanced segment
539    fn read_type_usage_metrics(&mut self) -> Result<HashMap<u64, String>, BinaryExportError> {
540        let count = self.read_u32()? as usize;
541        let mut metrics = HashMap::with_capacity(count);
542
543        for _ in 0..count {
544            let ptr = self.read_u64()?;
545            let json_data = self.read_string()?;
546            metrics.insert(ptr, json_data);
547        }
548
549        Ok(metrics)
550    }
551
552    /// Get advanced metrics data if available
553    pub fn get_advanced_metrics(&self) -> Option<&AdvancedMetricsData> {
554        self.advanced_metrics.as_ref()
555    }
556
557    /// Read u64 value in Little Endian format
558    fn read_u64(&mut self) -> Result<u64, BinaryExportError> {
559        let mut bytes = [0u8; 8];
560        self.reader.read_exact(&mut bytes)?;
561        Ok(u64::from_le_bytes(bytes))
562    }
563
564    /// Read u32 value in Little Endian format
565    fn read_u32(&mut self) -> Result<u32, BinaryExportError> {
566        let mut bytes = [0u8; 4];
567        self.reader.read_exact(&mut bytes)?;
568        Ok(u32::from_le_bytes(bytes))
569    }
570
571    /// Read optional string with length prefix or string table reference
572    fn read_optional_string(&mut self) -> Result<Option<String>, BinaryExportError> {
573        let mut length_bytes = [0u8; 4];
574        self.reader.read_exact(&mut length_bytes)?;
575        let length = u32::from_le_bytes(length_bytes);
576
577        if length == 0xFFFFFFFE {
578            // None marker
579            Ok(None)
580        } else if length == 0xFFFF {
581            // String table reference
582            let index = primitives::read_u16(&mut self.reader)?;
583            if let Some(ref string_table) = self.string_table {
584                if let Some(string) = string_table.get_string(index) {
585                    Ok(Some(string.to_string()))
586                } else {
587                    Err(BinaryExportError::CorruptedData(format!(
588                        "Invalid string table index: {index}",
589                    )))
590                }
591            } else {
592                Err(BinaryExportError::CorruptedData(
593                    "String table reference found but no string table loaded".to_string(),
594                ))
595            }
596        } else {
597            // Inline string (including empty strings with length 0)
598            let mut string_bytes = vec![0u8; length as usize];
599            self.reader.read_exact(&mut string_bytes)?;
600
601            let string = String::from_utf8(string_bytes).map_err(|_| {
602                BinaryExportError::CorruptedData("Invalid UTF-8 string".to_string())
603            })?;
604
605            Ok(Some(string))
606        }
607    }
608
609    /// Read string with length prefix or string table reference
610    fn read_string(&mut self) -> Result<String, BinaryExportError> {
611        let mut length_bytes = [0u8; 4];
612        self.reader.read_exact(&mut length_bytes)?;
613        let length = u32::from_le_bytes(length_bytes);
614
615        if length == 0xFFFF {
616            // String table reference
617            let index = primitives::read_u16(&mut self.reader)?;
618            if let Some(ref string_table) = self.string_table {
619                if let Some(string) = string_table.get_string(index) {
620                    Ok(string.to_string())
621                } else {
622                    Err(BinaryExportError::CorruptedData(format!(
623                        "Invalid string table index: {index}",
624                    )))
625                }
626            } else {
627                Err(BinaryExportError::CorruptedData(
628                    "String table reference found but no string table loaded".to_string(),
629                ))
630            }
631        } else {
632            // Inline string
633            let mut string_bytes = vec![0u8; length as usize];
634            self.reader.read_exact(&mut string_bytes)?;
635
636            String::from_utf8(string_bytes)
637                .map_err(|_| BinaryExportError::CorruptedData("Invalid UTF-8 string".to_string()))
638        }
639    }
640
641    /// Read an optional vector of strings
642    fn read_optional_string_vec(&mut self) -> Result<Option<Vec<String>>, BinaryExportError> {
643        let mut count_bytes = [0u8; 4];
644        self.reader.read_exact(&mut count_bytes)?;
645        let count = u32::from_le_bytes(count_bytes) as usize;
646
647        if count == 0 {
648            Ok(None)
649        } else {
650            let mut strings = Vec::with_capacity(count);
651            for _ in 0..count {
652                strings.push(self.read_string()?);
653            }
654            Ok(Some(strings))
655        }
656    }
657
658    /// Read single byte
659    fn read_u8(&mut self) -> Result<u8, BinaryExportError> {
660        let mut buffer = [0u8; 1];
661        self.reader.read_exact(&mut buffer)?;
662        Ok(buffer[0])
663    }
664
665    /// Read optional binary field using BinarySerializable trait
666    fn read_optional_binary_field<T: BinarySerializable>(
667        &mut self,
668    ) -> Result<Option<T>, BinaryExportError> {
669        if self.read_u8()? == 1 {
670            Ok(Some(T::read_binary(&mut self.reader)?))
671        } else {
672            Ok(None)
673        }
674    }
675
676    /// Read optional JSON field
677    fn read_optional_json_field<T: serde::de::DeserializeOwned>(
678        &mut self,
679    ) -> Result<Option<T>, BinaryExportError> {
680        if self.read_u8()? == 1 {
681            let json_str = self.read_string()?;
682            let value = serde_json::from_str(&json_str).map_err(|e| {
683                BinaryExportError::CorruptedData(format!("JSON deserialization failed: {e}"))
684            })?;
685            Ok(Some(value))
686        } else {
687            Ok(None)
688        }
689    }
690}
691
692#[cfg(test)]
693mod tests {
694    use super::*;
695    use crate::export::binary::writer::BinaryWriter;
696    use tempfile::NamedTempFile;
697
698    fn create_test_allocation() -> AllocationInfo {
699        AllocationInfo {
700            ptr: 0x1000,
701            size: 1024,
702            var_name: Some("test_var".to_string()),
703            type_name: Some("i32".to_string()),
704            scope_name: None,
705            timestamp_alloc: 1234567890,
706            timestamp_dealloc: None,
707            thread_id: "main".to_string(),
708            borrow_count: 0,
709            stack_trace: None,
710            is_leaked: false,
711            lifetime_ms: None,
712            borrow_info: None,
713            clone_info: None,
714            ownership_history_available: false,
715            smart_pointer_info: None,
716            memory_layout: None,
717            generic_info: None,
718            dynamic_type_info: None,
719            runtime_state: None,
720            stack_allocation: None,
721            temporary_object: None,
722            fragmentation_analysis: None,
723            generic_instantiation: None,
724            type_relationships: None,
725            type_usage: None,
726            function_call_tracking: None,
727            lifecycle_tracking: None,
728            access_tracking: None,
729            drop_chain_analysis: None,
730        }
731    }
732
733    #[test]
734    fn test_reader_creation() {
735        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
736
737        // Create empty file first
738        {
739            let mut writer =
740                BinaryWriter::new(temp_file.path()).expect("Failed to create temp file");
741            writer.write_header(0).expect("Failed to write header");
742            writer.finish().expect("Failed to finish writing");
743        }
744
745        let reader = BinaryReader::new(temp_file.path());
746        assert!(reader.is_ok());
747    }
748
749    #[test]
750    fn test_header_reading() {
751        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
752
753        // Write test data
754        {
755            let mut writer =
756                BinaryWriter::new(temp_file.path()).expect("Failed to create temp file");
757            writer.write_header(42).expect("Failed to write header");
758            writer.finish().expect("Failed to finish writing");
759        }
760
761        // Read and verify
762        let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create temp file");
763        let header = reader
764            .read_header()
765            .expect("Failed to read from binary file");
766
767        assert_eq!(header.total_count, 42);
768        assert!(header.is_valid_magic());
769        assert!(header.is_compatible_version());
770    }
771
772    #[test]
773    fn test_allocation_round_trip() {
774        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
775        let original_alloc = create_test_allocation();
776
777        // Write test data
778        {
779            let mut writer =
780                BinaryWriter::new(temp_file.path()).expect("Failed to create temp file");
781            writer.write_header(1).expect("Failed to write header");
782            writer
783                .write_allocation(&original_alloc)
784                .expect("Failed to write allocation");
785            writer.finish().expect("Failed to finish writing");
786        }
787
788        // Read and verify
789        let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create temp file");
790        let allocations = reader.read_all().expect("Failed to read from binary file");
791
792        assert_eq!(allocations.len(), 1);
793        let read_alloc = &allocations[0];
794
795        assert_eq!(read_alloc.ptr, original_alloc.ptr);
796        assert_eq!(read_alloc.size, original_alloc.size);
797        assert_eq!(read_alloc.timestamp_alloc, original_alloc.timestamp_alloc);
798        assert_eq!(read_alloc.var_name, original_alloc.var_name);
799        assert_eq!(read_alloc.type_name, original_alloc.type_name);
800        assert_eq!(read_alloc.thread_id, original_alloc.thread_id);
801    }
802
803    #[test]
804    fn test_advanced_metrics_round_trip() {
805        use crate::export::binary::config::BinaryExportConfig;
806
807        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
808        let mut original_alloc = create_test_allocation();
809        original_alloc.lifetime_ms = Some(2500); // Add lifecycle data
810
811        // Write test data with advanced metrics
812        {
813            let config = BinaryExportConfig::debug_comprehensive();
814            let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
815                .expect("Failed to create temp file");
816            writer.write_header(1).expect("Failed to write header");
817            writer
818                .write_allocation(&original_alloc)
819                .expect("Failed to write allocation");
820            writer
821                .write_advanced_metrics_segment(&[original_alloc.clone()])
822                .expect("Test operation failed");
823            writer.finish().expect("Failed to finish writing");
824        }
825
826        // Read and verify
827        let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create temp file");
828        let allocations = reader.read_all().expect("Failed to read from binary file");
829
830        assert_eq!(allocations.len(), 1);
831        let read_alloc = &allocations[0];
832
833        // Verify basic data
834        assert_eq!(read_alloc.ptr, original_alloc.ptr);
835        assert_eq!(read_alloc.size, original_alloc.size);
836        assert_eq!(read_alloc.lifetime_ms, original_alloc.lifetime_ms);
837
838        // Verify advanced metrics were read
839        let advanced_metrics = reader.get_advanced_metrics();
840        assert!(advanced_metrics.is_some());
841
842        let metrics = advanced_metrics.expect("Failed to get test value");
843        assert!(metrics
844            .lifecycle_metrics
845            .contains_key(&(original_alloc.ptr as u64)));
846
847        let lifecycle_metric = &metrics.lifecycle_metrics[&(original_alloc.ptr as u64)];
848        assert_eq!(lifecycle_metric.lifetime_ms, 2500);
849    }
850
851    #[test]
852    fn test_backward_compatibility() {
853        use crate::export::binary::config::BinaryExportConfig;
854
855        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
856        let original_alloc = create_test_allocation();
857
858        // Write test data without advanced metrics (old format)
859        {
860            let config = BinaryExportConfig::minimal();
861            let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
862                .expect("Failed to create temp file");
863            writer.write_header(1).expect("Failed to write header");
864            writer
865                .write_allocation(&original_alloc)
866                .expect("Failed to write allocation");
867            writer.finish().expect("Failed to finish writing");
868        }
869
870        // Read with new reader (should handle missing advanced metrics gracefully)
871        let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create temp file");
872        let allocations = reader.read_all().expect("Failed to read from binary file");
873
874        assert_eq!(allocations.len(), 1);
875        let read_alloc = &allocations[0];
876
877        // Basic data should still be readable
878        assert_eq!(read_alloc.ptr, original_alloc.ptr);
879        assert_eq!(read_alloc.size, original_alloc.size);
880
881        // Advanced metrics should be None (backward compatibility)
882        let advanced_metrics = reader.get_advanced_metrics();
883        assert!(advanced_metrics.is_none());
884    }
885
886    #[test]
887    fn test_binary_reader_error_handling() {
888        // Test with non-existent file
889        let result = BinaryReader::new("non_existent_file.bin");
890        assert!(result.is_err());
891
892        // Test with empty file
893        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
894        let result = BinaryReader::new(temp_file.path());
895        // Empty file might not always fail immediately, so we check if it fails or succeeds
896        // If it succeeds, reading should fail
897        if let Ok(mut reader) = result {
898            let read_result = reader.read_all();
899            assert!(read_result.is_err()); // Reading from empty file should fail
900        } else {
901            assert!(result.is_err()); // Creation should fail due to missing header
902        }
903
904        // Test with invalid header
905        {
906            let mut file = File::create(temp_file.path()).expect("Failed to create file");
907            file.write_all(b"INVALID_HEADER")
908                .expect("Failed to write invalid header");
909        }
910        let result = BinaryReader::new(temp_file.path());
911        // Invalid header might not always fail immediately, so we check if reading fails
912        if let Ok(mut reader) = result {
913            let read_result = reader.read_all();
914            assert!(
915                read_result.is_err(),
916                "Reading with invalid header should fail"
917            );
918        } else {
919            assert!(result.is_err(), "Creation with invalid header should fail");
920        }
921    }
922
923    #[test]
924    fn test_binary_reader_large_dataset() {
925        use crate::export::binary::config::BinaryExportConfig;
926
927        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
928        let mut allocations = Vec::new();
929
930        // Create a large dataset
931        for i in 0..1000 {
932            let mut alloc = create_test_allocation();
933            alloc.ptr = 0x1000 + i * 0x100;
934            alloc.size = 64 + (i % 100);
935            alloc.timestamp_alloc = 1000000 + i as u64;
936            allocations.push(alloc);
937        }
938
939        // Write large dataset
940        {
941            let config = BinaryExportConfig::default();
942            let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
943                .expect("Failed to create writer");
944            writer
945                .write_header(allocations.len() as u32)
946                .expect("Failed to write header");
947
948            for alloc in &allocations {
949                writer
950                    .write_allocation(alloc)
951                    .expect("Failed to write allocation");
952            }
953            writer.finish().expect("Failed to finish writing");
954        }
955
956        // Read and verify large dataset
957        let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
958        let read_allocations = reader.read_all().expect("Failed to read allocations");
959
960        assert_eq!(read_allocations.len(), 1000);
961
962        // Verify first and last allocations
963        assert_eq!(read_allocations[0].ptr, 0x1000);
964        assert_eq!(read_allocations[999].ptr, 0x1000 + 999 * 0x100);
965        assert_eq!(read_allocations[0].timestamp_alloc, 1000000);
966        assert_eq!(read_allocations[999].timestamp_alloc, 1000000 + 999);
967    }
968
969    #[test]
970    fn test_binary_reader_chunked_reading() {
971        use crate::export::binary::config::BinaryExportConfig;
972
973        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
974        let mut allocations = Vec::new();
975
976        // Create test data
977        for i in 0..100 {
978            let mut alloc = create_test_allocation();
979            alloc.ptr = 0x2000 + i * 0x50;
980            alloc.size = 32 + i;
981            allocations.push(alloc);
982        }
983
984        // Write test data
985        {
986            let config = BinaryExportConfig::default();
987            let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
988                .expect("Failed to create writer");
989            writer
990                .write_header(allocations.len() as u32)
991                .expect("Failed to write header");
992
993            for alloc in &allocations {
994                writer
995                    .write_allocation(alloc)
996                    .expect("Failed to write allocation");
997            }
998            writer.finish().expect("Failed to finish writing");
999        }
1000
1001        // Test chunked reading
1002        let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
1003
1004        // Read all allocations at once (since read_chunk doesn't exist)
1005        let all_read = reader.read_all().expect("Failed to read allocations");
1006
1007        assert_eq!(all_read.len(), 100);
1008        assert_eq!(all_read[0].ptr, 0x2000);
1009        assert_eq!(all_read[99].ptr, 0x2000 + 99 * 0x50);
1010    }
1011
1012    #[test]
1013    fn test_binary_reader_metadata() {
1014        use crate::export::binary::config::BinaryExportConfig;
1015
1016        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
1017        let alloc = create_test_allocation();
1018
1019        // Write with metadata
1020        {
1021            let config = BinaryExportConfig::debug_comprehensive();
1022            let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
1023                .expect("Failed to create writer");
1024            writer.write_header(1).expect("Failed to write header");
1025            writer
1026                .write_allocation(&alloc)
1027                .expect("Failed to write allocation");
1028            writer.finish().expect("Failed to finish writing");
1029        }
1030
1031        // Read and check metadata
1032        let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
1033
1034        // Test that we can read the allocation
1035        let allocations = reader.read_all().expect("Failed to read allocations");
1036        assert_eq!(allocations.len(), 1);
1037        assert_eq!(allocations[0].ptr, alloc.ptr);
1038        assert_eq!(allocations[0].size, alloc.size);
1039    }
1040
1041    #[test]
1042    fn test_binary_reader_corrupted_data() {
1043        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
1044
1045        // Write valid header but corrupted data
1046        {
1047            let mut file = File::create(temp_file.path()).expect("Failed to create file");
1048
1049            // Write valid magic bytes and version
1050            file.write_all(b"MEMSCOPE").expect("Failed to write magic");
1051            file.write_all(&1u32.to_le_bytes())
1052                .expect("Failed to write version");
1053            file.write_all(&1u32.to_le_bytes())
1054                .expect("Failed to write count");
1055
1056            // Write corrupted allocation data
1057            file.write_all(&[0xFF; 100])
1058                .expect("Failed to write corrupted data");
1059        }
1060
1061        let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
1062        let result = reader.read_all();
1063
1064        // Should handle corrupted data gracefully
1065        assert!(result.is_err() || result.unwrap().is_empty());
1066    }
1067
1068    #[test]
1069    fn test_binary_reader_empty_dataset() {
1070        use crate::export::binary::config::BinaryExportConfig;
1071
1072        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
1073
1074        // Write empty dataset
1075        {
1076            let config = BinaryExportConfig::minimal();
1077            let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
1078                .expect("Failed to create writer");
1079            writer.write_header(0).expect("Failed to write header");
1080            writer.finish().expect("Failed to finish writing");
1081        }
1082
1083        // Read empty dataset
1084        let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
1085        let allocations = reader.read_all().expect("Failed to read allocations");
1086
1087        assert_eq!(allocations.len(), 0);
1088        // Verify empty dataset was read correctly
1089        assert_eq!(allocations.len(), 0);
1090    }
1091
1092    #[test]
1093    fn test_binary_reader_seek_operations() {
1094        use crate::export::binary::config::BinaryExportConfig;
1095
1096        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
1097        let mut allocations = Vec::new();
1098
1099        // Create test data
1100        for i in 0..50 {
1101            let mut alloc = create_test_allocation();
1102            alloc.ptr = 0x3000 + i * 0x100;
1103            alloc.timestamp_alloc = 2000000 + i as u64;
1104            allocations.push(alloc);
1105        }
1106
1107        // Write test data
1108        {
1109            let config = BinaryExportConfig::default();
1110            let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
1111                .expect("Failed to create writer");
1112            writer
1113                .write_header(allocations.len() as u32)
1114                .expect("Failed to write header");
1115
1116            for alloc in &allocations {
1117                writer
1118                    .write_allocation(alloc)
1119                    .expect("Failed to write allocation");
1120            }
1121            writer.finish().expect("Failed to finish writing");
1122        }
1123
1124        // Test reading all allocations
1125        let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
1126
1127        let all_allocations = reader.read_all().expect("Failed to read allocations");
1128        assert_eq!(all_allocations.len(), 50);
1129        assert_eq!(all_allocations[0].ptr, 0x3000);
1130        assert_eq!(all_allocations[49].ptr, 0x3000 + 49 * 0x100);
1131    }
1132
1133    #[test]
1134    fn test_binary_reader_statistics() {
1135        use crate::export::binary::config::BinaryExportConfig;
1136
1137        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
1138        let mut allocations = Vec::new();
1139
1140        // Create varied test data
1141        for i in 0..20 {
1142            let mut alloc = create_test_allocation();
1143            alloc.ptr = 0x4000 + i * 0x200;
1144            alloc.size = if i % 2 == 0 { 64 } else { 128 };
1145            alloc.timestamp_alloc = 3000000 + (i * 1000) as u64;
1146            alloc.borrow_count = i % 5;
1147            allocations.push(alloc);
1148        }
1149
1150        // Write test data
1151        {
1152            let config = BinaryExportConfig::debug_comprehensive();
1153            let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
1154                .expect("Failed to create writer");
1155            writer
1156                .write_header(allocations.len() as u32)
1157                .expect("Failed to write header");
1158
1159            for alloc in &allocations {
1160                writer
1161                    .write_allocation(alloc)
1162                    .expect("Failed to write allocation");
1163            }
1164            writer.finish().expect("Failed to finish writing");
1165        }
1166
1167        // Read and gather statistics
1168        let mut reader = BinaryReader::new(temp_file.path()).expect("Failed to create reader");
1169        let read_allocations = reader.read_all().expect("Failed to read allocations");
1170
1171        // Verify statistics
1172        assert_eq!(read_allocations.len(), 20);
1173
1174        let total_size: usize = read_allocations.iter().map(|a| a.size).sum();
1175        let expected_total: usize = (0..20).map(|i| if i % 2 == 0 { 64 } else { 128 }).sum();
1176        assert_eq!(total_size, expected_total);
1177
1178        let max_borrow_count = read_allocations
1179            .iter()
1180            .map(|a| a.borrow_count)
1181            .max()
1182            .unwrap();
1183        assert_eq!(max_borrow_count, 4); // (19 % 5) = 4
1184
1185        let min_timestamp = read_allocations
1186            .iter()
1187            .map(|a| a.timestamp_alloc)
1188            .min()
1189            .unwrap();
1190        assert_eq!(min_timestamp, 3000000);
1191    }
1192
1193    #[test]
1194    fn test_binary_reader_concurrent_access() {
1195        use crate::export::binary::config::BinaryExportConfig;
1196        use std::sync::Arc;
1197        use std::thread;
1198
1199        let temp_file = NamedTempFile::new().expect("Failed to create temp file");
1200        let mut allocations = Vec::new();
1201
1202        // Create test data
1203        for i in 0..100 {
1204            let mut alloc = create_test_allocation();
1205            alloc.ptr = 0x5000 + i * 0x50;
1206            alloc.size = 32 + (i % 50);
1207            allocations.push(alloc);
1208        }
1209
1210        // Write test data
1211        {
1212            let config = BinaryExportConfig::default();
1213            let mut writer = BinaryWriter::new_with_config(temp_file.path(), &config)
1214                .expect("Failed to create writer");
1215            writer
1216                .write_header(allocations.len() as u32)
1217                .expect("Failed to write header");
1218
1219            for alloc in &allocations {
1220                writer
1221                    .write_allocation(alloc)
1222                    .expect("Failed to write allocation");
1223            }
1224            writer.finish().expect("Failed to finish writing");
1225        }
1226
1227        // Test concurrent reading
1228        let file_path = Arc::new(temp_file.path().to_path_buf());
1229        let mut handles = vec![];
1230
1231        for thread_id in 0..5 {
1232            let path_clone = file_path.clone();
1233            let handle = thread::spawn(move || {
1234                let mut reader =
1235                    BinaryReader::new(path_clone.as_ref()).expect("Failed to create reader");
1236                let chunk_size = 20;
1237                let _start_index = thread_id * chunk_size;
1238
1239                // Read all allocations (concurrent reading test)
1240                let all_allocations = reader.read_all().expect("Failed to read allocations");
1241                (thread_id, all_allocations.len())
1242            });
1243            handles.push(handle);
1244        }
1245
1246        // Collect results
1247        for handle in handles {
1248            let (thread_id, allocation_count) = handle.join().expect("Thread should complete");
1249            assert_eq!(
1250                allocation_count, 100,
1251                "Thread {} should read all allocations",
1252                thread_id
1253            );
1254        }
1255    }
1256}