Skip to main content

runmat_snapshot/
format.rs

1//! Snapshot file format and serialization
2//!
3//! High-performance binary format optimized for fast loading and validation.
4//! Uses a structured layout with versioning and integrity checks.
5
6use runmat_time::system_time_now;
7use std::time::{Duration, SystemTime};
8
9use serde::{Deserialize, Serialize};
10
11/// Snapshot file format magic number
12pub const SNAPSHOT_MAGIC: &[u8; 7] = b"RUNMAT\0";
13
14/// Current snapshot format version
15pub const SNAPSHOT_VERSION: u32 = 2;
16
17/// Oldest snapshot format version this build can safely load.
18pub const MIN_SUPPORTED_SNAPSHOT_VERSION: u32 = 2;
19
20/// Snapshot file format structure
21#[derive(Debug, Clone)]
22pub struct SnapshotFormat {
23    /// File header
24    pub header: SnapshotHeader,
25
26    /// Compressed snapshot data
27    pub data: Vec<u8>,
28
29    /// Optional integrity checksum
30    pub checksum: Option<Vec<u8>>,
31}
32
33/// Snapshot file header
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct SnapshotHeader {
36    /// Magic number for format identification
37    pub magic: [u8; 7],
38
39    /// Format version
40    pub version: u32,
41
42    /// Snapshot metadata
43    pub metadata: SnapshotMetadata,
44
45    /// Data section info
46    pub data_info: DataSectionInfo,
47
48    /// Checksum info (if enabled)
49    pub checksum_info: Option<ChecksumInfo>,
50
51    /// Header size (for format evolution)
52    pub header_size: u32,
53}
54
55/// Snapshot metadata
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct SnapshotMetadata {
58    /// Creation timestamp
59    pub created_at: SystemTime,
60
61    /// RunMat version used to create snapshot
62    pub runmat_version: String,
63
64    /// Snapshot creation tool version
65    pub tool_version: String,
66
67    /// Build configuration used
68    pub build_config: BuildConfig,
69
70    /// Performance characteristics
71    pub performance_metrics: PerformanceMetrics,
72
73    /// Feature flags enabled during creation
74    pub feature_flags: Vec<String>,
75
76    /// Target platform information
77    pub target_platform: PlatformInfo,
78}
79
80/// Build configuration
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct BuildConfig {
83    /// Optimization level used
84    pub optimization_level: String,
85
86    /// Debug information included
87    pub debug_info: bool,
88
89    /// Compiler used
90    pub compiler: String,
91
92    /// Compilation flags
93    pub compile_flags: Vec<String>,
94
95    /// Features enabled
96    pub enabled_features: Vec<String>,
97}
98
99/// Performance metrics from snapshot creation
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct PerformanceMetrics {
102    /// Time to create snapshot
103    pub creation_time: Duration,
104
105    /// Number of builtins captured
106    pub builtin_count: u64,
107
108    /// HIR cache entries
109    pub hir_cache_entries: u64,
110
111    /// Bytecode cache entries
112    pub bytecode_cache_entries: u64,
113
114    /// Total uncompressed size
115    pub uncompressed_size: u64,
116
117    /// Compression ratio achieved
118    pub compression_ratio: f64,
119
120    /// Memory usage during creation
121    pub peak_memory_usage: u64,
122}
123
124/// Target platform information
125#[derive(Debug, Clone, Serialize, Deserialize)]
126pub struct PlatformInfo {
127    /// Operating system
128    pub os: String,
129
130    /// Architecture
131    pub arch: String,
132
133    /// CPU features available
134    pub cpu_features: Vec<String>,
135
136    /// Memory page size
137    pub page_size: usize,
138
139    /// Cache line size
140    pub cache_line_size: usize,
141
142    /// Endianness
143    pub endianness: Endianness,
144}
145
146/// Endianness information
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub enum Endianness {
149    Little,
150    Big,
151}
152
153/// Data section information
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct DataSectionInfo {
156    /// Compression algorithm used
157    pub compression: CompressionInfo,
158
159    /// Uncompressed data size
160    pub uncompressed_size: u64,
161
162    /// Compressed data size
163    pub compressed_size: u64,
164
165    /// Data section offset in file
166    pub data_offset: u64,
167
168    /// Alignment requirements
169    pub alignment: usize,
170}
171
172/// Compression information
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct CompressionInfo {
175    /// Compression algorithm
176    pub algorithm: CompressionAlgorithm,
177
178    /// Compression level
179    pub level: u32,
180
181    /// Algorithm-specific parameters
182    pub parameters: std::collections::HashMap<String, String>,
183}
184
185/// Compression algorithms
186#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
187pub enum CompressionAlgorithm {
188    None,
189    Lz4 { fast: bool },
190    Zstd { dictionary: Option<Vec<u8>> },
191}
192
193/// Checksum information
194#[derive(Debug, Clone, Serialize, Deserialize)]
195pub struct ChecksumInfo {
196    /// Checksum algorithm
197    pub algorithm: ChecksumAlgorithm,
198
199    /// Checksum size in bytes
200    pub size: usize,
201
202    /// Checksum offset in file
203    pub offset: u64,
204}
205
206/// Checksum algorithms
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub enum ChecksumAlgorithm {
209    Sha256,
210    Blake3,
211    Crc32,
212}
213
214impl SnapshotHeader {
215    /// Create a new snapshot header
216    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, // Will be calculated during serialization
234        }
235    }
236
237    /// Validate header magic and version
238    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    /// Check if snapshot is compatible with current platform
260    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    /// Get expected loading performance characteristics
269    pub fn estimated_load_time(&self) -> Duration {
270        // Estimate based on data size and compression
271        let base_time = Duration::from_millis(10); // Base overhead
272        let data_time = Duration::from_nanos(
273            (self.data_info.compressed_size * 10) / 1024, // ~10ns per KB
274        );
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    /// Create metadata for current environment
286    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    /// Detect active feature flags
299    #[allow(clippy::vec_init_then_push)] // Conditional compilation makes vec![] problematic
300    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    /// Check compatibility with current environment
316    pub fn is_compatible(&self) -> bool {
317        // Check major version compatibility
318        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    /// Get human-readable age of snapshot
326    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    /// Detect current build configuration
335    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(), // Would need to be passed from build system
348            enabled_features: Vec::new(), // Would need feature detection
349        }
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    /// Detect current platform information
369    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    /// Detect available CPU features
385    #[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    /// Detect memory page size
416    fn detect_page_size() -> usize {
417        // Default to common page sizes
418        #[cfg(unix)]
419        {
420            unsafe { libc::sysconf(libc::_SC_PAGESIZE) as usize }
421        }
422        #[cfg(not(unix))]
423        {
424            4096 // Common default
425        }
426    }
427
428    /// Detect CPU cache line size
429    fn detect_cache_line_size() -> usize {
430        // Use common default, could be detected more precisely
431        64
432    }
433}
434
435impl SnapshotFormat {
436    /// Create a new snapshot format
437    pub fn new(header: SnapshotHeader, data: Vec<u8>) -> Self {
438        Self {
439            header,
440            data,
441            checksum: None,
442        }
443    }
444
445    /// Calculate and set checksum
446    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, // Will be set during serialization
469            });
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    /// Validate checksum
482    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) // No checksum to validate
506            }
507        }
508        #[cfg(not(feature = "validation"))]
509        {
510            Ok(true) // Skip validation if feature disabled
511        }
512    }
513
514    /// Get total file size
515    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; // 1MB
599
600        let load_time = header.estimated_load_time();
601        assert!(load_time > Duration::ZERO);
602    }
603}