nub_arch_x86_abi/lib.rs
1//! Wire format for the host ↔ guest "run this PVM program" RPC.
2//!
3//! The host pre-publishes each `Cap` it wants the guest to see via
4//! the [`FN_ID_NUB_PUT_CAP`] RPC (rkyv-archived `WireCap` payload;
5//! see the `state_cache` module in `nub-arch-x86` for the guest-side
6//! heap-resident directory it lands in), then ships a fixed-size
7//! [`InvokePacket`] referencing the published `Cap::Instance` by
8//! hash on every call. The invoke packet is `#[repr(C)]` bytes (no
9//! codec); the response is rkyv-archived ([`InvocationResult`]).
10
11#![cfg_attr(not(feature = "std"), no_std)]
12
13extern crate alloc;
14
15/// `fn_id` for the `nub_smoke` skeleton RPC (returns `42u64`).
16pub const FN_ID_NUB_SMOKE: u32 = 0;
17
18/// `fn_id` for the `nub_heap_stats` diagnostic. Payload is empty;
19/// response is 32 bytes packing four LE u64s (allocated_bytes,
20/// allocation_count, fragment_count, available_bytes).
21pub const FN_ID_NUB_HEAP_STATS: u32 = 2;
22
23/// `fn_id` for the cache-based RPC. Payload is a
24/// [`InvokePacket`] (host-side `#[repr(C)]` bytes, no rkyv); the
25/// guest dereferences cache VAs by `instance_hash` lookup, runs the
26/// JIT, and replies with rkyv-archived [`InvocationResult`].
27pub const FN_ID_NUB_INVOKE_CACHED: u32 = 3;
28
29/// `fn_id` for the heap-resident cap directory `put_cap` RPC.
30///
31/// Payload: rkyv-archived `javm_cap::wire::WireCap`. Guest decodes
32/// into a `Cap`, computes its content hash, inserts the cap into the
33/// guest-resident `DIRECTORY` (a `Mutex<HashMap<CapHash, Box<Cap>>>`
34/// living in talc heap), and replies with the rkyv-archived
35/// [`CapHash`] (raw 32 bytes).
36pub const FN_ID_NUB_PUT_CAP: u32 = 4;
37
38/// `fn_id` for the boot-info-read diagnostic RPC. Empty payload; the
39/// guest replies with the raw bytes of its [`BootInfo`] struct. The
40/// host can also obtain the same data by reading the kernel's
41/// `.boot_info` ELF section directly — this RPC exists as a
42/// belt-and-braces fallback in case the ELF symbol lookup misses.
43pub const FN_ID_NUB_GET_BOOT_INFO: u32 = 5;
44
45/// Number of guest-function slots reserved in the dispatch table.
46/// Must be at least `max(FN_ID_*) + 1`.
47pub const GUEST_FN_TABLE_SIZE: usize = 8;
48
49/// Boot-time info published by the guest at a known location (linker
50/// section `.boot_info`). The host reads it after the sandbox boots
51/// to learn the VA of the guest's cap directory, then dereferences
52/// the directory directly from host code (the kernel half is mapped
53/// at the same VA via the shallow-PML4-copy mechanism, so a
54/// directory-VA pointer is valid both in guest kernel mode and via
55/// the host's mmap shadow of the kernel image).
56///
57/// `magic` is checked first as a sanity guard against reading a
58/// stale or wrong-binary boot region. `directory_va` is the address
59/// of the inner `HashMap` (not the wrapping `Mutex`), so the host
60/// reader can take the directory lock and then index by `CapHash`.
61/// `directory_type_id` lets future protocol upgrades reject a
62/// mismatched layout (today: hash of the type signature `Mutex<
63/// HashMap<CapHash, Box<Cap>, FixedState, Global>>` — bumped when
64/// any field is added or its type changes).
65#[repr(C)]
66#[derive(Clone, Copy, Debug)]
67pub struct BootInfo {
68 /// `BootInfo::MAGIC` ("JAR_BOOT" in ASCII, little-endian). Host
69 /// reader rejects a region whose first 8 bytes don't match.
70 pub magic: u64,
71 /// VA of the cap directory's inner `HashMap` (NOT the wrapping
72 /// `Mutex`). Resolved by `nub-arch-x86` at boot via
73 /// `init_directory_va`.
74 pub directory_va: u64,
75 /// Hash of the directory's type signature. Bumped when the wire
76 /// layout of the directory changes. Today: opaque sentinel, the
77 /// host just compares for equality.
78 pub directory_type_id: u64,
79 /// Base of the per-process GUEST_VA reservation. Mirrors the
80 /// host-side constant; reproduced here so the host can sanity-
81 /// check the guest agrees on the layout.
82 pub guest_va_base: u64,
83 /// Reserved space for future fields. Zero-initialised; host
84 /// readers should not depend on the contents.
85 pub _reserved: [u64; 12],
86}
87
88impl BootInfo {
89 /// Constant numeric guard. The hex digits spell "JAR_BOOT" when
90 /// interpreted as ASCII bytes in big-endian order
91 /// (`0x4A 0x41 0x52 0x5F 0x42 0x4F 0x4F 0x54`). Stored as the
92 /// numeric u64 with that big-endian interpretation — read+compare
93 /// is a single u64 load.
94 pub const MAGIC: u64 = 0x4A41_525F_424F_4F54;
95}
96
97/// 32-byte Cap::Instance identity hash. Matches
98/// `javm_cap::CapHash` byte-wise (kept as a local alias here so
99/// `nub-arch-x86-abi` stays free of the javm-cap dependency, which
100/// pulls in `alloc::collections` etc.).
101pub type CapHash = [u8; 32];
102
103/// Fixed-layout invocation packet. Sent as raw `#[repr(C)]` bytes via
104/// the existing rkyv `Request` envelope (its `payload` field). The
105/// guest reads the bytes directly with `core::ptr::read_unaligned`.
106///
107/// `instance_hash` keys the cap to invoke (a published `Cap::Instance`).
108/// `endpoint_idx` selects the entry within `ImageCap.endpoints`.
109/// `args` overlay φ[7..=10] on top of the endpoint's `initial_regs`.
110#[repr(C)]
111#[derive(Clone, Copy, Debug, PartialEq, Eq)]
112pub struct InvokePacket {
113 pub instance_hash: CapHash,
114 pub endpoint_idx: u32,
115 pub _pad: u32,
116 pub args: [u64; 4],
117 pub initial_gas: u64,
118}
119
120impl InvokePacket {
121 /// Size of the packet in bytes — what the host writes to the
122 /// `Request.payload` and what the guest reads back.
123 pub const SIZE: usize = core::mem::size_of::<Self>();
124
125 /// Cast the packet to its raw bytes.
126 pub fn as_bytes(&self) -> &[u8] {
127 unsafe { core::slice::from_raw_parts(self as *const Self as *const u8, Self::SIZE) }
128 }
129
130 /// Parse a packet from raw bytes (length-checked).
131 pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
132 if bytes.len() != Self::SIZE {
133 return None;
134 }
135 Some(unsafe { core::ptr::read_unaligned(bytes.as_ptr() as *const Self) })
136 }
137}
138
139/// Invocation result. Both backends produce this shape on completion;
140/// rkyv-archived on the wire from the cached path's response.
141#[derive(Debug, Clone, Copy, PartialEq, Eq, rkyv::Archive, rkyv::Serialize)]
142#[rkyv(derive(Debug, PartialEq, Eq))]
143pub struct InvocationResult {
144 pub exit_reason: u32,
145 pub exit_arg: u32,
146 pub return_value: u64,
147 pub gas_remaining: u64,
148}