memscope_rs/export/binary/
format.rs

1//! Binary file format definitions using simple TLV (Type-Length-Value) structure
2
3/// File magic bytes for format identification
4pub const MAGIC_BYTES: &[u8; 8] = b"MEMSCOPE";
5
6pub const FORMAT_VERSION: u32 = 3; // Updated for Task 6.2: Enhanced safety analysis and passport tracking
7pub const HEADER_SIZE: usize = 32; // Enhanced header: 8+4+4+1+2+2+1+4+4+2 = 32 bytes
8pub const ALLOCATION_RECORD_TYPE: u8 = 1;
9
10// Task 6.2: New segment types for enhanced safety analysis
11pub const UNSAFE_REPORT_SEGMENT_TYPE: u8 = 2;
12pub const MEMORY_PASSPORT_SEGMENT_TYPE: u8 = 3;
13pub const CALL_STACK_SEGMENT_TYPE: u8 = 4;
14pub const FFI_FUNCTION_SEGMENT_TYPE: u8 = 5;
15pub const ADVANCED_METRICS_SEGMENT_TYPE: u8 = 6;
16
17// Segment magic identifiers
18pub const UNSAFE_REPORT_MAGIC: &[u8; 4] = b"USAF"; // Unsafe Analysis segment
19pub const MEMORY_PASSPORT_MAGIC: &[u8; 4] = b"MPPT"; // Memory Passport segment
20pub const CALL_STACK_MAGIC: &[u8; 4] = b"CSTK"; // Call Stack segment
21pub const FFI_FUNCTION_MAGIC: &[u8; 4] = b"FFIR"; // FFI Function Resolution segment
22pub const ADVANCED_METRICS_MAGIC: &[u8; 4] = b"ADVD"; // Advanced Data segment identifier
23
24/// Binary export mode for header identification
25#[repr(u8)]
26#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
27pub enum BinaryExportMode {
28    /// User-only export mode (strict filtering)
29    UserOnly = 0,
30    /// Full export mode (loose filtering, all data)
31    Full = 1,
32}
33
34impl From<u8> for BinaryExportMode {
35    fn from(value: u8) -> Self {
36        match value {
37            0 => BinaryExportMode::UserOnly,
38            1 => BinaryExportMode::Full,
39            _ => BinaryExportMode::UserOnly, // Default fallback
40        }
41    }
42}
43
44/// Feature flags for enhanced binary format (bit field)
45pub mod feature_flags {
46    /// Call stack normalization enabled
47    pub const CALL_STACK_NORMALIZATION: u8 = 0b00000001;
48    /// FFI function resolution enabled
49    pub const FFI_FUNCTION_RESOLUTION: u8 = 0b00000010;
50    /// Safety analysis enabled
51    pub const SAFETY_ANALYSIS: u8 = 0b00000100;
52    /// Memory passport tracking enabled
53    pub const MEMORY_PASSPORT_TRACKING: u8 = 0b00001000;
54    /// Enhanced lifetime analysis enabled
55    pub const ENHANCED_LIFETIME_ANALYSIS: u8 = 0b00010000;
56    /// Reserved for future use
57    pub const RESERVED_1: u8 = 0b00100000;
58    pub const RESERVED_2: u8 = 0b01000000;
59    pub const RESERVED_3: u8 = 0b10000000;
60}
61
62/// Enhanced file header structure (32 bytes fixed size)
63/// Extended to include safety analysis and passport tracking information
64#[repr(C)]
65#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
66pub struct FileHeader {
67    pub magic: [u8; 8],           // 8 bytes: File magic identifier
68    pub version: u32,             // 4 bytes: Format version
69    pub total_count: u32,         // 4 bytes: Total allocation count (user + system)
70    pub export_mode: u8,          // 1 byte: Export mode (user_only vs full)
71    pub user_count: u16,          // 2 bytes: User allocation count (var_name.is_some())
72    pub system_count: u16,        // 2 bytes: System allocation count (var_name.is_none())
73    pub features_enabled: u8,     // 1 byte: Feature flags (bit field)
74    pub unsafe_report_count: u32, // 4 bytes: Number of unsafe reports
75    pub passport_count: u32,      // 4 bytes: Number of memory passports
76    pub reserved: u16,            // 2 bytes: Reserved for future use
77}
78
79impl FileHeader {
80    /// Create a new file header with enhanced information
81    pub fn new(
82        total_count: u32,
83        export_mode: BinaryExportMode,
84        user_count: u16,
85        system_count: u16,
86    ) -> Self {
87        Self {
88            magic: *MAGIC_BYTES,
89            version: FORMAT_VERSION,
90            total_count,
91            export_mode: export_mode as u8,
92            user_count,
93            system_count,
94            features_enabled: feature_flags::CALL_STACK_NORMALIZATION
95                | feature_flags::FFI_FUNCTION_RESOLUTION,
96            unsafe_report_count: 0,
97            passport_count: 0,
98            reserved: 0,
99        }
100    }
101
102    /// Create a legacy header for backward compatibility
103    pub fn new_legacy(count: u32) -> Self {
104        Self {
105            magic: *MAGIC_BYTES,
106            version: FORMAT_VERSION,
107            total_count: count,
108            export_mode: BinaryExportMode::UserOnly as u8,
109            user_count: count as u16,
110            system_count: 0,
111            features_enabled: 0,
112            unsafe_report_count: 0,
113            passport_count: 0,
114            reserved: 0,
115        }
116    }
117
118    pub fn is_valid_magic(&self) -> bool {
119        self.magic == *MAGIC_BYTES
120    }
121
122    pub fn is_compatible_version(&self) -> bool {
123        // Support backward compatibility: can read older versions
124        self.version <= FORMAT_VERSION && self.version >= 1
125    }
126
127    pub fn get_version(&self) -> u32 {
128        self.version
129    }
130
131    pub fn is_legacy_version(&self) -> bool {
132        self.version < FORMAT_VERSION
133    }
134
135    /// Get the export mode from the header
136    pub fn get_export_mode(&self) -> BinaryExportMode {
137        BinaryExportMode::from(self.export_mode)
138    }
139
140    /// Check if this is a user-only binary
141    pub fn is_user_only(&self) -> bool {
142        self.get_export_mode() == BinaryExportMode::UserOnly
143    }
144
145    /// Check if this is a full binary
146    pub fn is_full_binary(&self) -> bool {
147        self.get_export_mode() == BinaryExportMode::Full
148    }
149
150    /// Get allocation count information
151    pub fn get_allocation_counts(&self) -> (u32, u16, u16) {
152        (self.total_count, self.user_count, self.system_count)
153    }
154
155    /// Validate allocation count consistency
156    pub fn is_count_consistent(&self) -> bool {
157        self.total_count == (self.user_count as u32 + self.system_count as u32)
158    }
159
160    /// Convert header to bytes using Little Endian format
161    pub fn to_bytes(&self) -> [u8; HEADER_SIZE] {
162        let mut bytes = [0u8; HEADER_SIZE];
163
164        bytes[0..8].copy_from_slice(&self.magic); // 8 bytes: magic
165        bytes[8..12].copy_from_slice(&self.version.to_le_bytes()); // 4 bytes: version
166        bytes[12..16].copy_from_slice(&self.total_count.to_le_bytes()); // 4 bytes: total_count
167        bytes[16] = self.export_mode; // 1 byte: export_mode
168        bytes[17..19].copy_from_slice(&self.user_count.to_le_bytes()); // 2 bytes: user_count
169        bytes[19..21].copy_from_slice(&self.system_count.to_le_bytes()); // 2 bytes: system_count
170        bytes[21] = self.features_enabled; // 1 byte: features_enabled
171        bytes[22..24].copy_from_slice(&self.unsafe_report_count.to_le_bytes()[0..2]); // 2 bytes: unsafe_report_count (lower 16 bits)
172        bytes[24..28].copy_from_slice(&self.passport_count.to_le_bytes()); // 4 bytes: passport_count
173        bytes[28..30].copy_from_slice(&self.reserved.to_le_bytes()); // 2 bytes: reserved
174
175        bytes
176    }
177
178    /// Create header from bytes using Little Endian format
179    pub fn from_bytes(bytes: &[u8; HEADER_SIZE]) -> Self {
180        let mut magic = [0u8; 8];
181        magic.copy_from_slice(&bytes[0..8]);
182
183        let version = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
184        let total_count = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
185        let export_mode = bytes[16];
186        let user_count = u16::from_le_bytes([bytes[17], bytes[18]]);
187        let system_count = u16::from_le_bytes([bytes[19], bytes[20]]);
188        let features_enabled = bytes[21];
189        let unsafe_report_count = u16::from_le_bytes([bytes[22], bytes[23]]) as u32;
190        let passport_count = u32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]);
191        let reserved = u16::from_le_bytes([bytes[28], bytes[29]]);
192
193        Self {
194            magic,
195            version,
196            total_count,
197            export_mode,
198            user_count,
199            system_count,
200            features_enabled,
201            unsafe_report_count,
202            passport_count,
203            reserved,
204        }
205    }
206}
207
208/// Allocation record structure for binary serialization with improve.md extensions
209#[derive(Debug, Clone, PartialEq)]
210pub struct AllocationRecord {
211    pub ptr: u64,
212    pub size: u64,
213    pub timestamp: u64,
214    pub var_name: Option<String>,
215    pub type_name: Option<String>,
216    pub thread_id: String,
217    // improve.md extensions
218    pub lifetime_ms: Option<u64>,
219    pub borrow_info: Option<crate::core::types::BorrowInfo>,
220    pub clone_info: Option<crate::core::types::CloneInfo>,
221    pub ownership_history_available: bool,
222}
223
224impl AllocationRecord {
225    /// Calculate serialized size in bytes with improve.md extensions
226    #[allow(dead_code)]
227    pub fn serialized_size(&self) -> usize {
228        let mut size = 1 + 4; // Type + Length
229        size += 8 + 8 + 8; // ptr + size + timestamp
230
231        size += 4; // var_name_len
232        if let Some(ref name) = self.var_name {
233            size += name.len();
234        }
235
236        size += 4; // type_name_len
237        if let Some(ref name) = self.type_name {
238            size += name.len();
239        }
240
241        size += 4; // thread_id_len
242        size += self.thread_id.len();
243
244        // improve.md extensions
245        size += 8; // lifetime_ms (Option<u64> as u64, 0 for None)
246        size += 1; // borrow_info presence flag
247        if self.borrow_info.is_some() {
248            size += 4 + 4 + 4 + 8; // immutable_borrows + mutable_borrows + max_concurrent + timestamp
249        }
250        size += 1; // clone_info presence flag
251        if self.clone_info.is_some() {
252            size += 4 + 1 + 8; // clone_count + is_clone + original_ptr (Option<usize> as u64)
253        }
254        size += 1; // ownership_history_available
255
256        size
257    }
258}
259
260/// Task 6: Advanced metrics segment header
261#[repr(C)]
262#[derive(Debug, Clone, PartialEq)]
263pub struct AdvancedMetricsHeader {
264    pub magic: [u8; 4],      // "ADVD"
265    pub segment_size: u32,   // Size of the entire segment including header
266    pub metrics_bitmap: u32, // Bitmap indicating which metrics are present
267    pub reserved: u32,       // Reserved for future use
268}
269
270impl AdvancedMetricsHeader {
271    pub fn new(segment_size: u32, metrics_bitmap: u32) -> Self {
272        Self {
273            magic: *ADVANCED_METRICS_MAGIC,
274            segment_size,
275            metrics_bitmap,
276            reserved: 0,
277        }
278    }
279
280    pub fn is_valid_magic(&self) -> bool {
281        self.magic == *ADVANCED_METRICS_MAGIC
282    }
283
284    /// Convert header to bytes using Little Endian format
285    pub fn to_bytes(&self) -> [u8; 16] {
286        let mut bytes = [0u8; 16];
287
288        bytes[0..4].copy_from_slice(&self.magic);
289        bytes[4..8].copy_from_slice(&self.segment_size.to_le_bytes());
290        bytes[8..12].copy_from_slice(&self.metrics_bitmap.to_le_bytes());
291        bytes[12..16].copy_from_slice(&self.reserved.to_le_bytes());
292
293        bytes
294    }
295
296    /// Create header from bytes using Little Endian format
297    pub fn from_bytes(bytes: &[u8; 16]) -> Self {
298        let mut magic = [0u8; 4];
299        magic.copy_from_slice(&bytes[0..4]);
300
301        let segment_size = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
302        let metrics_bitmap = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
303        let reserved = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
304
305        Self {
306            magic,
307            segment_size,
308            metrics_bitmap,
309            reserved,
310        }
311    }
312}
313
314/// Task 6: Metrics bitmap flags for identifying which advanced metrics are present
315#[repr(u32)]
316#[derive(Debug, Clone, Copy, PartialEq)]
317pub enum MetricsBitmapFlags {
318    LifecycleAnalysis = 1 << 0,     // Task 4 lifecycle metrics
319    ContainerAnalysis = 1 << 1,     // Task 3 container analysis
320    TypeUsageStats = 1 << 2,        // Task 2 type usage statistics
321    SourceAnalysis = 1 << 3,        // Source code analysis
322    FragmentationAnalysis = 1 << 4, // Memory fragmentation analysis
323    ThreadContext = 1 << 5,         // Thread context information
324    DropChainAnalysis = 1 << 6,     // Drop chain analysis
325    ZstAnalysis = 1 << 7,           // Zero-sized type analysis
326    HealthScoring = 1 << 8,         // Memory health scoring
327    PerformanceBenchmarks = 1 << 9, // Performance benchmark data
328                                    // Bits 10-31 reserved for future metrics
329}
330
331impl MetricsBitmapFlags {
332    /// Check if a specific metric is enabled in the bitmap
333    pub fn is_enabled(bitmap: u32, flag: MetricsBitmapFlags) -> bool {
334        (bitmap & flag as u32) != 0
335    }
336
337    /// Enable a specific metric in the bitmap
338    pub fn enable(bitmap: u32, flag: MetricsBitmapFlags) -> u32 {
339        bitmap | (flag as u32)
340    }
341
342    /// Disable a specific metric in the bitmap
343    #[allow(dead_code)]
344    pub fn disable(bitmap: u32, flag: MetricsBitmapFlags) -> u32 {
345        bitmap & !(flag as u32)
346    }
347}
348
349/// Endian conversion utilities
350pub mod endian {
351    #[allow(dead_code)]
352    pub fn u32_to_le_bytes(value: u32) -> [u8; 4] {
353        value.to_le_bytes()
354    }
355
356    #[allow(dead_code)]
357    pub fn u32_from_le_bytes(bytes: [u8; 4]) -> u32 {
358        u32::from_le_bytes(bytes)
359    }
360
361    #[allow(dead_code)]
362    pub fn u64_to_le_bytes(value: u64) -> [u8; 8] {
363        value.to_le_bytes()
364    }
365
366    #[allow(dead_code)]
367    pub fn u64_from_le_bytes(bytes: [u8; 8]) -> u64 {
368        u64::from_le_bytes(bytes)
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use super::*;
375
376    #[test]
377    fn test_file_header_creation() {
378        let header = FileHeader::new(100, BinaryExportMode::Full, 60, 40);
379        assert_eq!(header.magic, *MAGIC_BYTES);
380        assert_eq!(header.version, FORMAT_VERSION);
381        assert_eq!(header.total_count, 100);
382        assert_eq!(header.user_count, 60);
383        assert_eq!(header.system_count, 40);
384        assert_eq!(header.get_export_mode(), BinaryExportMode::Full);
385        assert!(header.is_valid_magic());
386        assert!(header.is_compatible_version());
387        assert!(header.is_count_consistent());
388        assert!(header.is_full_binary());
389        assert!(!header.is_user_only());
390    }
391
392    #[test]
393    fn test_file_header_serialization() {
394        let header = FileHeader::new(42, BinaryExportMode::UserOnly, 42, 0);
395        let bytes = header.to_bytes();
396        let deserialized = FileHeader::from_bytes(&bytes);
397
398        assert_eq!(header, deserialized);
399    }
400
401    #[test]
402    fn test_legacy_header_creation() {
403        let header = FileHeader::new_legacy(50);
404        assert_eq!(header.total_count, 50);
405        assert_eq!(header.user_count, 50);
406        assert_eq!(header.system_count, 0);
407        assert_eq!(header.get_export_mode(), BinaryExportMode::UserOnly);
408        assert!(header.is_user_only());
409        assert!(!header.is_full_binary());
410    }
411
412    #[test]
413    fn test_binary_export_mode_conversion() {
414        assert_eq!(BinaryExportMode::from(0), BinaryExportMode::UserOnly);
415        assert_eq!(BinaryExportMode::from(1), BinaryExportMode::Full);
416        assert_eq!(BinaryExportMode::from(255), BinaryExportMode::UserOnly); // Default fallback
417    }
418
419    #[test]
420    fn test_allocation_record_size_calculation() {
421        let record = AllocationRecord {
422            ptr: 0x1000,
423            size: 1024,
424            timestamp: 1234567890,
425            var_name: Some("test_var".to_string()),
426            type_name: Some("i32".to_string()),
427            thread_id: "main".to_string(),
428            // improve.md extensions
429            lifetime_ms: None,
430            borrow_info: None,
431            clone_info: None,
432            ownership_history_available: false,
433        };
434
435        let expected_size = 1 + 4 + // Type + Length
436                           8 + 8 + 8 + // ptr + size + timestamp
437                           4 + 8 + // var_name_len + var_name ("test_var" = 8 chars)
438                           4 + 3 + // type_name_len + type_name ("i32" = 3 chars)
439                           4 + 4 + // thread_id_len + thread_id ("main" = 4 chars)
440                           11; // Additional fields for improve.md extensions (8 + 1 + 1 + 1)
441
442        assert_eq!(record.serialized_size(), expected_size);
443    }
444
445    #[test]
446    fn test_endian_conversion() {
447        let value = 0x12345678u32;
448        let bytes = endian::u32_to_le_bytes(value);
449        let converted = endian::u32_from_le_bytes(bytes);
450        assert_eq!(value, converted);
451
452        let value64 = 0x123456789ABCDEFu64;
453        let bytes64 = endian::u64_to_le_bytes(value64);
454        let converted64 = endian::u64_from_le_bytes(bytes64);
455        assert_eq!(value64, converted64);
456    }
457
458    #[test]
459    fn test_advanced_metrics_header_creation() {
460        let header = AdvancedMetricsHeader::new(1024, 0x12345678);
461        assert_eq!(header.magic, *ADVANCED_METRICS_MAGIC);
462        assert_eq!(header.segment_size, 1024);
463        assert_eq!(header.metrics_bitmap, 0x12345678);
464        assert_eq!(header.reserved, 0);
465        assert!(header.is_valid_magic());
466    }
467
468    #[test]
469    fn test_advanced_metrics_header_serialization() {
470        let header = AdvancedMetricsHeader::new(2048, 0xABCDEF00);
471        let bytes = header.to_bytes();
472        let deserialized = AdvancedMetricsHeader::from_bytes(&bytes);
473
474        assert_eq!(header, deserialized);
475    }
476
477    #[test]
478    fn test_metrics_bitmap_flags() {
479        let mut bitmap = 0u32;
480
481        // Test enabling flags
482        bitmap = MetricsBitmapFlags::enable(bitmap, MetricsBitmapFlags::LifecycleAnalysis);
483        assert!(MetricsBitmapFlags::is_enabled(
484            bitmap,
485            MetricsBitmapFlags::LifecycleAnalysis
486        ));
487        assert!(!MetricsBitmapFlags::is_enabled(
488            bitmap,
489            MetricsBitmapFlags::ContainerAnalysis
490        ));
491
492        bitmap = MetricsBitmapFlags::enable(bitmap, MetricsBitmapFlags::ContainerAnalysis);
493        assert!(MetricsBitmapFlags::is_enabled(
494            bitmap,
495            MetricsBitmapFlags::LifecycleAnalysis
496        ));
497        assert!(MetricsBitmapFlags::is_enabled(
498            bitmap,
499            MetricsBitmapFlags::ContainerAnalysis
500        ));
501
502        // Test disabling flags
503        bitmap = MetricsBitmapFlags::disable(bitmap, MetricsBitmapFlags::LifecycleAnalysis);
504        assert!(!MetricsBitmapFlags::is_enabled(
505            bitmap,
506            MetricsBitmapFlags::LifecycleAnalysis
507        ));
508        assert!(MetricsBitmapFlags::is_enabled(
509            bitmap,
510            MetricsBitmapFlags::ContainerAnalysis
511        ));
512    }
513}