1use bincode::{Decode, Encode};
4
5pub const MAGIC_BYTES: &[u8; 8] = b"MEMSCOPE";
7
8pub const FORMAT_VERSION: u32 = 3; pub const HEADER_SIZE: usize = 32; pub const ALLOCATION_RECORD_TYPE: u8 = 1;
11
12pub const UNSAFE_REPORT_SEGMENT_TYPE: u8 = 2;
14pub const MEMORY_PASSPORT_SEGMENT_TYPE: u8 = 3;
15pub const CALL_STACK_SEGMENT_TYPE: u8 = 4;
16pub const FFI_FUNCTION_SEGMENT_TYPE: u8 = 5;
17pub const ADVANCED_METRICS_SEGMENT_TYPE: u8 = 6;
18
19pub 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)]
28#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize, Encode, Decode)]
29pub enum BinaryExportMode {
30 UserOnly = 0,
32 Full = 1,
34}
35
36impl From<u8> for BinaryExportMode {
37 fn from(value: u8) -> Self {
38 match value {
39 0 => BinaryExportMode::UserOnly,
40 1 => BinaryExportMode::Full,
41 _ => BinaryExportMode::UserOnly, }
43 }
44}
45
46pub mod feature_flags {
48 pub const CALL_STACK_NORMALIZATION: u8 = 0b00000001;
50 pub const FFI_FUNCTION_RESOLUTION: u8 = 0b00000010;
52 pub const SAFETY_ANALYSIS: u8 = 0b00000100;
54 pub const MEMORY_PASSPORT_TRACKING: u8 = 0b00001000;
56 pub const ENHANCED_LIFETIME_ANALYSIS: u8 = 0b00010000;
58 pub const RESERVED_1: u8 = 0b00100000;
60 pub const RESERVED_2: u8 = 0b01000000;
61 pub const RESERVED_3: u8 = 0b10000000;
62}
63
64#[repr(C)]
67#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Encode, Decode)]
68pub struct FileHeader {
69 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, }
80
81impl FileHeader {
82 pub fn new(
84 total_count: u32,
85 export_mode: BinaryExportMode,
86 user_count: u16,
87 system_count: u16,
88 ) -> Self {
89 Self {
90 magic: *MAGIC_BYTES,
91 version: FORMAT_VERSION,
92 total_count,
93 export_mode: export_mode as u8,
94 user_count,
95 system_count,
96 features_enabled: feature_flags::CALL_STACK_NORMALIZATION
97 | feature_flags::FFI_FUNCTION_RESOLUTION,
98 unsafe_report_count: 0,
99 passport_count: 0,
100 reserved: 0,
101 }
102 }
103
104 pub fn new_legacy(count: u32) -> Self {
106 Self {
107 magic: *MAGIC_BYTES,
108 version: FORMAT_VERSION,
109 total_count: count,
110 export_mode: BinaryExportMode::UserOnly as u8,
111 user_count: count as u16,
112 system_count: 0,
113 features_enabled: 0,
114 unsafe_report_count: 0,
115 passport_count: 0,
116 reserved: 0,
117 }
118 }
119
120 pub fn is_valid_magic(&self) -> bool {
121 self.magic == *MAGIC_BYTES
122 }
123
124 pub fn is_compatible_version(&self) -> bool {
125 self.version <= FORMAT_VERSION && self.version >= 1
127 }
128
129 pub fn get_version(&self) -> u32 {
130 self.version
131 }
132
133 pub fn is_legacy_version(&self) -> bool {
134 self.version < FORMAT_VERSION
135 }
136
137 pub fn get_export_mode(&self) -> BinaryExportMode {
139 BinaryExportMode::from(self.export_mode)
140 }
141
142 pub fn is_user_only(&self) -> bool {
144 self.get_export_mode() == BinaryExportMode::UserOnly
145 }
146
147 pub fn is_full_binary(&self) -> bool {
149 self.get_export_mode() == BinaryExportMode::Full
150 }
151
152 pub fn get_allocation_counts(&self) -> (u32, u16, u16) {
154 (self.total_count, self.user_count, self.system_count)
155 }
156
157 pub fn is_count_consistent(&self) -> bool {
159 self.total_count == (self.user_count as u32 + self.system_count as u32)
160 }
161
162 pub fn to_bytes(&self) -> [u8; HEADER_SIZE] {
164 let mut bytes = [0u8; HEADER_SIZE];
165
166 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
178 }
179
180 pub fn from_bytes(bytes: &[u8; HEADER_SIZE]) -> Self {
182 let mut magic = [0u8; 8];
183 magic.copy_from_slice(&bytes[0..8]);
184
185 let version = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
186 let total_count = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
187 let export_mode = bytes[16];
188 let user_count = u16::from_le_bytes([bytes[17], bytes[18]]);
189 let system_count = u16::from_le_bytes([bytes[19], bytes[20]]);
190 let features_enabled = bytes[21];
191 let unsafe_report_count = u16::from_le_bytes([bytes[22], bytes[23]]) as u32;
192 let passport_count = u32::from_le_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]);
193 let reserved = u16::from_le_bytes([bytes[28], bytes[29]]);
194
195 Self {
196 magic,
197 version,
198 total_count,
199 export_mode,
200 user_count,
201 system_count,
202 features_enabled,
203 unsafe_report_count,
204 passport_count,
205 reserved,
206 }
207 }
208}
209
210#[derive(Debug, Clone, PartialEq)]
212pub struct AllocationRecord {
213 pub ptr: u64,
214 pub size: u64,
215 pub timestamp: u64,
216 pub var_name: Option<String>,
217 pub type_name: Option<String>,
218 pub thread_id: String,
219 pub lifetime_ms: Option<u64>,
221 pub borrow_info: Option<crate::core::types::BorrowInfo>,
222 pub clone_info: Option<crate::core::types::CloneInfo>,
223 pub ownership_history_available: bool,
224}
225
226impl AllocationRecord {
227 #[allow(dead_code)]
229 pub fn serialized_size(&self) -> usize {
230 let mut size = 1 + 4; size += 8 + 8 + 8; size += 4; if let Some(ref name) = self.var_name {
235 size += name.len();
236 }
237
238 size += 4; if let Some(ref name) = self.type_name {
240 size += name.len();
241 }
242
243 size += 4; size += self.thread_id.len();
245
246 size += 8; size += 1; if self.borrow_info.is_some() {
250 size += 4 + 4 + 4 + 8; }
252 size += 1; if self.clone_info.is_some() {
254 size += 4 + 1 + 8; }
256 size += 1; size
259 }
260}
261
262#[repr(C)]
264#[derive(Debug, Clone, PartialEq)]
265pub struct AdvancedMetricsHeader {
266 pub magic: [u8; 4], pub segment_size: u32, pub metrics_bitmap: u32, pub reserved: u32, }
271
272impl AdvancedMetricsHeader {
273 pub fn new(segment_size: u32, metrics_bitmap: u32) -> Self {
274 Self {
275 magic: *ADVANCED_METRICS_MAGIC,
276 segment_size,
277 metrics_bitmap,
278 reserved: 0,
279 }
280 }
281
282 pub fn is_valid_magic(&self) -> bool {
283 self.magic == *ADVANCED_METRICS_MAGIC
284 }
285
286 pub fn to_bytes(&self) -> [u8; 16] {
288 let mut bytes = [0u8; 16];
289
290 bytes[0..4].copy_from_slice(&self.magic);
291 bytes[4..8].copy_from_slice(&self.segment_size.to_le_bytes());
292 bytes[8..12].copy_from_slice(&self.metrics_bitmap.to_le_bytes());
293 bytes[12..16].copy_from_slice(&self.reserved.to_le_bytes());
294
295 bytes
296 }
297
298 pub fn from_bytes(bytes: &[u8; 16]) -> Self {
300 let mut magic = [0u8; 4];
301 magic.copy_from_slice(&bytes[0..4]);
302
303 let segment_size = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]);
304 let metrics_bitmap = u32::from_le_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
305 let reserved = u32::from_le_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
306
307 Self {
308 magic,
309 segment_size,
310 metrics_bitmap,
311 reserved,
312 }
313 }
314}
315
316#[repr(u32)]
318#[derive(Debug, Clone, Copy, PartialEq)]
319pub enum MetricsBitmapFlags {
320 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, }
332
333impl MetricsBitmapFlags {
334 pub fn is_enabled(bitmap: u32, flag: MetricsBitmapFlags) -> bool {
336 (bitmap & flag as u32) != 0
337 }
338
339 pub fn enable(bitmap: u32, flag: MetricsBitmapFlags) -> u32 {
341 bitmap | (flag as u32)
342 }
343
344 #[allow(dead_code)]
346 pub fn disable(bitmap: u32, flag: MetricsBitmapFlags) -> u32 {
347 bitmap & !(flag as u32)
348 }
349}
350
351pub mod endian {
353 #[allow(dead_code)]
354 pub fn u32_to_le_bytes(value: u32) -> [u8; 4] {
355 value.to_le_bytes()
356 }
357
358 #[allow(dead_code)]
359 pub fn u32_from_le_bytes(bytes: [u8; 4]) -> u32 {
360 u32::from_le_bytes(bytes)
361 }
362
363 #[allow(dead_code)]
364 pub fn u64_to_le_bytes(value: u64) -> [u8; 8] {
365 value.to_le_bytes()
366 }
367
368 #[allow(dead_code)]
369 pub fn u64_from_le_bytes(bytes: [u8; 8]) -> u64 {
370 u64::from_le_bytes(bytes)
371 }
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377
378 #[test]
379 fn test_file_header_creation() {
380 let header = FileHeader::new(100, BinaryExportMode::Full, 60, 40);
381 assert_eq!(header.magic, *MAGIC_BYTES);
382 assert_eq!(header.version, FORMAT_VERSION);
383 assert_eq!(header.total_count, 100);
384 assert_eq!(header.user_count, 60);
385 assert_eq!(header.system_count, 40);
386 assert_eq!(header.get_export_mode(), BinaryExportMode::Full);
387 assert!(header.is_valid_magic());
388 assert!(header.is_compatible_version());
389 assert!(header.is_count_consistent());
390 assert!(header.is_full_binary());
391 assert!(!header.is_user_only());
392 }
393
394 #[test]
395 fn test_file_header_serialization() {
396 let header = FileHeader::new(42, BinaryExportMode::UserOnly, 42, 0);
397 let bytes = header.to_bytes();
398 let deserialized = FileHeader::from_bytes(&bytes);
399
400 assert_eq!(header, deserialized);
401 }
402
403 #[test]
404 fn test_legacy_header_creation() {
405 let header = FileHeader::new_legacy(50);
406 assert_eq!(header.total_count, 50);
407 assert_eq!(header.user_count, 50);
408 assert_eq!(header.system_count, 0);
409 assert_eq!(header.get_export_mode(), BinaryExportMode::UserOnly);
410 assert!(header.is_user_only());
411 assert!(!header.is_full_binary());
412 }
413
414 #[test]
415 fn test_binary_export_mode_conversion() {
416 assert_eq!(BinaryExportMode::from(0), BinaryExportMode::UserOnly);
417 assert_eq!(BinaryExportMode::from(1), BinaryExportMode::Full);
418 assert_eq!(BinaryExportMode::from(255), BinaryExportMode::UserOnly); }
420
421 #[test]
422 fn test_allocation_record_size_calculation() {
423 let record = AllocationRecord {
424 ptr: 0x1000,
425 size: 1024,
426 timestamp: 1234567890,
427 var_name: Some("test_var".to_string()),
428 type_name: Some("i32".to_string()),
429 thread_id: "main".to_string(),
430 lifetime_ms: None,
432 borrow_info: None,
433 clone_info: None,
434 ownership_history_available: false,
435 };
436
437 let expected_size = 1 + 4 + 8 + 8 + 8 + 4 + 8 + 4 + 3 + 4 + 4 + 11; assert_eq!(record.serialized_size(), expected_size);
445 }
446
447 #[test]
448 fn test_endian_conversion() {
449 let value = 0x12345678u32;
450 let bytes = endian::u32_to_le_bytes(value);
451 let converted = endian::u32_from_le_bytes(bytes);
452 assert_eq!(value, converted);
453
454 let value64 = 0x123456789ABCDEFu64;
455 let bytes64 = endian::u64_to_le_bytes(value64);
456 let converted64 = endian::u64_from_le_bytes(bytes64);
457 assert_eq!(value64, converted64);
458 }
459
460 #[test]
461 fn test_advanced_metrics_header_creation() {
462 let header = AdvancedMetricsHeader::new(1024, 0x12345678);
463 assert_eq!(header.magic, *ADVANCED_METRICS_MAGIC);
464 assert_eq!(header.segment_size, 1024);
465 assert_eq!(header.metrics_bitmap, 0x12345678);
466 assert_eq!(header.reserved, 0);
467 assert!(header.is_valid_magic());
468 }
469
470 #[test]
471 fn test_advanced_metrics_header_serialization() {
472 let header = AdvancedMetricsHeader::new(2048, 0xABCDEF00);
473 let bytes = header.to_bytes();
474 let deserialized = AdvancedMetricsHeader::from_bytes(&bytes);
475
476 assert_eq!(header, deserialized);
477 }
478
479 #[test]
480 fn test_metrics_bitmap_flags() {
481 let mut bitmap = 0u32;
482
483 bitmap = MetricsBitmapFlags::enable(bitmap, MetricsBitmapFlags::LifecycleAnalysis);
485 assert!(MetricsBitmapFlags::is_enabled(
486 bitmap,
487 MetricsBitmapFlags::LifecycleAnalysis
488 ));
489 assert!(!MetricsBitmapFlags::is_enabled(
490 bitmap,
491 MetricsBitmapFlags::ContainerAnalysis
492 ));
493
494 bitmap = MetricsBitmapFlags::enable(bitmap, MetricsBitmapFlags::ContainerAnalysis);
495 assert!(MetricsBitmapFlags::is_enabled(
496 bitmap,
497 MetricsBitmapFlags::LifecycleAnalysis
498 ));
499 assert!(MetricsBitmapFlags::is_enabled(
500 bitmap,
501 MetricsBitmapFlags::ContainerAnalysis
502 ));
503
504 bitmap = MetricsBitmapFlags::disable(bitmap, MetricsBitmapFlags::LifecycleAnalysis);
506 assert!(!MetricsBitmapFlags::is_enabled(
507 bitmap,
508 MetricsBitmapFlags::LifecycleAnalysis
509 ));
510 assert!(MetricsBitmapFlags::is_enabled(
511 bitmap,
512 MetricsBitmapFlags::ContainerAnalysis
513 ));
514 }
515}