Skip to main content

microsandbox_protocol/control/
types.rs

1//! Shared control records. Their field spellings also preserve legacy JSON.
2
3use serde::{Deserialize, Serialize};
4use std::path::PathBuf;
5
6/// Aggregate disk-compaction result shared with SDK-facing types.
7pub type DiskCompactionResult = microsandbox_types::DiskCompactionResult;
8
9//--------------------------------------------------------------------------------------------------
10// Types
11//--------------------------------------------------------------------------------------------------
12
13/// Empty map payload for state and capability queries.
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
15pub struct Empty {}
16
17/// Facilities available for this runtime and VM configuration.
18#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
19pub struct Capabilities {
20    /// Runtime supports online root-disk growth over its extended control API.
21    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
22    pub root_disk_grow: bool,
23    /// Live CPU target changes are available.
24    pub cpu_resize: bool,
25    /// Live memory target changes are available.
26    pub memory_resize: bool,
27    /// Host secret changes are available.
28    pub secrets_update: bool,
29}
30
31/// Complete generation-two runtime facility inventory.
32///
33/// [`Capabilities`] remains the released generation-one shape so Rust callers that construct it
34/// with a struct literal keep compiling. This additive record is returned to generation-two peers.
35#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
36pub struct RuntimeCapabilities {
37    /// Runtime supports online root-disk growth over its extended control API.
38    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
39    pub root_disk_grow: bool,
40    /// Explicit guest writeback policy is accepted by capture and pause operations.
41    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
42    pub guest_flush_policy: bool,
43    /// Capture accepts an explicit disk-integrity policy.
44    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
45    pub optional_disk_integrity: bool,
46    /// Direct local branch capture is available.
47    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
48    pub branch_create: bool,
49    /// Linux descriptor-backed branching is available through the legacy transport exception.
50    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
51    pub branch_memfd: bool,
52    /// Resident pause and resume are available.
53    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
54    pub pause_resume: bool,
55    /// Explicit disk compaction is available.
56    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
57    pub disk_compact: bool,
58    /// Owned-disk compaction selectors are available.
59    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
60    pub disk_compact_owned: bool,
61    /// Live CPU target changes are available.
62    pub cpu_resize: bool,
63    /// Live memory target changes are available.
64    pub memory_resize: bool,
65    /// Host secret changes are available.
66    pub secrets_update: bool,
67    /// Same-epoch full checkpoint capture is available.
68    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
69    pub checkpoint_create: bool,
70    /// Live disk-only capture is available.
71    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
72    pub disk_checkpoint_create: bool,
73}
74
75/// Purpose of a full checkpoint capture.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "snake_case")]
78pub enum CheckpointCaptureIntent {
79    /// User-requested full snapshot.
80    FullSnapshot,
81    /// Local idle or park continuation.
82    Park,
83    /// Transparent continuity operation.
84    TransparentTransfer,
85}
86
87/// Create one same-epoch full checkpoint.
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89pub struct CheckpointCreate {
90    /// Optional writeback policy; omission preserves released behavior.
91    #[serde(default, skip_serializing_if = "Option::is_none")]
92    pub guest_flush: Option<microsandbox_types::GuestFlush>,
93    /// Whether disk content hashes are recorded.
94    #[serde(default)]
95    pub record_integrity: bool,
96    /// Caller-selected capture identity.
97    pub checkpoint_id: String,
98    /// Capture purpose.
99    pub intent: CheckpointCaptureIntent,
100}
101
102/// Published full-checkpoint state.
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub struct CheckpointState {
105    /// Stable checkpoint identity.
106    pub checkpoint_id: String,
107    /// Content-addressed composite root.
108    pub checkpoint_root: String,
109    /// Runtime-local installed closure path.
110    pub path: PathBuf,
111    /// `full` or `incremental` physical-memory mode.
112    pub memory_mode: String,
113    /// Logical memory bytes represented by the capture.
114    pub memory_logical_bytes: u64,
115    /// Non-zero memory bytes emitted by the capture.
116    pub memory_emitted_bytes: u64,
117}
118
119/// Full-checkpoint completion, including a published result whose source recovery failed.
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121pub struct CheckpointResult {
122    /// Published checkpoint when publication completed.
123    #[serde(default, skip_serializing_if = "Option::is_none")]
124    pub checkpoint: Option<CheckpointState>,
125    /// Source recovery diagnostic after successful publication.
126    #[serde(default, skip_serializing_if = "Option::is_none")]
127    pub recovery_error: Option<String>,
128}
129
130/// Seal the owned root disk without capturing RAM or execution state.
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
132pub struct DiskCheckpointCreate {
133    /// Optional writeback policy; omission preserves released behavior.
134    #[serde(default, skip_serializing_if = "Option::is_none")]
135    pub guest_flush: Option<microsandbox_types::GuestFlush>,
136    /// Caller-selected capture identity.
137    pub checkpoint_id: String,
138}
139
140/// Complete disk-only capture result.
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
142pub struct DiskCheckpointState {
143    /// Capture identity echoed from the request.
144    pub checkpoint_id: String,
145    /// Runtime-owned closure containing the sealed manifests and layers.
146    pub path: PathBuf,
147    /// Complete root generation.
148    pub disk: microsandbox_types::snapshot::disk::DiskGenerationManifest,
149    /// Complete owned-volume inventory.
150    #[serde(default, skip_serializing_if = "Vec::is_empty")]
151    pub owned_volumes: Vec<microsandbox_types::snapshot::OwnedVolumeCapture>,
152}
153
154/// Capture directly into a reserved child-owned local handoff directory.
155#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
156pub struct BranchCreate {
157    /// Optional writeback policy.
158    #[serde(default, skip_serializing_if = "Option::is_none")]
159    pub guest_flush: Option<microsandbox_types::GuestFlush>,
160    /// Whether disk content hashes are recorded.
161    #[serde(default)]
162    pub record_integrity: bool,
163    /// Unique capture identity.
164    pub branch_id: String,
165    /// Reserved child sandbox name.
166    pub child_name: String,
167    /// Cache containing the existing handoff reservation.
168    pub memory_cache_dir: PathBuf,
169}
170
171/// Completed direct local branch handoff.
172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
173pub struct BranchResult {
174    /// Runtime-owned handoff closure path.
175    pub path: PathBuf,
176}
177
178/// Pause the runtime, optionally requiring guest writeback first.
179#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
180pub struct Pause {
181    /// Optional policy; omission preserves the released unit-shaped pause request.
182    #[serde(default, skip_serializing_if = "Option::is_none")]
183    pub guest_flush: Option<microsandbox_types::GuestFlush>,
184}
185
186/// Host-confirmed resident suspension state.
187#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
188pub struct PauseState {
189    /// Whether a user pause is currently held.
190    pub paused: bool,
191    /// Whether recovery owns a suspension that ordinary resume must not release.
192    pub recovery_required: bool,
193    /// Why full capture cannot use this pause.
194    #[serde(default, skip_serializing_if = "Option::is_none")]
195    pub capture_unavailable: Option<String>,
196}
197
198/// Grow the owned root disk and its mounted filesystem.
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
200pub struct RootDiskGrow {
201    /// Target capacity in bytes.
202    pub size_bytes: u64,
203}
204
205/// Verified root-disk growth and measured phases.
206#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
207pub struct RootDiskState {
208    /// Committed ext4 capacity in bytes.
209    pub filesystem_bytes: u64,
210    /// Guest-observed virtio-block capacity in bytes.
211    pub device_bytes: u64,
212    /// Total operation time in microseconds.
213    pub total_us: u64,
214    /// VM pause through resume in microseconds.
215    pub pause_us: u64,
216    /// Guest expansion and verification time in microseconds.
217    pub guest_us: u64,
218}
219
220/// Compact selected sandbox-owned disk chains.
221#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
222pub struct DiskCompact {
223    /// Disks selected for maintenance.
224    #[serde(default)]
225    pub target: microsandbox_types::DiskCompactionTarget,
226    /// Maximum oldest sealed layers to compact per selected disk.
227    #[serde(default, skip_serializing_if = "Option::is_none")]
228    pub layers: Option<u64>,
229    /// Resolve without applying the plan.
230    #[serde(default)]
231    pub dry_run: bool,
232}
233
234/// Accepted and observed memory quantities, all in MiB.
235#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
236pub struct MemoryState {
237    /// Boot allocation.
238    pub boot_mib: u64,
239    /// Accepted target, which need not have converged yet.
240    pub target_mib: u64,
241    /// Current guest observation.
242    pub current_mib: u64,
243    /// Boot-time capacity ceiling.
244    pub max_mib: u64,
245}
246
247/// CPU capacity, accepted target, observation, and enforcement.
248#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
249pub struct CpuState {
250    /// CPUs possible for this VM boot.
251    pub possible: u32,
252    /// Accepted online target.
253    pub requested_online: u32,
254    /// Guest-reported online CPUs.
255    pub actual_online: u32,
256    /// Host-enforced online CPUs.
257    pub enforced: u32,
258}
259
260/// Native memory-target payload, without SDK convergence policy.
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
262pub struct MemoryTarget {
263    /// Requested total memory in MiB.
264    pub total_mib: u64,
265}
266
267/// Native CPU-target payload.
268#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
269pub struct CpuTarget {
270    /// Requested online CPUs.
271    pub online: u32,
272}
273
274/// One ordered host secret modification, preserving the JSON operation tags.
275#[derive(Debug, Clone, Serialize, Deserialize)]
276#[serde(tag = "change", rename_all = "snake_case")]
277pub enum SecretChange {
278    /// Replace an existing secret's value.
279    Rotate {
280        /// Secret identity.
281        name: String,
282        /// New secret material.
283        value: SecretValue,
284    },
285    /// Remove a secret; absence is a successful no-op.
286    Remove {
287        /// Secret identity.
288        name: String,
289    },
290    /// Replace an existing secret's allowed hosts.
291    SetAllowedHosts {
292        /// Secret identity.
293        name: String,
294        /// Replacement host patterns, in caller order.
295        hosts: Vec<String>,
296    },
297}
298
299/// Sequential, non-transactional secret modifications.
300#[derive(Debug, Clone, Serialize, Deserialize)]
301pub struct SecretsUpdate {
302    /// Apply in order and stop at the first operation failure.
303    pub changes: Vec<SecretChange>,
304}
305
306/// State mutation certainty reported by an operation error.
307#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
308#[serde(rename_all = "snake_case")]
309pub enum ErrorEffect {
310    /// This operation (or failed batch entry) did not change state.
311    None,
312    /// A change cannot be ruled out.
313    Unknown,
314}
315
316/// A recoverable peer error. Codes stay strings for future interoperability.
317#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
318pub struct ControlError {
319    /// Stable machine-readable code; preserve unknown future codes.
320    pub code: String,
321    /// Safe diagnostic text, never a request body or secret value.
322    pub message: String,
323    /// Certainty for this operation, not a retry instruction.
324    pub effect: ErrorEffect,
325}
326
327/// Completion of a sequential secret batch.
328#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
329#[serde(tag = "outcome", rename_all = "snake_case")]
330pub enum SecretsResult {
331    /// Every entry completed, including successful no-ops.
332    Complete {
333        /// Number of completed entries.
334        applied_count: u32,
335    },
336    /// Earlier entries completed; remaining entries were not attempted.
337    Failed {
338        /// Number of completed entries.
339        applied_count: u32,
340        /// Zero-based failed entry, equal to `applied_count`.
341        failed_index: u32,
342        /// Certainty here applies to the failed entry only.
343        error: ControlError,
344    },
345}
346
347/// Legacy JSON request, also used as a checked dispatch representation.
348#[derive(Debug, Clone, Serialize, Deserialize)]
349#[serde(tag = "op", rename_all = "snake_case")]
350pub enum ControlRequest {
351    /// Query available host operations.
352    Capabilities,
353    /// Set the memory target.
354    MemoryTarget {
355        /// Requested total memory in MiB.
356        total_mib: u64,
357    },
358    /// Observe memory state.
359    MemoryState,
360    /// Set the CPU target.
361    CpuTarget {
362        /// Requested online CPUs.
363        online: u32,
364    },
365    /// Observe CPU state.
366    CpuState,
367    /// Apply ordered secret modifications.
368    SecretsUpdate {
369        /// Caller-ordered changes.
370        changes: Vec<SecretChange>,
371    },
372}
373
374/// Existing JSON response with an additive discovery advertisement.
375///
376/// Omission of `control_protocols` identifies an ordinary legacy response.
377/// Raw JSON consumers must retain the original bytes to preserve unknown fields.
378#[derive(Debug, Clone, Default, Serialize, Deserialize)]
379pub struct JsonControlResponse {
380    /// Whether the operation succeeded.
381    pub ok: bool,
382    /// Legacy diagnostic; batch progress cannot be inferred from it.
383    #[serde(default, skip_serializing_if = "Option::is_none")]
384    pub error: Option<String>,
385    /// Present for memory operations.
386    #[serde(default, skip_serializing_if = "Option::is_none")]
387    pub memory: Option<MemoryState>,
388    /// Present for CPU operations.
389    #[serde(default, skip_serializing_if = "Option::is_none")]
390    pub cpu: Option<CpuState>,
391    /// Present for capability discovery.
392    #[serde(default, skip_serializing_if = "Option::is_none")]
393    pub capabilities: Option<Capabilities>,
394    /// Explicit operation formats, emitted only during discovery.
395    #[serde(default, skip_serializing_if = "Option::is_none")]
396    pub control_protocols: Option<Vec<String>>,
397}
398
399//--------------------------------------------------------------------------------------------------
400// Methods
401//--------------------------------------------------------------------------------------------------
402
403impl RuntimeCapabilities {
404    /// Project the extended inventory onto the frozen generation-one record.
405    pub fn generation_one(self) -> Capabilities {
406        Capabilities {
407            root_disk_grow: self.root_disk_grow,
408            cpu_resize: self.cpu_resize,
409            memory_resize: self.memory_resize,
410            secrets_update: self.secrets_update,
411        }
412    }
413}
414
415impl ControlError {
416    /// Construct an error known to precede mutation.
417    pub fn rejected(code: impl Into<String>, message: impl Into<String>) -> Self {
418        Self {
419            code: code.into(),
420            message: message.into(),
421            effect: ErrorEffect::None,
422        }
423    }
424}
425
426//--------------------------------------------------------------------------------------------------
427// Re-Exports
428//--------------------------------------------------------------------------------------------------
429
430pub use microsandbox_types::SecretValue;