1use serde::de::{self, Deserializer, Visitor};
4use serde::{Deserialize, Serialize, Serializer};
5use std::fmt;
6
7use super::command::HostCommand;
8use super::config::OperationConfig;
9use super::effect::EffectOutcome;
10use super::event::ExternalEvent;
11use super::root::{InitialContext, RootEntry};
12use super::scalar::{EffectId, InputId, OperationId, SCALAR_ERROR_MARKER, WireU64};
13use super::{KERNEL_ABI_VERSION, KernelBootstrapLimits};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct AbiRevision(u32);
24
25impl AbiRevision {
26 pub const CURRENT: Self = Self(KERNEL_ABI_VERSION);
27
28 pub const fn get(self) -> u32 {
29 self.0
30 }
31}
32
33impl Default for AbiRevision {
34 fn default() -> Self {
35 Self::CURRENT
36 }
37}
38
39impl Serialize for AbiRevision {
40 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
41 serializer.serialize_u32(self.0)
42 }
43}
44
45impl<'de> Deserialize<'de> for AbiRevision {
46 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
47 struct RevisionVisitor;
48
49 impl Visitor<'_> for RevisionVisitor {
50 type Value = AbiRevision;
51
52 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 write!(f, "the kernel ABI revision {KERNEL_ABI_VERSION}")
54 }
55
56 fn visit_u64<E: de::Error>(self, value: u64) -> Result<Self::Value, E> {
57 if value == u64::from(KERNEL_ABI_VERSION) {
58 Ok(AbiRevision::CURRENT)
59 } else {
60 Err(E::custom(format!(
61 "{SCALAR_ERROR_MARKER}: unsupported kernel ABI revision {value}; \
62 this kernel accepts only revision {KERNEL_ABI_VERSION}"
63 )))
64 }
65 }
66
67 fn visit_i64<E: de::Error>(self, value: i64) -> Result<Self::Value, E> {
68 if value < 0 {
69 return Err(E::custom(format!(
70 "{SCALAR_ERROR_MARKER}: unsupported kernel ABI revision {value}"
71 )));
72 }
73 self.visit_u64(value as u64)
74 }
75 }
76
77 deserializer.deserialize_u32(RevisionVisitor)
78 }
79}
80
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
96#[serde(deny_unknown_fields)]
97pub struct WireEnvelope {
98 pub abi_version: AbiRevision,
99 pub operation_id: OperationId,
100 pub input_id: InputId,
101 pub observed_at_ms: WireU64,
102 pub input: KernelInput,
103}
104
105impl WireEnvelope {
106 pub fn new(
107 operation_id: OperationId,
108 input_id: InputId,
109 observed_at_ms: WireU64,
110 input: KernelInput,
111 ) -> Self {
112 Self {
113 abi_version: AbiRevision::CURRENT,
114 operation_id,
115 input_id,
116 observed_at_ms,
117 input,
118 }
119 }
120}
121
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
137#[serde(tag = "kind", rename_all = "snake_case")]
138pub enum KernelInput {
139 ConfigureOperation(ConfigureOperation),
142 StartOperation(StartOperation),
144 ResolveEffect(ResolveEffect),
146 DeliverExternalEvent(DeliverExternalEvent),
148 HostControl(HostControl),
150}
151
152impl KernelInput {
153 pub fn authority(&self) -> InputAuthority {
155 match self {
156 Self::ConfigureOperation(_) => InputAuthority::HostBootstrap,
157 Self::StartOperation(_) => InputAuthority::HostRoot,
158 Self::ResolveEffect(_) => InputAuthority::HostEffectResolution,
159 Self::DeliverExternalEvent(_) => InputAuthority::HostObservedFact,
160 Self::HostControl(_) => InputAuthority::HostControlPlane,
161 }
162 }
163
164 pub fn admissible_lifecycles(&self) -> &'static [OperationLifecycle] {
168 match self {
169 Self::ConfigureOperation(_) => &[OperationLifecycle::Created],
170 Self::StartOperation(_) => &[OperationLifecycle::Configured],
171 Self::ResolveEffect(_) | Self::DeliverExternalEvent(_) => {
172 &[OperationLifecycle::Running, OperationLifecycle::Suspended]
173 }
174 Self::HostControl(_) => &[
175 OperationLifecycle::Configured,
176 OperationLifecycle::Running,
177 OperationLifecycle::Suspended,
178 ],
179 }
180 }
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
186#[serde(rename_all = "snake_case")]
187pub enum InputAuthority {
188 HostBootstrap,
190 HostRoot,
192 HostEffectResolution,
194 HostObservedFact,
196 HostControlPlane,
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
203#[serde(rename_all = "snake_case")]
204pub enum OperationLifecycle {
205 Created,
206 Configured,
207 Running,
208 Suspended,
209 Completed,
210 Cancelled,
211 Failed,
212}
213
214impl OperationLifecycle {
215 pub fn is_terminal(self) -> bool {
216 matches!(self, Self::Completed | Self::Cancelled | Self::Failed)
217 }
218}
219
220#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
222#[serde(deny_unknown_fields)]
223pub struct ConfigureOperation {
224 pub config: OperationConfig,
225}
226
227#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
231#[serde(deny_unknown_fields)]
232pub struct StartOperation {
233 pub entry: RootEntry,
234 pub initial_context: InitialContext,
235}
236
237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
239#[serde(deny_unknown_fields)]
240pub struct ResolveEffect {
241 pub effect_id: EffectId,
242 pub outcome: EffectOutcome,
243}
244
245#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
247#[serde(deny_unknown_fields)]
248pub struct DeliverExternalEvent {
249 pub event: ExternalEvent,
250}
251
252#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
254#[serde(deny_unknown_fields)]
255pub struct HostControl {
256 pub command: HostCommand,
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
266#[serde(rename_all = "snake_case")]
267pub enum WireRejectionKind {
268 InputTooLarge,
270 DepthExceeded,
272 CollectionTooLarge,
274 VersionMismatch,
276 MalformedJson,
278 UnknownField,
280 UnknownVariant,
282 MissingField,
284 InvalidScalar,
286 TypeMismatch,
288 PolicyViolation,
299}
300
301impl WireRejectionKind {
302 pub fn as_str(self) -> &'static str {
303 match self {
304 Self::InputTooLarge => "input_too_large",
305 Self::DepthExceeded => "depth_exceeded",
306 Self::CollectionTooLarge => "collection_too_large",
307 Self::VersionMismatch => "version_mismatch",
308 Self::MalformedJson => "malformed_json",
309 Self::UnknownField => "unknown_field",
310 Self::UnknownVariant => "unknown_variant",
311 Self::MissingField => "missing_field",
312 Self::InvalidScalar => "invalid_scalar",
313 Self::TypeMismatch => "type_mismatch",
314 Self::PolicyViolation => "policy_violation",
315 }
316 }
317}
318
319#[derive(Debug, Clone, PartialEq, Eq)]
321pub struct WireRejection {
322 pub kind: WireRejectionKind,
323 pub message: String,
324}
325
326impl WireRejection {
327 pub fn new(kind: WireRejectionKind, message: impl Into<String>) -> Self {
328 Self {
329 kind,
330 message: message.into(),
331 }
332 }
333}
334
335impl fmt::Display for WireRejection {
336 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 write!(f, "{}: {}", self.kind.as_str(), self.message)
338 }
339}
340
341impl std::error::Error for WireRejection {}
342
343#[derive(Deserialize)]
351struct AbiRevisionProbe {
352 #[serde(default)]
353 abi_version: Option<u32>,
354}
355
356pub fn decode_envelope_json(
360 input_json: &str,
361 limits: &KernelBootstrapLimits,
362) -> Result<WireEnvelope, WireRejection> {
363 if input_json.len() > limits.absolute_max_input_bytes as usize {
364 return Err(WireRejection::new(
365 WireRejectionKind::InputTooLarge,
366 format!(
367 "kernel input is {} bytes; the absolute bound is {} bytes",
368 input_json.len(),
369 limits.absolute_max_input_bytes
370 ),
371 ));
372 }
373
374 scan_structural_boundary(input_json, limits)?;
375
376 let probe: AbiRevisionProbe = serde_json::from_str(input_json).map_err(classify_serde_error)?;
377 match probe.abi_version {
378 Some(KERNEL_ABI_VERSION) => {}
379 other => {
380 let received = other.map_or_else(|| "missing".to_string(), |value| value.to_string());
381 return Err(WireRejection::new(
382 WireRejectionKind::VersionMismatch,
383 format!(
384 "kernel ABI revision mismatch: input revision {received}, \
385 this kernel accepts only revision {KERNEL_ABI_VERSION}"
386 ),
387 ));
388 }
389 }
390
391 serde_json::from_str(input_json).map_err(classify_serde_error)
392}
393
394pub fn encode_envelope_json(envelope: &WireEnvelope) -> String {
397 serde_json::to_string(envelope).expect("wire envelope is always serializable")
398}
399
400fn classify_serde_error(error: serde_json::Error) -> WireRejection {
401 let message = error.to_string();
402 let kind = if error.classify() == serde_json::error::Category::Syntax
403 || error.classify() == serde_json::error::Category::Eof
404 {
405 WireRejectionKind::MalformedJson
406 } else if message.contains(SCALAR_ERROR_MARKER) {
407 WireRejectionKind::InvalidScalar
408 } else if message.contains("unknown field") {
409 WireRejectionKind::UnknownField
410 } else if message.contains("unknown variant") {
411 WireRejectionKind::UnknownVariant
412 } else if message.contains("missing field") {
413 WireRejectionKind::MissingField
414 } else {
415 WireRejectionKind::TypeMismatch
416 };
417 WireRejection::new(kind, message)
418}
419
420pub fn scan_structural_boundary(
427 input_json: &str,
428 limits: &KernelBootstrapLimits,
429) -> Result<(), WireRejection> {
430 type Frame = (u64, bool);
432
433 let mut stack: Vec<Frame> = Vec::new();
434 let mut in_string = false;
435 let mut escaped = false;
436
437 for &byte in input_json.as_bytes() {
438 if in_string {
439 if escaped {
440 escaped = false;
441 } else if byte == b'\\' {
442 escaped = true;
443 } else if byte == b'"' {
444 in_string = false;
445 }
446 continue;
447 }
448
449 match byte {
450 b'"' => {
451 in_string = true;
452 mark_content(&mut stack);
453 }
454 b'{' | b'[' => {
455 mark_content(&mut stack);
456 stack.push((0, false));
457 if stack.len() > limits.absolute_max_json_depth as usize {
458 return Err(WireRejection::new(
459 WireRejectionKind::DepthExceeded,
460 format!(
461 "kernel input nests {} levels deep; the absolute bound is {}",
462 stack.len(),
463 limits.absolute_max_json_depth
464 ),
465 ));
466 }
467 }
468 b'}' | b']' => {
469 let Some((separators, has_content)) = stack.pop() else {
470 return Ok(());
472 };
473 let entries = if has_content { separators + 1 } else { 0 };
474 if entries > u64::from(limits.absolute_max_collection_entries) {
475 return Err(WireRejection::new(
476 WireRejectionKind::CollectionTooLarge,
477 format!(
478 "kernel input has a container with {entries} entries; \
479 the absolute bound is {}",
480 limits.absolute_max_collection_entries
481 ),
482 ));
483 }
484 }
485 b',' => {
486 if let Some(frame) = stack.last_mut() {
487 frame.0 += 1;
488 frame.1 = true;
489 }
490 }
491 byte if byte.is_ascii_whitespace() => {}
492 _ => mark_content(&mut stack),
493 }
494 }
495
496 Ok(())
497}
498
499fn mark_content(stack: &mut [(u64, bool)]) {
500 if let Some(frame) = stack.last_mut() {
501 frame.1 = true;
502 }
503}