1use runmat_time::system_time_now;
7use std::time::{Duration, SystemTime};
8
9use serde::{Deserialize, Serialize};
10
11pub const SNAPSHOT_MAGIC: &[u8; 7] = b"RUNMAT\0";
13
14pub const SNAPSHOT_VERSION: u32 = 2;
16
17pub const MIN_SUPPORTED_SNAPSHOT_VERSION: u32 = 2;
19
20#[derive(Debug, Clone)]
22pub struct SnapshotFormat {
23 pub header: SnapshotHeader,
25
26 pub data: Vec<u8>,
28
29 pub checksum: Option<Vec<u8>>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct SnapshotHeader {
36 pub magic: [u8; 7],
38
39 pub version: u32,
41
42 pub metadata: SnapshotMetadata,
44
45 pub data_info: DataSectionInfo,
47
48 pub checksum_info: Option<ChecksumInfo>,
50
51 pub header_size: u32,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct SnapshotMetadata {
58 pub created_at: SystemTime,
60
61 pub runmat_version: String,
63
64 pub tool_version: String,
66
67 pub build_config: BuildConfig,
69
70 pub performance_metrics: PerformanceMetrics,
72
73 pub feature_flags: Vec<String>,
75
76 pub target_platform: PlatformInfo,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct BuildConfig {
83 pub optimization_level: String,
85
86 pub debug_info: bool,
88
89 pub compiler: String,
91
92 pub compile_flags: Vec<String>,
94
95 pub enabled_features: Vec<String>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct PerformanceMetrics {
102 pub creation_time: Duration,
104
105 pub builtin_count: u64,
107
108 pub hir_cache_entries: u64,
110
111 pub bytecode_cache_entries: u64,
113
114 pub uncompressed_size: u64,
116
117 pub compression_ratio: f64,
119
120 pub peak_memory_usage: u64,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct PlatformInfo {
127 pub os: String,
129
130 pub arch: String,
132
133 pub cpu_features: Vec<String>,
135
136 pub page_size: usize,
138
139 pub cache_line_size: usize,
141
142 pub endianness: Endianness,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
148pub enum Endianness {
149 Little,
150 Big,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct DataSectionInfo {
156 pub compression: CompressionInfo,
158
159 pub uncompressed_size: u64,
161
162 pub compressed_size: u64,
164
165 pub data_offset: u64,
167
168 pub alignment: usize,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct CompressionInfo {
175 pub algorithm: CompressionAlgorithm,
177
178 pub level: u32,
180
181 pub parameters: std::collections::HashMap<String, String>,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
187pub enum CompressionAlgorithm {
188 None,
189 Lz4 { fast: bool },
190 Zstd { dictionary: Option<Vec<u8>> },
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct ChecksumInfo {
196 pub algorithm: ChecksumAlgorithm,
198
199 pub size: usize,
201
202 pub offset: u64,
204}
205
206#[derive(Debug, Clone, Serialize, Deserialize)]
208pub enum ChecksumAlgorithm {
209 Sha256,
210 Blake3,
211 Crc32,
212}
213
214impl SnapshotHeader {
215 pub fn new(metadata: SnapshotMetadata) -> Self {
217 Self {
218 magic: *SNAPSHOT_MAGIC,
219 version: SNAPSHOT_VERSION,
220 metadata,
221 data_info: DataSectionInfo {
222 compression: CompressionInfo {
223 algorithm: CompressionAlgorithm::None,
224 level: 0,
225 parameters: std::collections::HashMap::new(),
226 },
227 uncompressed_size: 0,
228 compressed_size: 0,
229 data_offset: 0,
230 alignment: 8,
231 },
232 checksum_info: None,
233 header_size: 0, }
235 }
236
237 pub fn validate(&self) -> crate::SnapshotResult<()> {
239 if self.magic != *SNAPSHOT_MAGIC {
240 return Err(crate::SnapshotError::Corrupted {
241 reason: "Invalid magic number".to_string(),
242 });
243 }
244
245 if !(MIN_SUPPORTED_SNAPSHOT_VERSION..=SNAPSHOT_VERSION).contains(&self.version) {
246 return Err(crate::SnapshotError::VersionMismatch {
247 expected: if MIN_SUPPORTED_SNAPSHOT_VERSION == SNAPSHOT_VERSION {
248 SNAPSHOT_VERSION.to_string()
249 } else {
250 format!("{MIN_SUPPORTED_SNAPSHOT_VERSION}..={SNAPSHOT_VERSION}")
251 },
252 found: self.version.to_string(),
253 });
254 }
255
256 Ok(())
257 }
258
259 pub fn is_platform_compatible(&self) -> bool {
261 let current_os = std::env::consts::OS;
262 let current_arch = std::env::consts::ARCH;
263
264 self.metadata.target_platform.os == current_os
265 && self.metadata.target_platform.arch == current_arch
266 }
267
268 pub fn estimated_load_time(&self) -> Duration {
270 let base_time = Duration::from_millis(10); let data_time = Duration::from_nanos(
273 (self.data_info.compressed_size * 10) / 1024, );
275
276 match self.data_info.compression.algorithm {
277 CompressionAlgorithm::None => base_time + data_time,
278 CompressionAlgorithm::Lz4 { .. } => base_time + data_time * 2,
279 CompressionAlgorithm::Zstd { .. } => base_time + data_time * 4,
280 }
281 }
282}
283
284impl SnapshotMetadata {
285 pub fn current() -> Self {
287 Self {
288 created_at: system_time_now(),
289 runmat_version: env!("CARGO_PKG_VERSION").to_string(),
290 tool_version: env!("CARGO_PKG_VERSION").to_string(),
291 build_config: BuildConfig::current(),
292 performance_metrics: PerformanceMetrics::default(),
293 feature_flags: Self::detect_feature_flags(),
294 target_platform: PlatformInfo::current(),
295 }
296 }
297
298 #[allow(clippy::vec_init_then_push)] fn detect_feature_flags() -> Vec<String> {
301 let mut flags = Vec::new();
302
303 #[cfg(feature = "compression")]
304 flags.push("compression".to_string());
305
306 #[cfg(feature = "validation")]
307 flags.push("validation".to_string());
308
309 #[cfg(feature = "blas-lapack")]
310 flags.push("blas-lapack".to_string());
311
312 flags
313 }
314
315 pub fn is_compatible(&self) -> bool {
317 let current_version = env!("CARGO_PKG_VERSION");
319 let current_major = current_version.split('.').next().unwrap_or("0");
320 let snapshot_major = self.runmat_version.split('.').next().unwrap_or("0");
321
322 current_major == snapshot_major
323 }
324
325 pub fn age(&self) -> Duration {
327 system_time_now()
328 .duration_since(self.created_at)
329 .unwrap_or(Duration::ZERO)
330 }
331}
332
333impl BuildConfig {
334 pub fn current() -> Self {
336 Self {
337 optimization_level: if cfg!(debug_assertions) {
338 "debug".to_string()
339 } else {
340 "release".to_string()
341 },
342 debug_info: cfg!(debug_assertions),
343 compiler: format!(
344 "rustc {}",
345 option_env!("RUSTC_VERSION").unwrap_or("unknown")
346 ),
347 compile_flags: Vec::new(), enabled_features: Vec::new(), }
350 }
351}
352
353impl Default for PerformanceMetrics {
354 fn default() -> Self {
355 Self {
356 creation_time: Duration::ZERO,
357 builtin_count: 0,
358 hir_cache_entries: 0,
359 bytecode_cache_entries: 0,
360 uncompressed_size: 0,
361 compression_ratio: 1.0,
362 peak_memory_usage: 0,
363 }
364 }
365}
366
367impl PlatformInfo {
368 pub fn current() -> Self {
370 Self {
371 os: std::env::consts::OS.to_string(),
372 arch: std::env::consts::ARCH.to_string(),
373 cpu_features: Self::detect_cpu_features(),
374 page_size: Self::detect_page_size(),
375 cache_line_size: Self::detect_cache_line_size(),
376 endianness: if cfg!(target_endian = "little") {
377 Endianness::Little
378 } else {
379 Endianness::Big
380 },
381 }
382 }
383
384 #[allow(unused_mut)]
386 fn detect_cpu_features() -> Vec<String> {
387 let mut features = Vec::new();
388
389 #[cfg(target_arch = "x86_64")]
390 {
391 if std::arch::is_x86_feature_detected!("sse4.2") {
392 features.push("sse4.2".to_string());
393 }
394 if std::arch::is_x86_feature_detected!("avx") {
395 features.push("avx".to_string());
396 }
397 if std::arch::is_x86_feature_detected!("avx2") {
398 features.push("avx2".to_string());
399 }
400 if std::arch::is_x86_feature_detected!("fma") {
401 features.push("fma".to_string());
402 }
403 }
404
405 #[cfg(target_arch = "aarch64")]
406 {
407 if std::arch::is_aarch64_feature_detected!("neon") {
408 features.push("neon".to_string());
409 }
410 }
411
412 features
413 }
414
415 fn detect_page_size() -> usize {
417 #[cfg(unix)]
419 {
420 unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
421 }
422 #[cfg(not(unix))]
423 {
424 4096 }
426 }
427
428 fn detect_cache_line_size() -> usize {
430 64
432 }
433}
434
435impl SnapshotFormat {
436 pub fn new(header: SnapshotHeader, data: Vec<u8>) -> Self {
438 Self {
439 header,
440 data,
441 checksum: None,
442 }
443 }
444
445 pub fn with_checksum(mut self, algorithm: ChecksumAlgorithm) -> crate::SnapshotResult<Self> {
447 #[cfg(feature = "validation")]
448 {
449 use sha2::{Digest, Sha256};
450
451 let checksum = match algorithm {
452 ChecksumAlgorithm::Sha256 => {
453 let mut hasher = Sha256::new();
454 hasher.update(&self.data);
455 hasher.finalize().to_vec()
456 }
457 ChecksumAlgorithm::Blake3 => blake3::hash(&self.data).as_bytes().to_vec(),
458 ChecksumAlgorithm::Crc32 => {
459 let crc = crc32fast::hash(&self.data);
460 crc.to_le_bytes().to_vec()
461 }
462 };
463
464 self.checksum = Some(checksum.clone());
465 self.header.checksum_info = Some(ChecksumInfo {
466 algorithm,
467 size: checksum.len(),
468 offset: 0, });
470 }
471 #[cfg(not(feature = "validation"))]
472 {
473 return Err(crate::SnapshotError::Configuration {
474 message: "Validation feature not enabled".to_string(),
475 });
476 }
477
478 Ok(self)
479 }
480
481 pub fn validate_checksum(&self) -> crate::SnapshotResult<bool> {
483 #[cfg(feature = "validation")]
484 {
485 if let (Some(checksum_info), Some(stored_checksum)) =
486 (&self.header.checksum_info, &self.checksum)
487 {
488 use sha2::{Digest, Sha256};
489
490 let calculated_checksum = match checksum_info.algorithm {
491 ChecksumAlgorithm::Sha256 => {
492 let mut hasher = Sha256::new();
493 hasher.update(&self.data);
494 hasher.finalize().to_vec()
495 }
496 ChecksumAlgorithm::Blake3 => blake3::hash(&self.data).as_bytes().to_vec(),
497 ChecksumAlgorithm::Crc32 => {
498 let crc = crc32fast::hash(&self.data);
499 crc.to_le_bytes().to_vec()
500 }
501 };
502
503 Ok(calculated_checksum == *stored_checksum)
504 } else {
505 Ok(true) }
507 }
508 #[cfg(not(feature = "validation"))]
509 {
510 Ok(true) }
512 }
513
514 pub fn total_size(&self) -> usize {
516 let header_size = bincode::serialized_size(&self.header).unwrap_or(0) as u64;
517 let data_size = self.data.len() as u64;
518 let checksum_size = self.checksum.as_ref().map_or(0, |c| c.len()) as u64;
519
520 (header_size + data_size + checksum_size) as usize
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527
528 #[test]
529 fn test_snapshot_header_validation() {
530 let metadata = SnapshotMetadata::current();
531 let header = SnapshotHeader::new(metadata);
532
533 assert!(header.validate().is_ok());
534 assert_eq!(header.magic, *SNAPSHOT_MAGIC);
535 assert_eq!(header.version, SNAPSHOT_VERSION);
536 }
537
538 #[test]
539 fn test_platform_compatibility() {
540 let metadata = SnapshotMetadata::current();
541 let header = SnapshotHeader::new(metadata);
542
543 assert!(header.is_platform_compatible());
544 }
545
546 #[test]
547 fn test_metadata_compatibility() {
548 let metadata = SnapshotMetadata::current();
549 assert!(metadata.is_compatible());
550 }
551
552 #[test]
553 fn test_platform_info() {
554 let platform = PlatformInfo::current();
555 assert!(!platform.os.is_empty());
556 assert!(!platform.arch.is_empty());
557 assert!(platform.page_size > 0);
558 assert!(platform.cache_line_size > 0);
559 }
560
561 #[test]
562 fn test_build_config() {
563 let config = BuildConfig::current();
564 assert!(!config.optimization_level.is_empty());
565 assert!(!config.compiler.is_empty());
566 }
567
568 #[test]
569 fn test_snapshot_format_creation() {
570 let metadata = SnapshotMetadata::current();
571 let header = SnapshotHeader::new(metadata);
572 let data = vec![1, 2, 3, 4, 5];
573 let format = SnapshotFormat::new(header, data);
574
575 assert_eq!(format.data.len(), 5);
576 assert!(format.checksum.is_none());
577 }
578
579 #[cfg(feature = "validation")]
580 #[test]
581 fn test_checksum_generation() {
582 let metadata = SnapshotMetadata::current();
583 let header = SnapshotHeader::new(metadata);
584 let data = vec![1, 2, 3, 4, 5];
585 let format = SnapshotFormat::new(header, data);
586
587 let format_with_checksum = format.with_checksum(ChecksumAlgorithm::Sha256).unwrap();
588
589 assert!(format_with_checksum.checksum.is_some());
590 assert!(format_with_checksum.header.checksum_info.is_some());
591 assert!(format_with_checksum.validate_checksum().unwrap());
592 }
593
594 #[test]
595 fn test_estimated_load_time() {
596 let metadata = SnapshotMetadata::current();
597 let mut header = SnapshotHeader::new(metadata);
598 header.data_info.compressed_size = 1024 * 1024; let load_time = header.estimated_load_time();
601 assert!(load_time > Duration::ZERO);
602 }
603}