Skip to main content

sim_lib_machine/
admission.rs

1use sha2::{Digest, Sha256};
2use sim_kernel::{ContentId, Symbol};
3
4use crate::{InstructionPolicy, LocatedCode};
5
6/// Hard bounds admitted for one machine description.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct AdmissionLimits {
9    /// Greatest number of decoded instructions.
10    pub instructions: usize,
11    /// Greatest logical operand width.
12    pub operand_units: usize,
13    /// Greatest number of indexed slots.
14    pub slots: usize,
15    /// Greatest guest frame depth.
16    pub frames: usize,
17    /// Greatest work allowance for one drive operation.
18    pub work: usize,
19}
20
21impl AdmissionLimits {
22    fn encode(self, digest: &mut Sha256) {
23        for value in [
24            self.instructions,
25            self.operand_units,
26            self.slots,
27            self.frames,
28            self.work,
29        ] {
30            digest.update(value.to_le_bytes());
31        }
32    }
33}
34
35/// Immutable code plus the consumer-owned metadata needed to admit it.
36pub struct MachineDescription<'a, P: InstructionPolicy, M> {
37    code: &'a LocatedCode<P>,
38    limits: AdmissionLimits,
39    metadata: &'a M,
40}
41
42impl<'a, P: InstructionPolicy, M> MachineDescription<'a, P, M> {
43    /// Describes already-frozen code under explicit machine limits.
44    pub fn new(code: &'a LocatedCode<P>, limits: AdmissionLimits, metadata: &'a M) -> Self {
45        Self {
46            code,
47            limits,
48            metadata,
49        }
50    }
51
52    /// Returns the immutable located code.
53    pub fn code(&self) -> &LocatedCode<P> {
54        self.code
55    }
56
57    /// Returns the declared limits.
58    pub fn limits(&self) -> AdmissionLimits {
59        self.limits
60    }
61
62    /// Returns consumer-owned entry and policy metadata.
63    pub fn metadata(&self) -> &M {
64        self.metadata
65    }
66}
67
68/// Pure consumer checks and canonical encoding used during admission.
69///
70/// These callbacks validate data only. Effect classification and execution are
71/// intentionally not part of this trait, so admission cannot invoke them.
72/// A WebAssembly validator or an eBPF verifier can supply the policy.
73pub trait AdmissionPolicy<P: InstructionPolicy, M> {
74    /// Structured consumer refusal.
75    type Refusal;
76
77    /// Checks the machine-wide description, including entry shape and policy compatibility.
78    fn validate_description(
79        description: &MachineDescription<'_, P, M>,
80    ) -> Result<(), Self::Refusal>;
81
82    /// Checks one instruction. Calling this for every instruction proves coverage.
83    fn validate_instruction(
84        instruction: &P::Instruction,
85        metadata: &M,
86    ) -> Result<(), Self::Refusal>;
87
88    /// Appends a canonical, unambiguous encoding of consumer-owned metadata.
89    fn encode_metadata(metadata: &M, output: &mut Vec<u8>);
90
91    /// Appends a canonical, unambiguous encoding of one decoded instruction.
92    fn encode_instruction(instruction: &P::Instruction, output: &mut Vec<u8>);
93}
94
95/// A refusal produced before a permit can exist.
96#[derive(Clone, Debug, PartialEq, Eq)]
97pub enum AdmissionError<R> {
98    /// A declared limit is zero.
99    ZeroLimit {
100        /// Name of the invalid limit.
101        limit: &'static str,
102    },
103    /// Located code exceeds the declared instruction bound.
104    InstructionLimit {
105        /// Number of located instructions.
106        actual: usize,
107        /// Declared maximum.
108        limit: usize,
109    },
110    /// A pure consumer validation rejected the description.
111    Policy(R),
112}
113
114/// Proof that one exact immutable machine description passed admission.
115#[derive(Clone, Debug, PartialEq, Eq)]
116pub struct MachinePermit {
117    content_id: ContentId,
118}
119
120impl MachinePermit {
121    /// Validates the complete description and mints its content-bound permit.
122    pub fn admit<P, M, A>(
123        description: &MachineDescription<'_, P, M>,
124    ) -> Result<Self, AdmissionError<A::Refusal>>
125    where
126        P: InstructionPolicy,
127        P::InstructionId: Copy + Eq + Ord,
128        A: AdmissionPolicy<P, M>,
129    {
130        validate_limits(description)?;
131        A::validate_description(description).map_err(AdmissionError::Policy)?;
132        for located in description.code.instructions() {
133            A::validate_instruction(located.instruction(), description.metadata)
134                .map_err(AdmissionError::Policy)?;
135        }
136        Ok(Self {
137            content_id: content_id::<P, M, A>(description),
138        })
139    }
140
141    /// Returns the kernel content identity admitted by this permit.
142    pub fn content_id(&self) -> &ContentId {
143        &self.content_id
144    }
145
146    /// Returns whether this permit binds exactly the supplied description.
147    pub fn accepts<P, M, A>(&self, description: &MachineDescription<'_, P, M>) -> bool
148    where
149        P: InstructionPolicy,
150        P::InstructionId: Copy + Eq + Ord,
151        A: AdmissionPolicy<P, M>,
152    {
153        self.content_id == content_id::<P, M, A>(description)
154    }
155}
156
157fn validate_limits<P: InstructionPolicy, M, R>(
158    description: &MachineDescription<'_, P, M>,
159) -> Result<(), AdmissionError<R>> {
160    for (limit, value) in [
161        ("instructions", description.limits.instructions),
162        ("operand_units", description.limits.operand_units),
163        ("slots", description.limits.slots),
164        ("frames", description.limits.frames),
165        ("work", description.limits.work),
166    ] {
167        if value == 0 {
168            return Err(AdmissionError::ZeroLimit { limit });
169        }
170    }
171    if description.code.len() > description.limits.instructions {
172        return Err(AdmissionError::InstructionLimit {
173            actual: description.code.len(),
174            limit: description.limits.instructions,
175        });
176    }
177    Ok(())
178}
179
180fn content_id<P, M, A>(description: &MachineDescription<'_, P, M>) -> ContentId
181where
182    P: InstructionPolicy,
183    P::InstructionId: Copy + Eq + Ord,
184    A: AdmissionPolicy<P, M>,
185{
186    let mut digest = Sha256::new();
187    digest.update(b"sim-lib-machine/admission/v1\0");
188    description.limits.encode(&mut digest);
189    let mut encoded = Vec::new();
190    A::encode_metadata(description.metadata, &mut encoded);
191    hash_field(&mut digest, &encoded);
192    description
193        .code
194        .hash_structure(&mut digest, |instruction, output| {
195            A::encode_instruction(instruction, output);
196        });
197    ContentId::from_bytes(
198        Symbol::qualified("core", "sha256"),
199        digest.finalize().into(),
200    )
201}
202
203fn hash_field(digest: &mut Sha256, bytes: &[u8]) {
204    digest.update(bytes.len().to_le_bytes());
205    digest.update(bytes);
206}