1pub const MAGIC_BYTES: &[u8; 8] = b"MEMSCOPE";
5
6pub const FORMAT_VERSION: u32 = 3; pub const HEADER_SIZE: usize = 32; pub const ALLOCATION_RECORD_TYPE: u8 = 1;
9
10pub 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
17pub const UNSAFE_REPORT_MAGIC: &[u8; 4] = b"USAF"; pub const MEMORY_PASSPORT_MAGIC: &[u8; 4] = b"MPPT"; pub const CALL_STACK_MAGIC: &[u8; 4] = b"CSTK"; pub const FFI_FUNCTION_MAGIC: &[u8; 4] = b"FFIR"; pub const ADVANCED_METRICS_MAGIC: &[u8; 4] = b"ADVD"; #[repr(u8)]
26#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
27pub enum BinaryExportMode {
28 UserOnly = 0,
30 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, }
41 }
42}
43
44pub mod feature_flags {
46 pub const CALL_STACK_NORMALIZATION: u8 = 0b00000001;
48 pub const FFI_FUNCTION_RESOLUTION: u8 = 0b00000010;
50 pub const SAFETY_ANALYSIS: u8 = 0b00000100;
52 pub const MEMORY_PASSPORT_TRACKING: u8 = 0b00001000;
54 pub const ENHANCED_LIFETIME_ANALYSIS: u8 = 0b00010000;
56 pub const RESERVED_1: u8 = 0b00100000;
58 pub const RESERVED_2: u8 = 0b01000000;
59 pub const RESERVED_3: u8 = 0b10000000;
60}
61
62#[repr(C)]
65#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
66pub struct FileHeader {
67 pub magic: [u8; 8], pub version: u32, pub total_count: u32, pub export_mode: u8, pub user_count: u16, pub system_count: u16, pub features_enabled: u8, pub unsafe_report_count: u32, pub passport_count: u32, pub reserved: u16, }
78
79impl FileHeader {
80 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 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 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 pub fn get_export_mode(&self) -> BinaryExportMode {
137 BinaryExportMode::from(self.export_mode)
138 }
139
140 pub fn is_user_only(&self) -> bool {
142 self.get_export_mode() == BinaryExportMode::UserOnly
143 }
144
145 pub fn is_full_binary(&self) -> bool {
147 self.get_export_mode() == BinaryExportMode::Full
148 }
149
150 pub fn get_allocation_counts(&self) -> (u32, u16, u16) {
152 (self.total_count, self.user_count, self.system_count)
153 }
154
155 pub fn is_count_consistent(&self) -> bool {
157 self.total_count == (self.user_count as u32 + self.system_count as u32)
158 }
159
160 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); bytes[8..12].copy_from_slice(&self.version.to_le_bytes()); bytes[12..16].copy_from_slice(&self.total_count.to_le_bytes()); bytes[16] = self.export_mode; bytes[17..19].copy_from_slice(&self.user_count.to_le_bytes()); bytes[19..21].copy_from_slice(&self.system_count.to_le_bytes()); bytes[21] = self.features_enabled; bytes[22..24].copy_from_slice(&self.unsafe_report_count.to_le_bytes()[0..2]); bytes[24..28].copy_from_slice(&self.passport_count.to_le_bytes()); bytes[28..30].copy_from_slice(&self.reserved.to_le_bytes()); bytes
176 }
177
178 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#[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 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 #[allow(dead_code)]
227 pub fn serialized_size(&self) -> usize {
228 let mut size = 1 + 4; size += 8 + 8 + 8; size += 4; if let Some(ref name) = self.var_name {
233 size += name.len();
234 }
235
236 size += 4; if let Some(ref name) = self.type_name {
238 size += name.len();
239 }
240
241 size += 4; size += self.thread_id.len();
243
244 size += 8; size += 1; if self.borrow_info.is_some() {
248 size += 4 + 4 + 4 + 8; }
250 size += 1; if self.clone_info.is_some() {
252 size += 4 + 1 + 8; }
254 size += 1; size
257 }
258}
259
260#[repr(C)]
262#[derive(Debug, Clone, PartialEq)]
263pub struct AdvancedMetricsHeader {
264 pub magic: [u8; 4], pub segment_size: u32, pub metrics_bitmap: u32, pub reserved: u32, }
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 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 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#[repr(u32)]
316#[derive(Debug, Clone, Copy, PartialEq)]
317pub enum MetricsBitmapFlags {
318 LifecycleAnalysis = 1 << 0, ContainerAnalysis = 1 << 1, TypeUsageStats = 1 << 2, SourceAnalysis = 1 << 3, FragmentationAnalysis = 1 << 4, ThreadContext = 1 << 5, DropChainAnalysis = 1 << 6, ZstAnalysis = 1 << 7, HealthScoring = 1 << 8, PerformanceBenchmarks = 1 << 9, }
330
331impl MetricsBitmapFlags {
332 pub fn is_enabled(bitmap: u32, flag: MetricsBitmapFlags) -> bool {
334 (bitmap & flag as u32) != 0
335 }
336
337 pub fn enable(bitmap: u32, flag: MetricsBitmapFlags) -> u32 {
339 bitmap | (flag as u32)
340 }
341
342 #[allow(dead_code)]
344 pub fn disable(bitmap: u32, flag: MetricsBitmapFlags) -> u32 {
345 bitmap & !(flag as u32)
346 }
347}
348
349pub 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); }
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 lifetime_ms: None,
430 borrow_info: None,
431 clone_info: None,
432 ownership_history_available: false,
433 };
434
435 let expected_size = 1 + 4 + 8 + 8 + 8 + 4 + 8 + 4 + 3 + 4 + 4 + 11; 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 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 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}