⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/pkg/format/
mod.rs

1//! Package File Format
2//!
3//! Binary format specification for VeridianOS packages (.vpkg files).
4//!
5//! This module is organized into submodules:
6//! - [`compression`]: LZ4, Zstandard, and Brotli compression implementations
7//! - [`signature`]: Cryptographic signature structures for package verification
8//!
9//! ## Package Structure
10//!
11//! ```text
12//! +------------------+
13//! | Header (64 bytes)|
14//! +------------------+
15//! | Metadata (JSON)  |
16//! +------------------+
17//! | Content (files)  |
18//! +------------------+
19//! | Signatures       |
20//! | - Ed25519        |
21//! | - Dilithium      |
22//! +------------------+
23//! ```
24
25#![allow(clippy::no_effect, clippy::identity_op)]
26
27mod compression;
28mod signature;
29
30// Re-export public types
31pub use compression::{compress, decompress};
32pub use signature::{PackageSignatures, SignaturePolicy, TrustLevel, TrustedKey, TrustedKeyRing};
33
34/// Package file magic number
35pub const VPKG_MAGIC: [u8; 4] = *b"VPKG";
36
37/// Package format version
38pub const VPKG_VERSION: u32 = 1;
39
40/// Package types
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42#[repr(u8)]
43pub enum PackageType {
44    /// Binary executable package
45    Binary = 0,
46    /// Library package
47    Library = 1,
48    /// Kernel module/driver
49    KernelModule = 2,
50    /// Data-only package
51    Data = 3,
52    /// Meta-package (dependencies only)
53    Meta = 4,
54}
55
56/// Compression algorithms
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[repr(u8)]
59pub enum Compression {
60    /// No compression
61    None = 0,
62    /// Zstandard compression (recommended)
63    Zstd = 1,
64    /// LZ4 compression (fast)
65    Lz4 = 2,
66    /// Brotli compression (high ratio)
67    Brotli = 3,
68}
69
70impl Compression {
71    /// Create from byte value
72    pub fn from_u8(value: u8) -> Option<Self> {
73        match value {
74            0 => Some(Compression::None),
75            1 => Some(Compression::Zstd),
76            2 => Some(Compression::Lz4),
77            3 => Some(Compression::Brotli),
78            _ => None,
79        }
80    }
81
82    /// Get compression ratio estimate
83    pub fn ratio_estimate(&self) -> f32 {
84        match self {
85            Compression::None => 1.0,
86            Compression::Zstd => 0.3,    // ~70% reduction
87            Compression::Lz4 => 0.5,     // ~50% reduction
88            Compression::Brotli => 0.25, // ~75% reduction
89        }
90    }
91}
92
93/// Package file header (64 bytes)
94#[repr(C, packed)]
95#[derive(Debug, Clone, Copy)]
96pub struct PackageHeader {
97    /// Magic number "VPKG"
98    pub magic: [u8; 4],
99    /// Format version
100    pub version: u32,
101    /// Package type
102    pub pkg_type: u8,
103    /// Compression algorithm
104    pub compression: u8,
105    /// Reserved bytes
106    pub _reserved: [u8; 6],
107    /// Offset to metadata section
108    pub metadata_offset: u64,
109    /// Size of metadata section
110    pub metadata_size: u64,
111    /// Offset to content section
112    pub content_offset: u64,
113    /// Size of content section
114    pub content_size: u64,
115    /// Offset to signature section
116    pub signature_offset: u64,
117    /// Size of signature section
118    pub signature_size: u64,
119}
120
121impl PackageHeader {
122    /// Create new package header
123    pub fn new(
124        pkg_type: PackageType,
125        compression: Compression,
126        metadata_size: u64,
127        content_size: u64,
128        signature_size: u64,
129    ) -> Self {
130        let mut offset = 64u64; // Start after header
131
132        let metadata_offset = offset;
133        offset += metadata_size;
134
135        let content_offset = offset;
136        offset += content_size;
137
138        let signature_offset = offset;
139
140        Self {
141            magic: VPKG_MAGIC,
142            version: VPKG_VERSION,
143            pkg_type: pkg_type as u8,
144            compression: compression as u8,
145            _reserved: [0; 6],
146            metadata_offset,
147            metadata_size,
148            content_offset,
149            content_size,
150            signature_offset,
151            signature_size,
152        }
153    }
154
155    /// Validate package header
156    pub fn validate(&self) -> bool {
157        self.magic == VPKG_MAGIC && self.version == VPKG_VERSION
158    }
159
160    /// Get package type
161    pub fn get_type(&self) -> Option<PackageType> {
162        match self.pkg_type {
163            0 => Some(PackageType::Binary),
164            1 => Some(PackageType::Library),
165            2 => Some(PackageType::KernelModule),
166            3 => Some(PackageType::Data),
167            4 => Some(PackageType::Meta),
168            _ => None,
169        }
170    }
171
172    /// Get compression algorithm
173    pub fn get_compression(&self) -> Option<Compression> {
174        Compression::from_u8(self.compression)
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use alloc::{vec, vec::Vec};
181
182    use super::*;
183
184    #[test]
185    fn test_header_creation() {
186        let header = PackageHeader::new(PackageType::Binary, Compression::Zstd, 1024, 4096, 128);
187
188        assert!(header.validate());
189        assert_eq!(header.get_type(), Some(PackageType::Binary));
190        assert_eq!(header.get_compression(), Some(Compression::Zstd));
191    }
192
193    #[test]
194    fn test_compression_enum() {
195        assert_eq!(Compression::from_u8(0), Some(Compression::None));
196        assert_eq!(Compression::from_u8(1), Some(Compression::Zstd));
197        assert_eq!(Compression::from_u8(99), None);
198    }
199
200    #[test]
201    fn test_signature_serialization() {
202        let sigs = PackageSignatures {
203            ed25519_sig: [0x42; 64],
204            dilithium_sig: vec![0xAA; 100],
205        };
206
207        let bytes = sigs.to_bytes();
208        let restored = PackageSignatures::from_bytes(&bytes).unwrap();
209
210        assert_eq!(restored.ed25519_sig, sigs.ed25519_sig);
211        assert_eq!(restored.dilithium_sig, sigs.dilithium_sig);
212    }
213
214    #[test]
215    fn test_lz4_compression_roundtrip() {
216        let original = b"Hello, World! This is a test of LZ4 compression. \
217                        It should compress and decompress correctly.";
218
219        let compressed = compress(original, Compression::Lz4).unwrap();
220        let decompressed = decompress(&compressed, Compression::Lz4).unwrap();
221
222        assert_eq!(decompressed, original);
223    }
224
225    #[test]
226    fn test_zstd_compression_roundtrip() {
227        let original = b"Test data for Zstd compression. \
228                        AAAAAAAAAA BBBBBBBBBB CCCCCCCCCC repetitive data.";
229
230        let compressed = compress(original, Compression::Zstd).unwrap();
231        let decompressed = decompress(&compressed, Compression::Zstd).unwrap();
232
233        assert_eq!(decompressed, original);
234    }
235
236    #[test]
237    fn test_brotli_compression_roundtrip() {
238        let original = b"Brotli compression test with some repeated patterns \
239                        and varied content for better compression testing.";
240
241        let compressed = compress(original, Compression::Brotli).unwrap();
242        let decompressed = decompress(&compressed, Compression::Brotli).unwrap();
243
244        assert_eq!(decompressed, original);
245    }
246
247    #[test]
248    fn test_no_compression() {
249        let original = b"Uncompressed data";
250
251        let compressed = compress(original, Compression::None).unwrap();
252        let decompressed = decompress(&compressed, Compression::None).unwrap();
253
254        assert_eq!(compressed, original);
255        assert_eq!(decompressed, original);
256    }
257
258    #[test]
259    fn test_empty_input() {
260        let empty: &[u8] = &[];
261
262        // LZ4 empty
263        let lz4_result = compress(empty, Compression::Lz4).unwrap();
264        assert!(lz4_result.is_empty());
265
266        // Brotli empty
267        let brotli_result = compress(empty, Compression::Brotli).unwrap();
268        assert!(!brotli_result.is_empty()); // Has empty stream marker
269
270        // None empty
271        let none_result = compress(empty, Compression::None).unwrap();
272        assert!(none_result.is_empty());
273    }
274
275    #[test]
276    fn test_highly_compressible_data() {
277        // Create highly repetitive data
278        let mut original = Vec::with_capacity(10000);
279        for _ in 0..1000 {
280            original.extend_from_slice(b"AAAAAAAAAA");
281        }
282
283        // LZ4 should compress this well
284        let compressed = compress(&original, Compression::Lz4).unwrap();
285        assert!(compressed.len() < original.len() / 2);
286
287        let decompressed = decompress(&compressed, Compression::Lz4).unwrap();
288        assert_eq!(decompressed, original);
289    }
290}