Skip to main content

rvf_types/
kernel.rs

1//! KERNEL_SEG (0x0E) types for the RVF computational container.
2//!
3//! Defines the 128-byte `KernelHeader` and associated enums per ADR-030.
4//! The KERNEL_SEG embeds a unikernel image that can self-boot an RVF file
5//! as a standalone query-serving microservice.
6
7use crate::error::RvfError;
8
9/// Magic number for `KernelHeader`: "RVKN" in big-endian.
10pub const KERNEL_MAGIC: u32 = 0x5256_4B4E;
11
12/// Kernel flags: kernel image is cryptographically signed.
13pub const KERNEL_FLAG_SIGNED: u32 = 1 << 8;
14/// Kernel flags: kernel image is compressed per the `compression` field.
15pub const KERNEL_FLAG_COMPRESSED: u32 = 1 << 10;
16/// Kernel flags: kernel must run inside a TEE enclave.
17pub const KERNEL_FLAG_REQUIRES_TEE: u32 = 1 << 0;
18/// Kernel flags: kernel measurement stored in WITNESS_SEG.
19pub const KERNEL_FLAG_MEASURED: u32 = 1 << 9;
20/// Kernel flags: kernel requires KVM (hardware virtualization).
21pub const KERNEL_FLAG_REQUIRES_KVM: u32 = 1 << 1;
22/// Kernel flags: kernel requires UEFI boot.
23pub const KERNEL_FLAG_REQUIRES_UEFI: u32 = 1 << 2;
24/// Kernel flags: kernel includes network stack.
25pub const KERNEL_FLAG_HAS_NETWORKING: u32 = 1 << 3;
26/// Kernel flags: kernel exposes RVF query API on api_port.
27pub const KERNEL_FLAG_HAS_QUERY_API: u32 = 1 << 4;
28/// Kernel flags: kernel exposes RVF ingest API.
29pub const KERNEL_FLAG_HAS_INGEST_API: u32 = 1 << 5;
30/// Kernel flags: kernel exposes health/metrics API.
31pub const KERNEL_FLAG_HAS_ADMIN_API: u32 = 1 << 6;
32/// Kernel flags: kernel can generate TEE attestation quotes.
33pub const KERNEL_FLAG_ATTESTATION_READY: u32 = 1 << 7;
34/// Kernel flags: kernel is position-independent.
35pub const KERNEL_FLAG_RELOCATABLE: u32 = 1 << 11;
36/// Kernel flags: kernel includes VirtIO network driver.
37pub const KERNEL_FLAG_HAS_VIRTIO_NET: u32 = 1 << 12;
38/// Kernel flags: kernel includes VirtIO block driver.
39pub const KERNEL_FLAG_HAS_VIRTIO_BLK: u32 = 1 << 13;
40/// Kernel flags: kernel includes VSOCK for host communication.
41pub const KERNEL_FLAG_HAS_VSOCK: u32 = 1 << 14;
42
43/// Target CPU architecture for the kernel image.
44#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46#[repr(u8)]
47pub enum KernelArch {
48    /// AMD64 / Intel 64.
49    X86_64 = 0x00,
50    /// ARM 64-bit (ARMv8-A and later).
51    Aarch64 = 0x01,
52    /// RISC-V 64-bit (RV64GC).
53    Riscv64 = 0x02,
54    /// Architecture-independent (e.g., interpreted).
55    Universal = 0xFE,
56    /// Reserved / unspecified.
57    Unknown = 0xFF,
58}
59
60impl TryFrom<u8> for KernelArch {
61    type Error = RvfError;
62
63    fn try_from(value: u8) -> Result<Self, Self::Error> {
64        match value {
65            0x00 => Ok(Self::X86_64),
66            0x01 => Ok(Self::Aarch64),
67            0x02 => Ok(Self::Riscv64),
68            0xFE => Ok(Self::Universal),
69            0xFF => Ok(Self::Unknown),
70            _ => Err(RvfError::InvalidEnumValue {
71                type_name: "KernelArch",
72                value: value as u64,
73            }),
74        }
75    }
76}
77
78/// Kernel type / runtime model.
79#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81#[repr(u8)]
82pub enum KernelType {
83    /// Hermit OS unikernel (Rust-native).
84    Hermit = 0x00,
85    /// Minimal Linux kernel (bzImage compatible).
86    MicroLinux = 0x01,
87    /// Asterinas framekernel (Linux ABI compatible).
88    Asterinas = 0x02,
89    /// WASI Preview 2 component (alternative to WASM_SEG).
90    WasiPreview2 = 0x03,
91    /// Custom kernel (requires external VMM knowledge).
92    Custom = 0x04,
93    /// Test stub for CI (boots, reports health, exits).
94    TestStub = 0xFE,
95}
96
97impl TryFrom<u8> for KernelType {
98    type Error = RvfError;
99
100    fn try_from(value: u8) -> Result<Self, Self::Error> {
101        match value {
102            0x00 => Ok(Self::Hermit),
103            0x01 => Ok(Self::MicroLinux),
104            0x02 => Ok(Self::Asterinas),
105            0x03 => Ok(Self::WasiPreview2),
106            0x04 => Ok(Self::Custom),
107            0xFE => Ok(Self::TestStub),
108            _ => Err(RvfError::InvalidEnumValue {
109                type_name: "KernelType",
110                value: value as u64,
111            }),
112        }
113    }
114}
115
116/// Transport mechanism for the kernel's query API.
117#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
119#[repr(u8)]
120pub enum ApiTransport {
121    /// HTTP/1.1 over TCP (default).
122    TcpHttp = 0x00,
123    /// gRPC over TCP (HTTP/2).
124    TcpGrpc = 0x01,
125    /// VirtIO socket (Firecracker host<->guest).
126    Vsock = 0x02,
127    /// Shared memory region (for same-host co-location).
128    SharedMem = 0x03,
129    /// No network API (batch mode only).
130    None = 0xFF,
131}
132
133impl TryFrom<u8> for ApiTransport {
134    type Error = RvfError;
135
136    fn try_from(value: u8) -> Result<Self, Self::Error> {
137        match value {
138            0x00 => Ok(Self::TcpHttp),
139            0x01 => Ok(Self::TcpGrpc),
140            0x02 => Ok(Self::Vsock),
141            0x03 => Ok(Self::SharedMem),
142            0xFF => Ok(Self::None),
143            _ => Err(RvfError::InvalidEnumValue {
144                type_name: "ApiTransport",
145                value: value as u64,
146            }),
147        }
148    }
149}
150
151/// 128-byte header for KERNEL_SEG payloads.
152///
153/// Follows the standard 64-byte `SegmentHeader`. All multi-byte fields are
154/// little-endian on the wire except `api_port` which is network byte order
155/// (big-endian) per ADR-030.
156#[derive(Clone, Copy, Debug)]
157#[repr(C)]
158pub struct KernelHeader {
159    /// Magic: `KERNEL_MAGIC` (0x52564B4E, "RVKN").
160    pub kernel_magic: u32,
161    /// KernelHeader format version (currently 1).
162    pub header_version: u16,
163    /// Target architecture (see `KernelArch`).
164    pub arch: u8,
165    /// Kernel type (see `KernelType`).
166    pub kernel_type: u8,
167    /// Bitfield flags (see `KERNEL_FLAG_*` constants).
168    pub kernel_flags: u32,
169    /// Minimum RAM required (MiB).
170    pub min_memory_mb: u32,
171    /// Virtual address of kernel entry point.
172    pub entry_point: u64,
173    /// Uncompressed kernel image size (bytes).
174    pub image_size: u64,
175    /// Compressed kernel image size (bytes).
176    pub compressed_size: u64,
177    /// Compression algorithm (same enum as `SegmentHeader.compression`).
178    pub compression: u8,
179    /// API transport (see `ApiTransport`).
180    pub api_transport: u8,
181    /// Default API port (network byte order).
182    pub api_port: u16,
183    /// Supported RVF query API version.
184    pub api_version: u32,
185    /// SHAKE-256-256 of uncompressed kernel image.
186    pub image_hash: [u8; 32],
187    /// Unique build identifier (UUID v7).
188    pub build_id: [u8; 16],
189    /// Build time (nanosecond UNIX timestamp).
190    pub build_timestamp: u64,
191    /// Recommended vCPU count (0 = single).
192    pub vcpu_count: u32,
193    /// Reserved (must be zero).
194    pub reserved_0: u32,
195    /// Offset to kernel command line within payload.
196    pub cmdline_offset: u64,
197    /// Length of kernel command line (bytes).
198    pub cmdline_length: u32,
199    /// Reserved (must be zero).
200    pub reserved_1: u32,
201}
202
203// Compile-time assertion: KernelHeader must be exactly 128 bytes.
204const _: () = assert!(core::mem::size_of::<KernelHeader>() == 128);
205
206impl KernelHeader {
207    /// Serialize the header to a 128-byte little-endian array.
208    pub fn to_bytes(&self) -> [u8; 128] {
209        let mut buf = [0u8; 128];
210        buf[0x00..0x04].copy_from_slice(&self.kernel_magic.to_le_bytes());
211        buf[0x04..0x06].copy_from_slice(&self.header_version.to_le_bytes());
212        buf[0x06] = self.arch;
213        buf[0x07] = self.kernel_type;
214        buf[0x08..0x0C].copy_from_slice(&self.kernel_flags.to_le_bytes());
215        buf[0x0C..0x10].copy_from_slice(&self.min_memory_mb.to_le_bytes());
216        buf[0x10..0x18].copy_from_slice(&self.entry_point.to_le_bytes());
217        buf[0x18..0x20].copy_from_slice(&self.image_size.to_le_bytes());
218        buf[0x20..0x28].copy_from_slice(&self.compressed_size.to_le_bytes());
219        buf[0x28] = self.compression;
220        buf[0x29] = self.api_transport;
221        buf[0x2A..0x2C].copy_from_slice(&self.api_port.to_be_bytes());
222        buf[0x2C..0x30].copy_from_slice(&self.api_version.to_le_bytes());
223        buf[0x30..0x50].copy_from_slice(&self.image_hash);
224        buf[0x50..0x60].copy_from_slice(&self.build_id);
225        buf[0x60..0x68].copy_from_slice(&self.build_timestamp.to_le_bytes());
226        buf[0x68..0x6C].copy_from_slice(&self.vcpu_count.to_le_bytes());
227        buf[0x6C..0x70].copy_from_slice(&self.reserved_0.to_le_bytes());
228        buf[0x70..0x78].copy_from_slice(&self.cmdline_offset.to_le_bytes());
229        buf[0x78..0x7C].copy_from_slice(&self.cmdline_length.to_le_bytes());
230        buf[0x7C..0x80].copy_from_slice(&self.reserved_1.to_le_bytes());
231        buf
232    }
233
234    /// Deserialize a `KernelHeader` from a 128-byte slice.
235    pub fn from_bytes(data: &[u8; 128]) -> Result<Self, RvfError> {
236        let magic = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
237        if magic != KERNEL_MAGIC {
238            return Err(RvfError::BadMagic {
239                expected: KERNEL_MAGIC,
240                got: magic,
241            });
242        }
243
244        Ok(Self {
245            kernel_magic: magic,
246            header_version: u16::from_le_bytes([data[0x04], data[0x05]]),
247            arch: data[0x06],
248            kernel_type: data[0x07],
249            kernel_flags: u32::from_le_bytes([data[0x08], data[0x09], data[0x0A], data[0x0B]]),
250            min_memory_mb: u32::from_le_bytes([data[0x0C], data[0x0D], data[0x0E], data[0x0F]]),
251            entry_point: u64::from_le_bytes([
252                data[0x10], data[0x11], data[0x12], data[0x13],
253                data[0x14], data[0x15], data[0x16], data[0x17],
254            ]),
255            image_size: u64::from_le_bytes([
256                data[0x18], data[0x19], data[0x1A], data[0x1B],
257                data[0x1C], data[0x1D], data[0x1E], data[0x1F],
258            ]),
259            compressed_size: u64::from_le_bytes([
260                data[0x20], data[0x21], data[0x22], data[0x23],
261                data[0x24], data[0x25], data[0x26], data[0x27],
262            ]),
263            compression: data[0x28],
264            api_transport: data[0x29],
265            api_port: u16::from_be_bytes([data[0x2A], data[0x2B]]),
266            api_version: u32::from_le_bytes([data[0x2C], data[0x2D], data[0x2E], data[0x2F]]),
267            image_hash: {
268                let mut h = [0u8; 32];
269                h.copy_from_slice(&data[0x30..0x50]);
270                h
271            },
272            build_id: {
273                let mut id = [0u8; 16];
274                id.copy_from_slice(&data[0x50..0x60]);
275                id
276            },
277            build_timestamp: u64::from_le_bytes([
278                data[0x60], data[0x61], data[0x62], data[0x63],
279                data[0x64], data[0x65], data[0x66], data[0x67],
280            ]),
281            vcpu_count: u32::from_le_bytes([data[0x68], data[0x69], data[0x6A], data[0x6B]]),
282            reserved_0: u32::from_le_bytes([data[0x6C], data[0x6D], data[0x6E], data[0x6F]]),
283            cmdline_offset: u64::from_le_bytes([
284                data[0x70], data[0x71], data[0x72], data[0x73],
285                data[0x74], data[0x75], data[0x76], data[0x77],
286            ]),
287            cmdline_length: u32::from_le_bytes([data[0x78], data[0x79], data[0x7A], data[0x7B]]),
288            reserved_1: u32::from_le_bytes([data[0x7C], data[0x7D], data[0x7E], data[0x7F]]),
289        })
290    }
291}
292
293#[cfg(test)]
294mod tests {
295    use super::*;
296
297    fn sample_header() -> KernelHeader {
298        KernelHeader {
299            kernel_magic: KERNEL_MAGIC,
300            header_version: 1,
301            arch: KernelArch::X86_64 as u8,
302            kernel_type: KernelType::Hermit as u8,
303            kernel_flags: KERNEL_FLAG_HAS_QUERY_API | KERNEL_FLAG_COMPRESSED,
304            min_memory_mb: 32,
305            entry_point: 0x0020_0000,
306            image_size: 400_000,
307            compressed_size: 180_000,
308            compression: 2, // ZSTD
309            api_transport: ApiTransport::TcpHttp as u8,
310            api_port: 8080,
311            api_version: 1,
312            image_hash: [0xAB; 32],
313            build_id: [0xCD; 16],
314            build_timestamp: 1_700_000_000_000_000_000,
315            vcpu_count: 1,
316            reserved_0: 0,
317            cmdline_offset: 128,
318            cmdline_length: 64,
319            reserved_1: 0,
320        }
321    }
322
323    #[test]
324    fn header_size_is_128() {
325        assert_eq!(core::mem::size_of::<KernelHeader>(), 128);
326    }
327
328    #[test]
329    fn magic_bytes_match_ascii() {
330        let bytes_be = KERNEL_MAGIC.to_be_bytes();
331        assert_eq!(&bytes_be, b"RVKN");
332    }
333
334    #[test]
335    fn round_trip_serialization() {
336        let original = sample_header();
337        let bytes = original.to_bytes();
338        let decoded = KernelHeader::from_bytes(&bytes).expect("from_bytes should succeed");
339
340        assert_eq!(decoded.kernel_magic, KERNEL_MAGIC);
341        assert_eq!(decoded.header_version, 1);
342        assert_eq!(decoded.arch, KernelArch::X86_64 as u8);
343        assert_eq!(decoded.kernel_type, KernelType::Hermit as u8);
344        assert_eq!(decoded.kernel_flags, KERNEL_FLAG_HAS_QUERY_API | KERNEL_FLAG_COMPRESSED);
345        assert_eq!(decoded.min_memory_mb, 32);
346        assert_eq!(decoded.entry_point, 0x0020_0000);
347        assert_eq!(decoded.image_size, 400_000);
348        assert_eq!(decoded.compressed_size, 180_000);
349        assert_eq!(decoded.compression, 2);
350        assert_eq!(decoded.api_transport, ApiTransport::TcpHttp as u8);
351        assert_eq!(decoded.api_port, 8080);
352        assert_eq!(decoded.api_version, 1);
353        assert_eq!(decoded.image_hash, [0xAB; 32]);
354        assert_eq!(decoded.build_id, [0xCD; 16]);
355        assert_eq!(decoded.build_timestamp, 1_700_000_000_000_000_000);
356        assert_eq!(decoded.vcpu_count, 1);
357        assert_eq!(decoded.reserved_0, 0);
358        assert_eq!(decoded.cmdline_offset, 128);
359        assert_eq!(decoded.cmdline_length, 64);
360        assert_eq!(decoded.reserved_1, 0);
361    }
362
363    #[test]
364    fn bad_magic_returns_error() {
365        let mut bytes = sample_header().to_bytes();
366        bytes[0] = 0x00; // corrupt magic
367        let err = KernelHeader::from_bytes(&bytes).unwrap_err();
368        match err {
369            RvfError::BadMagic { expected, .. } => assert_eq!(expected, KERNEL_MAGIC),
370            other => panic!("expected BadMagic, got {other:?}"),
371        }
372    }
373
374    #[test]
375    fn field_offsets() {
376        let h = sample_header();
377        let base = &h as *const _ as usize;
378
379        assert_eq!(&h.kernel_magic as *const _ as usize - base, 0x00);
380        assert_eq!(&h.header_version as *const _ as usize - base, 0x04);
381        assert_eq!(&h.arch as *const _ as usize - base, 0x06);
382        assert_eq!(&h.kernel_type as *const _ as usize - base, 0x07);
383        assert_eq!(&h.kernel_flags as *const _ as usize - base, 0x08);
384        assert_eq!(&h.min_memory_mb as *const _ as usize - base, 0x0C);
385        assert_eq!(&h.entry_point as *const _ as usize - base, 0x10);
386        assert_eq!(&h.image_size as *const _ as usize - base, 0x18);
387        assert_eq!(&h.compressed_size as *const _ as usize - base, 0x20);
388        assert_eq!(&h.compression as *const _ as usize - base, 0x28);
389        assert_eq!(&h.api_transport as *const _ as usize - base, 0x29);
390        assert_eq!(&h.api_port as *const _ as usize - base, 0x2A);
391        assert_eq!(&h.api_version as *const _ as usize - base, 0x2C);
392        assert_eq!(&h.image_hash as *const _ as usize - base, 0x30);
393        assert_eq!(&h.build_id as *const _ as usize - base, 0x50);
394        assert_eq!(&h.build_timestamp as *const _ as usize - base, 0x60);
395        assert_eq!(&h.vcpu_count as *const _ as usize - base, 0x68);
396        assert_eq!(&h.reserved_0 as *const _ as usize - base, 0x6C);
397        assert_eq!(&h.cmdline_offset as *const _ as usize - base, 0x70);
398        assert_eq!(&h.cmdline_length as *const _ as usize - base, 0x78);
399        assert_eq!(&h.reserved_1 as *const _ as usize - base, 0x7C);
400    }
401
402    #[test]
403    fn kernel_arch_try_from() {
404        assert_eq!(KernelArch::try_from(0x00), Ok(KernelArch::X86_64));
405        assert_eq!(KernelArch::try_from(0x01), Ok(KernelArch::Aarch64));
406        assert_eq!(KernelArch::try_from(0x02), Ok(KernelArch::Riscv64));
407        assert_eq!(KernelArch::try_from(0xFE), Ok(KernelArch::Universal));
408        assert_eq!(KernelArch::try_from(0xFF), Ok(KernelArch::Unknown));
409        assert!(KernelArch::try_from(0x03).is_err());
410        assert!(KernelArch::try_from(0x80).is_err());
411    }
412
413    #[test]
414    fn kernel_type_try_from() {
415        assert_eq!(KernelType::try_from(0x00), Ok(KernelType::Hermit));
416        assert_eq!(KernelType::try_from(0x01), Ok(KernelType::MicroLinux));
417        assert_eq!(KernelType::try_from(0x02), Ok(KernelType::Asterinas));
418        assert_eq!(KernelType::try_from(0x03), Ok(KernelType::WasiPreview2));
419        assert_eq!(KernelType::try_from(0x04), Ok(KernelType::Custom));
420        assert_eq!(KernelType::try_from(0xFE), Ok(KernelType::TestStub));
421        assert!(KernelType::try_from(0x05).is_err());
422        assert!(KernelType::try_from(0xFF).is_err());
423    }
424
425    #[test]
426    fn api_transport_try_from() {
427        assert_eq!(ApiTransport::try_from(0x00), Ok(ApiTransport::TcpHttp));
428        assert_eq!(ApiTransport::try_from(0x01), Ok(ApiTransport::TcpGrpc));
429        assert_eq!(ApiTransport::try_from(0x02), Ok(ApiTransport::Vsock));
430        assert_eq!(ApiTransport::try_from(0x03), Ok(ApiTransport::SharedMem));
431        assert_eq!(ApiTransport::try_from(0xFF), Ok(ApiTransport::None));
432        assert!(ApiTransport::try_from(0x04).is_err());
433        assert!(ApiTransport::try_from(0x80).is_err());
434    }
435
436    #[test]
437    fn kernel_flags_bit_positions() {
438        assert_eq!(KERNEL_FLAG_REQUIRES_TEE, 0x0001);
439        assert_eq!(KERNEL_FLAG_REQUIRES_KVM, 0x0002);
440        assert_eq!(KERNEL_FLAG_REQUIRES_UEFI, 0x0004);
441        assert_eq!(KERNEL_FLAG_HAS_NETWORKING, 0x0008);
442        assert_eq!(KERNEL_FLAG_HAS_QUERY_API, 0x0010);
443        assert_eq!(KERNEL_FLAG_HAS_INGEST_API, 0x0020);
444        assert_eq!(KERNEL_FLAG_HAS_ADMIN_API, 0x0040);
445        assert_eq!(KERNEL_FLAG_ATTESTATION_READY, 0x0080);
446        assert_eq!(KERNEL_FLAG_SIGNED, 0x0100);
447        assert_eq!(KERNEL_FLAG_MEASURED, 0x0200);
448        assert_eq!(KERNEL_FLAG_COMPRESSED, 0x0400);
449        assert_eq!(KERNEL_FLAG_RELOCATABLE, 0x0800);
450        assert_eq!(KERNEL_FLAG_HAS_VIRTIO_NET, 0x1000);
451        assert_eq!(KERNEL_FLAG_HAS_VIRTIO_BLK, 0x2000);
452        assert_eq!(KERNEL_FLAG_HAS_VSOCK, 0x4000);
453    }
454
455    #[test]
456    fn api_port_network_byte_order() {
457        let mut h = sample_header();
458        h.api_port = 0x1F90; // 8080
459        let bytes = h.to_bytes();
460        // api_port at offset 0x2A, big-endian
461        assert_eq!(bytes[0x2A], 0x1F);
462        assert_eq!(bytes[0x2B], 0x90);
463        let decoded = KernelHeader::from_bytes(&bytes).unwrap();
464        assert_eq!(decoded.api_port, 0x1F90);
465    }
466
467    #[test]
468    fn zero_filled_reserved_fields() {
469        let h = sample_header();
470        let bytes = h.to_bytes();
471        // reserved_0 at 0x6C..0x70 should be zero
472        assert_eq!(&bytes[0x6C..0x70], &[0, 0, 0, 0]);
473        // reserved_1 at 0x7C..0x80 should be zero
474        assert_eq!(&bytes[0x7C..0x80], &[0, 0, 0, 0]);
475    }
476}