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/// Secret material that is redacted in diagnostics and cleared on drop.
275#[derive(Clone, Serialize, Deserialize, zeroize::Zeroize, zeroize::ZeroizeOnDrop)]
276#[serde(transparent)]
277pub struct SecretValue(pub String);
278
279/// One ordered host secret modification, preserving the JSON operation tags.
280#[derive(Debug, Clone, Serialize, Deserialize)]
281#[serde(tag = "change", rename_all = "snake_case")]
282pub enum SecretChange {
283    /// Replace an existing secret's value.
284    Rotate {
285        /// Secret identity.
286        name: String,
287        /// New secret material.
288        value: SecretValue,
289    },
290    /// Remove a secret; absence is a successful no-op.
291    Remove {
292        /// Secret identity.
293        name: String,
294    },
295    /// Replace an existing secret's allowed hosts.
296    SetAllowedHosts {
297        /// Secret identity.
298        name: String,
299        /// Replacement host patterns, in caller order.
300        hosts: Vec<String>,
301    },
302}
303
304/// Sequential, non-transactional secret modifications.
305#[derive(Debug, Clone, Serialize, Deserialize)]
306pub struct SecretsUpdate {
307    /// Apply in order and stop at the first operation failure.
308    pub changes: Vec<SecretChange>,
309}
310
311/// State mutation certainty reported by an operation error.
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
313#[serde(rename_all = "snake_case")]
314pub enum ErrorEffect {
315    /// This operation (or failed batch entry) did not change state.
316    None,
317    /// A change cannot be ruled out.
318    Unknown,
319}
320
321/// A recoverable peer error. Codes stay strings for future interoperability.
322#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
323pub struct ControlError {
324    /// Stable machine-readable code; preserve unknown future codes.
325    pub code: String,
326    /// Safe diagnostic text, never a request body or secret value.
327    pub message: String,
328    /// Certainty for this operation, not a retry instruction.
329    pub effect: ErrorEffect,
330}
331
332/// Completion of a sequential secret batch.
333#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
334#[serde(tag = "outcome", rename_all = "snake_case")]
335pub enum SecretsResult {
336    /// Every entry completed, including successful no-ops.
337    Complete {
338        /// Number of completed entries.
339        applied_count: u32,
340    },
341    /// Earlier entries completed; remaining entries were not attempted.
342    Failed {
343        /// Number of completed entries.
344        applied_count: u32,
345        /// Zero-based failed entry, equal to `applied_count`.
346        failed_index: u32,
347        /// Certainty here applies to the failed entry only.
348        error: ControlError,
349    },
350}
351
352/// Legacy JSON request, also used as a checked dispatch representation.
353#[derive(Debug, Clone, Serialize, Deserialize)]
354#[serde(tag = "op", rename_all = "snake_case")]
355pub enum ControlRequest {
356    /// Query available host operations.
357    Capabilities,
358    /// Set the memory target.
359    MemoryTarget {
360        /// Requested total memory in MiB.
361        total_mib: u64,
362    },
363    /// Observe memory state.
364    MemoryState,
365    /// Set the CPU target.
366    CpuTarget {
367        /// Requested online CPUs.
368        online: u32,
369    },
370    /// Observe CPU state.
371    CpuState,
372    /// Apply ordered secret modifications.
373    SecretsUpdate {
374        /// Caller-ordered changes.
375        changes: Vec<SecretChange>,
376    },
377}
378
379/// Existing JSON response with an additive discovery advertisement.
380///
381/// Omission of `control_protocols` identifies an ordinary legacy response.
382/// Raw JSON consumers must retain the original bytes to preserve unknown fields.
383#[derive(Debug, Clone, Default, Serialize, Deserialize)]
384pub struct JsonControlResponse {
385    /// Whether the operation succeeded.
386    pub ok: bool,
387    /// Legacy diagnostic; batch progress cannot be inferred from it.
388    #[serde(default, skip_serializing_if = "Option::is_none")]
389    pub error: Option<String>,
390    /// Present for memory operations.
391    #[serde(default, skip_serializing_if = "Option::is_none")]
392    pub memory: Option<MemoryState>,
393    /// Present for CPU operations.
394    #[serde(default, skip_serializing_if = "Option::is_none")]
395    pub cpu: Option<CpuState>,
396    /// Present for capability discovery.
397    #[serde(default, skip_serializing_if = "Option::is_none")]
398    pub capabilities: Option<Capabilities>,
399    /// Explicit operation formats, emitted only during discovery.
400    #[serde(default, skip_serializing_if = "Option::is_none")]
401    pub control_protocols: Option<Vec<String>>,
402}
403
404//--------------------------------------------------------------------------------------------------
405// Methods
406//--------------------------------------------------------------------------------------------------
407
408impl RuntimeCapabilities {
409    /// Project the extended inventory onto the frozen generation-one record.
410    pub fn generation_one(self) -> Capabilities {
411        Capabilities {
412            root_disk_grow: self.root_disk_grow,
413            cpu_resize: self.cpu_resize,
414            memory_resize: self.memory_resize,
415            secrets_update: self.secrets_update,
416        }
417    }
418}
419
420impl ControlError {
421    /// Construct an error known to precede mutation.
422    pub fn rejected(code: impl Into<String>, message: impl Into<String>) -> Self {
423        Self {
424            code: code.into(),
425            message: message.into(),
426            effect: ErrorEffect::None,
427        }
428    }
429}
430
431//--------------------------------------------------------------------------------------------------
432// Trait Implementations
433//--------------------------------------------------------------------------------------------------
434
435impl std::fmt::Debug for SecretValue {
436    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
437        f.write_str("[redacted]")
438    }
439}