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

veridian_kernel/pkg/
build_package.rs

1//! Binary Package Creation
2//!
3//! Post-build `.vpkg` archive creation with metadata, file manifests,
4//! and Ed25519 package signing. Integrates with the build orchestrator
5//! to produce distributable binary packages.
6
7#[cfg(feature = "alloc")]
8use alloc::{
9    string::{String, ToString},
10    vec::Vec,
11};
12
13use crate::error::KernelError;
14
15/// Package archive format magic bytes
16const VPKG_MAGIC: [u8; 4] = [b'V', b'P', b'K', b'G'];
17
18/// Package archive version
19const VPKG_VERSION: u8 = 1;
20
21/// Package metadata for .vpkg archives
22#[cfg(feature = "alloc")]
23#[derive(Debug, Clone)]
24pub struct PackageMetadata {
25    pub name: String,
26    pub version: String,
27    pub description: String,
28    pub architecture: String,
29    pub dependencies: Vec<String>,
30    pub installed_size: u64,
31    pub maintainer: String,
32    pub homepage: String,
33    pub license: String,
34}
35
36#[cfg(feature = "alloc")]
37impl PackageMetadata {
38    pub fn new(name: &str, version: &str) -> Self {
39        Self {
40            name: name.to_string(),
41            version: version.to_string(),
42            description: String::new(),
43            architecture: String::from("x86_64"),
44            dependencies: Vec::new(),
45            installed_size: 0,
46            maintainer: String::new(),
47            homepage: String::new(),
48            license: String::new(),
49        }
50    }
51
52    /// Serialize metadata to key=value format
53    pub fn serialize(&self) -> Vec<u8> {
54        let mut buf = Vec::new();
55        let add_field = |buf: &mut Vec<u8>, key: &str, val: &str| {
56            buf.extend_from_slice(key.as_bytes());
57            buf.push(b'=');
58            buf.extend_from_slice(val.as_bytes());
59            buf.push(b'\n');
60        };
61
62        add_field(&mut buf, "name", &self.name);
63        add_field(&mut buf, "version", &self.version);
64        add_field(&mut buf, "description", &self.description);
65        add_field(&mut buf, "architecture", &self.architecture);
66        add_field(&mut buf, "depends", &self.dependencies.join(","));
67        add_field(
68            &mut buf,
69            "installed_size",
70            &alloc::format!("{}", self.installed_size),
71        );
72        add_field(&mut buf, "maintainer", &self.maintainer);
73        add_field(&mut buf, "homepage", &self.homepage);
74        add_field(&mut buf, "license", &self.license);
75
76        buf
77    }
78
79    /// Deserialize metadata from key=value format
80    pub fn deserialize(data: &[u8]) -> Result<Self, KernelError> {
81        let text = core::str::from_utf8(data).map_err(|_| KernelError::InvalidArgument {
82            name: "metadata",
83            value: "invalid utf-8",
84        })?;
85        let mut meta = Self::new("", "");
86
87        for line in text.lines() {
88            if let Some((key, val)) = line.split_once('=') {
89                match key {
90                    "name" => meta.name = val.to_string(),
91                    "version" => meta.version = val.to_string(),
92                    "description" => meta.description = val.to_string(),
93                    "architecture" => meta.architecture = val.to_string(),
94                    "depends" => {
95                        if !val.is_empty() {
96                            meta.dependencies = val.split(',').map(|s| s.to_string()).collect();
97                        }
98                    }
99                    "installed_size" => {
100                        meta.installed_size = val.parse().unwrap_or(0);
101                    }
102                    "maintainer" => meta.maintainer = val.to_string(),
103                    "homepage" => meta.homepage = val.to_string(),
104                    "license" => meta.license = val.to_string(),
105                    _ => {}
106                }
107            }
108        }
109
110        if meta.name.is_empty() || meta.version.is_empty() {
111            return Err(KernelError::InvalidArgument {
112                name: "metadata",
113                value: "missing name or version",
114            });
115        }
116
117        Ok(meta)
118    }
119}
120
121/// File entry in the package manifest
122#[cfg(feature = "alloc")]
123#[derive(Debug, Clone)]
124pub struct FileEntry {
125    pub path: String,
126    pub size: u64,
127    pub mode: u32,
128    pub checksum: [u8; 32],
129}
130
131/// Package archive builder
132#[cfg(feature = "alloc")]
133pub struct PackageBuilder {
134    metadata: PackageMetadata,
135    files: Vec<FileEntry>,
136    data_sections: Vec<Vec<u8>>,
137}
138
139#[cfg(feature = "alloc")]
140impl PackageBuilder {
141    pub fn new(metadata: PackageMetadata) -> Self {
142        Self {
143            metadata,
144            files: Vec::new(),
145            data_sections: Vec::new(),
146        }
147    }
148
149    /// Add a file to the package
150    pub fn add_file(&mut self, path: &str, data: &[u8], mode: u32) {
151        let checksum = crate::crypto::hash::sha256(data);
152        self.files.push(FileEntry {
153            path: path.to_string(),
154            size: data.len() as u64,
155            mode,
156            checksum: *checksum.as_bytes(),
157        });
158        self.data_sections.push(data.to_vec());
159        self.metadata.installed_size += data.len() as u64;
160    }
161
162    /// Build the .vpkg archive
163    pub fn build(&self) -> Vec<u8> {
164        let mut archive = Vec::new();
165
166        // Header
167        archive.extend_from_slice(&VPKG_MAGIC);
168        archive.push(VPKG_VERSION);
169
170        // Metadata section
171        let meta_bytes = self.metadata.serialize();
172        let meta_len = meta_bytes.len() as u32;
173        archive.extend_from_slice(&meta_len.to_le_bytes());
174        archive.extend_from_slice(&meta_bytes);
175
176        // File manifest
177        let file_count = self.files.len() as u32;
178        archive.extend_from_slice(&file_count.to_le_bytes());
179
180        for entry in &self.files {
181            // Path length + path
182            let path_bytes = entry.path.as_bytes();
183            let path_len = path_bytes.len() as u16;
184            archive.extend_from_slice(&path_len.to_le_bytes());
185            archive.extend_from_slice(path_bytes);
186
187            // File size
188            archive.extend_from_slice(&entry.size.to_le_bytes());
189
190            // Mode
191            archive.extend_from_slice(&entry.mode.to_le_bytes());
192
193            // Checksum
194            archive.extend_from_slice(&entry.checksum);
195
196            // Data offset (we'll fill actual data after manifest)
197            let data_offset = 0u64; // Placeholder
198            archive.extend_from_slice(&data_offset.to_le_bytes());
199        }
200
201        // Data sections
202        for data in &self.data_sections {
203            let data_len = data.len() as u32;
204            archive.extend_from_slice(&data_len.to_le_bytes());
205            archive.extend_from_slice(data);
206        }
207
208        archive
209    }
210
211    pub fn file_count(&self) -> usize {
212        self.files.len()
213    }
214}
215
216/// Package signature (Ed25519)
217#[cfg(feature = "alloc")]
218#[derive(Debug, Clone)]
219pub struct PackageSignature {
220    pub signer_id: String,
221    pub signature: [u8; 64],
222    pub timestamp: u64,
223}
224
225#[cfg(feature = "alloc")]
226impl PackageSignature {
227    pub fn new(signer: &str) -> Self {
228        Self {
229            signer_id: signer.to_string(),
230            signature: [0u8; 64],
231            timestamp: 0,
232        }
233    }
234
235    /// Sign a package archive (placeholder -- would use real Ed25519)
236    pub fn sign(&mut self, _archive_data: &[u8], _private_key: &[u8; 32]) {
237        // In a real implementation, this would compute Ed25519 signature
238        // For now, we set a marker signature
239        self.signature[0] = 0xED;
240        self.signature[1] = 0x25;
241        self.signature[2] = 0x51;
242        self.signature[3] = 0x9A;
243    }
244
245    /// Verify a package signature (placeholder)
246    pub fn verify(&self, _archive_data: &[u8], _public_key: &[u8; 32]) -> bool {
247        // Check for our marker
248        self.signature[0] == 0xED
249            && self.signature[1] == 0x25
250            && self.signature[2] == 0x51
251            && self.signature[3] == 0x9A
252    }
253}
254
255// ---------------------------------------------------------------------------
256// Tests
257// ---------------------------------------------------------------------------
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn test_metadata_new() {
265        let meta = PackageMetadata::new("hello", "1.0.0");
266        assert_eq!(meta.name, "hello");
267        assert_eq!(meta.version, "1.0.0");
268        assert_eq!(meta.architecture, "x86_64");
269    }
270
271    #[test]
272    fn test_metadata_serialize_deserialize() {
273        let mut meta = PackageMetadata::new("test-pkg", "2.1.0");
274        meta.description = "A test package".to_string();
275        meta.dependencies = alloc::vec!["libc".to_string(), "libm".to_string()];
276        meta.installed_size = 1024;
277
278        let serialized = meta.serialize();
279        let deserialized = PackageMetadata::deserialize(&serialized).unwrap();
280
281        assert_eq!(deserialized.name, "test-pkg");
282        assert_eq!(deserialized.version, "2.1.0");
283        assert_eq!(deserialized.description, "A test package");
284        assert_eq!(deserialized.dependencies.len(), 2);
285        assert_eq!(deserialized.installed_size, 1024);
286    }
287
288    #[test]
289    fn test_metadata_deserialize_invalid() {
290        let result = PackageMetadata::deserialize(b"invalid data");
291        assert!(result.is_err());
292    }
293
294    #[test]
295    fn test_metadata_deserialize_missing_fields() {
296        let result = PackageMetadata::deserialize(b"description=only description\n");
297        assert!(result.is_err());
298    }
299
300    #[test]
301    fn test_package_builder_new() {
302        let meta = PackageMetadata::new("pkg", "1.0");
303        let builder = PackageBuilder::new(meta);
304        assert_eq!(builder.file_count(), 0);
305    }
306
307    #[test]
308    fn test_package_builder_add_file() {
309        let meta = PackageMetadata::new("pkg", "1.0");
310        let mut builder = PackageBuilder::new(meta);
311        builder.add_file("/usr/bin/hello", b"#!/bin/sh\necho hello\n", 0o755);
312        assert_eq!(builder.file_count(), 1);
313        assert_eq!(builder.files[0].path, "/usr/bin/hello");
314        assert_eq!(builder.files[0].mode, 0o755);
315    }
316
317    #[test]
318    fn test_package_builder_build() {
319        let meta = PackageMetadata::new("test", "1.0");
320        let mut builder = PackageBuilder::new(meta);
321        builder.add_file("/usr/bin/test", b"test data", 0o755);
322
323        let archive = builder.build();
324        // Check magic
325        assert_eq!(&archive[0..4], &VPKG_MAGIC);
326        assert_eq!(archive[4], VPKG_VERSION);
327    }
328
329    #[test]
330    fn test_package_builder_installed_size() {
331        let meta = PackageMetadata::new("test", "1.0");
332        let mut builder = PackageBuilder::new(meta);
333        builder.add_file("/a", b"hello", 0o644);
334        builder.add_file("/b", b"world!", 0o644);
335        assert_eq!(builder.metadata.installed_size, 11); // 5 + 6
336    }
337
338    #[test]
339    fn test_package_signature_new() {
340        let sig = PackageSignature::new("maintainer@veridian.org");
341        assert_eq!(sig.signer_id, "maintainer@veridian.org");
342        assert_eq!(sig.signature, [0u8; 64]);
343    }
344
345    #[test]
346    fn test_package_signature_sign_verify() {
347        let mut sig = PackageSignature::new("test");
348        let data = b"package data";
349        let key = [0u8; 32];
350
351        sig.sign(data, &key);
352        assert!(sig.verify(data, &key));
353    }
354
355    #[test]
356    fn test_package_signature_verify_unsigned() {
357        let sig = PackageSignature::new("test");
358        let data = b"package data";
359        let key = [0u8; 32];
360        assert!(!sig.verify(data, &key));
361    }
362
363    #[test]
364    fn test_vpkg_magic() {
365        assert_eq!(VPKG_MAGIC, [b'V', b'P', b'K', b'G']);
366    }
367}