Skip to main content

secure_exec_sidecar/
protocol.rs

1use crate::generated_protocol::v1 as generated_protocol;
2use serde::de::{self, SeqAccess, Visitor};
3use serde::ser::SerializeTuple;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use serde_json::Value;
6use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
7use std::error::Error;
8use std::fmt;
9
10pub use crate::wire::{OwnershipRequirement, ProtocolCodecError, RequestDirection};
11
12pub const PROTOCOL_NAME: &str = crate::wire::PROTOCOL_NAME;
13pub const PROTOCOL_VERSION: u16 = crate::wire::PROTOCOL_VERSION;
14pub const DEFAULT_MAX_FRAME_BYTES: usize = crate::wire::DEFAULT_MAX_FRAME_BYTES;
15pub const DEFAULT_COMPLETED_RESPONSE_CAP: usize = 10_000;
16pub type RequestId = crate::wire::RequestId;
17pub type ExtEnvelope = crate::wire::ExtEnvelope;
18
19fn serialize_bare_newtype_tag<S, T>(serializer: S, tag: u64, payload: &T) -> Result<S::Ok, S::Error>
20where
21    S: Serializer,
22    T: Serialize,
23{
24    let mut tuple = serializer.serialize_tuple(2)?;
25    tuple.serialize_element(&serde_bare::Uint(tag))?;
26    tuple.serialize_element(payload)?;
27    tuple.end()
28}
29
30/// Convert the compatibility protocol frame into the generated wire frame.
31pub fn to_generated_protocol_frame(
32    frame: &ProtocolFrame,
33) -> Result<generated_protocol::ProtocolFrame, ProtocolCodecError> {
34    Ok(match frame {
35        ProtocolFrame::Request(frame) => {
36            generated_protocol::ProtocolFrame::RequestFrame(generated_protocol::RequestFrame {
37                schema: to_generated_protocol_schema(&frame.schema),
38                request_id: frame.request_id,
39                ownership: to_generated_ownership_scope(&frame.ownership),
40                payload: to_generated_request_payload(&frame.payload)?,
41            })
42        }
43        ProtocolFrame::Response(frame) => {
44            generated_protocol::ProtocolFrame::ResponseFrame(generated_protocol::ResponseFrame {
45                schema: to_generated_protocol_schema(&frame.schema),
46                request_id: frame.request_id,
47                ownership: to_generated_ownership_scope(&frame.ownership),
48                payload: to_generated_response_payload(&frame.payload)?,
49            })
50        }
51        ProtocolFrame::Event(frame) => {
52            generated_protocol::ProtocolFrame::EventFrame(generated_protocol::EventFrame {
53                schema: to_generated_protocol_schema(&frame.schema),
54                ownership: to_generated_ownership_scope(&frame.ownership),
55                payload: to_generated_event_payload(&frame.payload),
56            })
57        }
58        ProtocolFrame::SidecarRequest(frame) => {
59            generated_protocol::ProtocolFrame::SidecarRequestFrame(
60                generated_protocol::SidecarRequestFrame {
61                    schema: to_generated_protocol_schema(&frame.schema),
62                    request_id: frame.request_id,
63                    ownership: to_generated_ownership_scope(&frame.ownership),
64                    payload: to_generated_sidecar_request_payload(&frame.payload)?,
65                },
66            )
67        }
68        ProtocolFrame::SidecarResponse(frame) => {
69            generated_protocol::ProtocolFrame::SidecarResponseFrame(
70                generated_protocol::SidecarResponseFrame {
71                    schema: to_generated_protocol_schema(&frame.schema),
72                    request_id: frame.request_id,
73                    ownership: to_generated_ownership_scope(&frame.ownership),
74                    payload: to_generated_sidecar_response_payload(&frame.payload)?,
75                },
76            )
77        }
78    })
79}
80
81/// Convert the generated wire frame into the compatibility protocol frame.
82pub fn from_generated_protocol_frame(
83    frame: generated_protocol::ProtocolFrame,
84) -> Result<ProtocolFrame, ProtocolCodecError> {
85    Ok(match frame {
86        generated_protocol::ProtocolFrame::RequestFrame(frame) => {
87            ProtocolFrame::Request(RequestFrame {
88                schema: from_generated_protocol_schema(frame.schema),
89                request_id: frame.request_id,
90                ownership: from_generated_ownership_scope(frame.ownership),
91                payload: from_generated_request_payload(frame.payload)?,
92            })
93        }
94        generated_protocol::ProtocolFrame::ResponseFrame(frame) => {
95            ProtocolFrame::Response(ResponseFrame {
96                schema: from_generated_protocol_schema(frame.schema),
97                request_id: frame.request_id,
98                ownership: from_generated_ownership_scope(frame.ownership),
99                payload: from_generated_response_payload(frame.payload)?,
100            })
101        }
102        generated_protocol::ProtocolFrame::EventFrame(frame) => ProtocolFrame::Event(EventFrame {
103            schema: from_generated_protocol_schema(frame.schema),
104            ownership: from_generated_ownership_scope(frame.ownership),
105            payload: from_generated_event_payload(frame.payload),
106        }),
107        generated_protocol::ProtocolFrame::SidecarRequestFrame(frame) => {
108            ProtocolFrame::SidecarRequest(SidecarRequestFrame {
109                schema: from_generated_protocol_schema(frame.schema),
110                request_id: frame.request_id,
111                ownership: from_generated_ownership_scope(frame.ownership),
112                payload: from_generated_sidecar_request_payload(frame.payload)?,
113            })
114        }
115        generated_protocol::ProtocolFrame::SidecarResponseFrame(frame) => {
116            ProtocolFrame::SidecarResponse(SidecarResponseFrame {
117                schema: from_generated_protocol_schema(frame.schema),
118                request_id: frame.request_id,
119                ownership: from_generated_ownership_scope(frame.ownership),
120                payload: from_generated_sidecar_response_payload(frame.payload)?,
121            })
122        }
123    })
124}
125
126fn to_generated_protocol_schema(schema: &ProtocolSchema) -> generated_protocol::ProtocolSchema {
127    schema.clone()
128}
129
130fn from_generated_protocol_schema(schema: generated_protocol::ProtocolSchema) -> ProtocolSchema {
131    schema
132}
133
134// `OwnershipScope` is now an alias for the generated `crate::wire::OwnershipScope`, so the
135// compat<->generated converters are identity functions. They are retained so the JSON-carrying
136// frame-conversion chain (still hand-written, out of scope for this pass) keeps compiling
137// without site-by-site rewiring.
138pub(crate) fn to_generated_ownership_scope(
139    ownership: &OwnershipScope,
140) -> generated_protocol::OwnershipScope {
141    ownership.clone()
142}
143
144pub(crate) fn from_generated_ownership_scope(
145    ownership: generated_protocol::OwnershipScope,
146) -> OwnershipScope {
147    ownership
148}
149
150fn to_generated_dispose_reason(reason: &DisposeReason) -> generated_protocol::DisposeReason {
151    match reason {
152        DisposeReason::Requested => generated_protocol::DisposeReason::Requested,
153        DisposeReason::ConnectionClosed => generated_protocol::DisposeReason::ConnectionClosed,
154        DisposeReason::HostShutdown => generated_protocol::DisposeReason::HostShutdown,
155    }
156}
157
158fn from_generated_dispose_reason(reason: generated_protocol::DisposeReason) -> DisposeReason {
159    match reason {
160        generated_protocol::DisposeReason::Requested => DisposeReason::Requested,
161        generated_protocol::DisposeReason::ConnectionClosed => DisposeReason::ConnectionClosed,
162        generated_protocol::DisposeReason::HostShutdown => DisposeReason::HostShutdown,
163    }
164}
165
166fn to_generated_filesystem_operation(
167    operation: &FilesystemOperation,
168) -> generated_protocol::FilesystemOperation {
169    match operation {
170        FilesystemOperation::Read => generated_protocol::FilesystemOperation::Read,
171        FilesystemOperation::Write => generated_protocol::FilesystemOperation::Write,
172        FilesystemOperation::Stat => generated_protocol::FilesystemOperation::Stat,
173        FilesystemOperation::ReadDir => generated_protocol::FilesystemOperation::ReadDir,
174        FilesystemOperation::Mkdir => generated_protocol::FilesystemOperation::Mkdir,
175        FilesystemOperation::Remove => generated_protocol::FilesystemOperation::Remove,
176        FilesystemOperation::Rename => generated_protocol::FilesystemOperation::Rename,
177    }
178}
179
180fn from_generated_filesystem_operation(
181    operation: generated_protocol::FilesystemOperation,
182) -> FilesystemOperation {
183    match operation {
184        generated_protocol::FilesystemOperation::Read => FilesystemOperation::Read,
185        generated_protocol::FilesystemOperation::Write => FilesystemOperation::Write,
186        generated_protocol::FilesystemOperation::Stat => FilesystemOperation::Stat,
187        generated_protocol::FilesystemOperation::ReadDir => FilesystemOperation::ReadDir,
188        generated_protocol::FilesystemOperation::Mkdir => FilesystemOperation::Mkdir,
189        generated_protocol::FilesystemOperation::Remove => FilesystemOperation::Remove,
190        generated_protocol::FilesystemOperation::Rename => FilesystemOperation::Rename,
191    }
192}
193
194fn to_generated_guest_filesystem_operation(
195    operation: &GuestFilesystemOperation,
196) -> generated_protocol::GuestFilesystemOperation {
197    match operation {
198        GuestFilesystemOperation::ReadFile => {
199            generated_protocol::GuestFilesystemOperation::ReadFile
200        }
201        GuestFilesystemOperation::WriteFile => {
202            generated_protocol::GuestFilesystemOperation::WriteFile
203        }
204        GuestFilesystemOperation::CreateDir => {
205            generated_protocol::GuestFilesystemOperation::CreateDir
206        }
207        GuestFilesystemOperation::Mkdir => generated_protocol::GuestFilesystemOperation::Mkdir,
208        GuestFilesystemOperation::Exists => generated_protocol::GuestFilesystemOperation::Exists,
209        GuestFilesystemOperation::Stat => generated_protocol::GuestFilesystemOperation::Stat,
210        GuestFilesystemOperation::Lstat => generated_protocol::GuestFilesystemOperation::Lstat,
211        GuestFilesystemOperation::ReadDir => generated_protocol::GuestFilesystemOperation::ReadDir,
212        GuestFilesystemOperation::RemoveFile => {
213            generated_protocol::GuestFilesystemOperation::RemoveFile
214        }
215        GuestFilesystemOperation::RemoveDir => {
216            generated_protocol::GuestFilesystemOperation::RemoveDir
217        }
218        GuestFilesystemOperation::Rename => generated_protocol::GuestFilesystemOperation::Rename,
219        GuestFilesystemOperation::Realpath => {
220            generated_protocol::GuestFilesystemOperation::Realpath
221        }
222        GuestFilesystemOperation::Symlink => generated_protocol::GuestFilesystemOperation::Symlink,
223        GuestFilesystemOperation::ReadLink => {
224            generated_protocol::GuestFilesystemOperation::ReadLink
225        }
226        GuestFilesystemOperation::Link => generated_protocol::GuestFilesystemOperation::Link,
227        GuestFilesystemOperation::Chmod => generated_protocol::GuestFilesystemOperation::Chmod,
228        GuestFilesystemOperation::Chown => generated_protocol::GuestFilesystemOperation::Chown,
229        GuestFilesystemOperation::Utimes => generated_protocol::GuestFilesystemOperation::Utimes,
230        GuestFilesystemOperation::Truncate => {
231            generated_protocol::GuestFilesystemOperation::Truncate
232        }
233        GuestFilesystemOperation::Pread => generated_protocol::GuestFilesystemOperation::Pread,
234    }
235}
236
237fn from_generated_guest_filesystem_operation(
238    operation: generated_protocol::GuestFilesystemOperation,
239) -> GuestFilesystemOperation {
240    match operation {
241        generated_protocol::GuestFilesystemOperation::ReadFile => {
242            GuestFilesystemOperation::ReadFile
243        }
244        generated_protocol::GuestFilesystemOperation::WriteFile => {
245            GuestFilesystemOperation::WriteFile
246        }
247        generated_protocol::GuestFilesystemOperation::CreateDir => {
248            GuestFilesystemOperation::CreateDir
249        }
250        generated_protocol::GuestFilesystemOperation::Mkdir => GuestFilesystemOperation::Mkdir,
251        generated_protocol::GuestFilesystemOperation::Exists => GuestFilesystemOperation::Exists,
252        generated_protocol::GuestFilesystemOperation::Stat => GuestFilesystemOperation::Stat,
253        generated_protocol::GuestFilesystemOperation::Lstat => GuestFilesystemOperation::Lstat,
254        generated_protocol::GuestFilesystemOperation::ReadDir => GuestFilesystemOperation::ReadDir,
255        generated_protocol::GuestFilesystemOperation::RemoveFile => {
256            GuestFilesystemOperation::RemoveFile
257        }
258        generated_protocol::GuestFilesystemOperation::RemoveDir => {
259            GuestFilesystemOperation::RemoveDir
260        }
261        generated_protocol::GuestFilesystemOperation::Rename => GuestFilesystemOperation::Rename,
262        generated_protocol::GuestFilesystemOperation::Realpath => {
263            GuestFilesystemOperation::Realpath
264        }
265        generated_protocol::GuestFilesystemOperation::Symlink => GuestFilesystemOperation::Symlink,
266        generated_protocol::GuestFilesystemOperation::ReadLink => {
267            GuestFilesystemOperation::ReadLink
268        }
269        generated_protocol::GuestFilesystemOperation::Link => GuestFilesystemOperation::Link,
270        generated_protocol::GuestFilesystemOperation::Chmod => GuestFilesystemOperation::Chmod,
271        generated_protocol::GuestFilesystemOperation::Chown => GuestFilesystemOperation::Chown,
272        generated_protocol::GuestFilesystemOperation::Utimes => GuestFilesystemOperation::Utimes,
273        generated_protocol::GuestFilesystemOperation::Truncate => {
274            GuestFilesystemOperation::Truncate
275        }
276        generated_protocol::GuestFilesystemOperation::Pread => GuestFilesystemOperation::Pread,
277    }
278}
279
280fn to_generated_permission_mode(mode: &PermissionMode) -> generated_protocol::PermissionMode {
281    mode.clone()
282}
283
284fn from_generated_permission_mode(mode: generated_protocol::PermissionMode) -> PermissionMode {
285    mode
286}
287
288fn to_generated_root_filesystem_entry_encoding(
289    encoding: &RootFilesystemEntryEncoding,
290) -> generated_protocol::RootFilesystemEntryEncoding {
291    match encoding {
292        RootFilesystemEntryEncoding::Utf8 => generated_protocol::RootFilesystemEntryEncoding::Utf8,
293        RootFilesystemEntryEncoding::Base64 => {
294            generated_protocol::RootFilesystemEntryEncoding::Base64
295        }
296    }
297}
298
299fn from_generated_root_filesystem_entry_encoding(
300    encoding: generated_protocol::RootFilesystemEntryEncoding,
301) -> RootFilesystemEntryEncoding {
302    match encoding {
303        generated_protocol::RootFilesystemEntryEncoding::Utf8 => RootFilesystemEntryEncoding::Utf8,
304        generated_protocol::RootFilesystemEntryEncoding::Base64 => {
305            RootFilesystemEntryEncoding::Base64
306        }
307    }
308}
309
310fn to_generated_stream_channel(channel: &StreamChannel) -> generated_protocol::StreamChannel {
311    match channel {
312        StreamChannel::Stdout => generated_protocol::StreamChannel::Stdout,
313        StreamChannel::Stderr => generated_protocol::StreamChannel::Stderr,
314    }
315}
316
317fn from_generated_stream_channel(channel: generated_protocol::StreamChannel) -> StreamChannel {
318    match channel {
319        generated_protocol::StreamChannel::Stdout => StreamChannel::Stdout,
320        generated_protocol::StreamChannel::Stderr => StreamChannel::Stderr,
321    }
322}
323
324fn to_generated_vm_lifecycle_state(
325    state: &VmLifecycleState,
326) -> generated_protocol::VmLifecycleState {
327    match state {
328        VmLifecycleState::Creating => generated_protocol::VmLifecycleState::Creating,
329        VmLifecycleState::Ready => generated_protocol::VmLifecycleState::Ready,
330        VmLifecycleState::Disposing => generated_protocol::VmLifecycleState::Disposing,
331        VmLifecycleState::Disposed => generated_protocol::VmLifecycleState::Disposed,
332        VmLifecycleState::Failed => generated_protocol::VmLifecycleState::Failed,
333    }
334}
335
336fn from_generated_vm_lifecycle_state(
337    state: generated_protocol::VmLifecycleState,
338) -> VmLifecycleState {
339    match state {
340        generated_protocol::VmLifecycleState::Creating => VmLifecycleState::Creating,
341        generated_protocol::VmLifecycleState::Ready => VmLifecycleState::Ready,
342        generated_protocol::VmLifecycleState::Disposing => VmLifecycleState::Disposing,
343        generated_protocol::VmLifecycleState::Disposed => VmLifecycleState::Disposed,
344        generated_protocol::VmLifecycleState::Failed => VmLifecycleState::Failed,
345    }
346}
347
348fn to_generated_guest_filesystem_stat(
349    stat: &GuestFilesystemStat,
350) -> generated_protocol::GuestFilesystemStat {
351    stat.clone()
352}
353
354fn from_generated_guest_filesystem_stat(
355    stat: generated_protocol::GuestFilesystemStat,
356) -> GuestFilesystemStat {
357    stat
358}
359
360fn to_generated_process_snapshot_entry(
361    entry: &ProcessSnapshotEntry,
362) -> generated_protocol::ProcessSnapshotEntry {
363    entry.clone()
364}
365
366fn from_generated_process_snapshot_entry(
367    entry: generated_protocol::ProcessSnapshotEntry,
368) -> ProcessSnapshotEntry {
369    entry
370}
371
372fn to_generated_ext_envelope(envelope: &ExtEnvelope) -> generated_protocol::ExtEnvelope {
373    envelope.clone()
374}
375
376fn from_generated_ext_envelope(envelope: generated_protocol::ExtEnvelope) -> ExtEnvelope {
377    envelope
378}
379
380fn to_generated_request_payload(
381    payload: &RequestPayload,
382) -> Result<generated_protocol::RequestPayload, ProtocolCodecError> {
383    Ok(match payload {
384        RequestPayload::Authenticate(inner) => {
385            generated_protocol::RequestPayload::AuthenticateRequest(inner.clone())
386        }
387        RequestPayload::OpenSession(inner) => {
388            generated_protocol::RequestPayload::OpenSessionRequest(inner.clone())
389        }
390        RequestPayload::CreateVm(inner) => {
391            generated_protocol::RequestPayload::CreateVmRequest(inner.clone())
392        }
393        RequestPayload::DisposeVm(inner) => generated_protocol::RequestPayload::DisposeVmRequest(
394            generated_protocol::DisposeVmRequest {
395                reason: to_generated_dispose_reason(&inner.reason),
396            },
397        ),
398        RequestPayload::BootstrapRootFilesystem(inner) => {
399            generated_protocol::RequestPayload::BootstrapRootFilesystemRequest(inner.clone())
400        }
401        RequestPayload::ConfigureVm(inner) => {
402            generated_protocol::RequestPayload::ConfigureVmRequest(inner.clone())
403        }
404        RequestPayload::RegisterHostCallbacks(inner) => {
405            generated_protocol::RequestPayload::RegisterHostCallbacksRequest(inner.clone())
406        }
407        RequestPayload::CreateLayer(_) => generated_protocol::RequestPayload::CreateLayerRequest,
408        RequestPayload::SealLayer(inner) => {
409            generated_protocol::RequestPayload::SealLayerRequest(inner.clone())
410        }
411        RequestPayload::ImportSnapshot(inner) => {
412            generated_protocol::RequestPayload::ImportSnapshotRequest(inner.clone())
413        }
414        RequestPayload::ExportSnapshot(inner) => {
415            generated_protocol::RequestPayload::ExportSnapshotRequest(inner.clone())
416        }
417        RequestPayload::CreateOverlay(inner) => {
418            generated_protocol::RequestPayload::CreateOverlayRequest(inner.clone())
419        }
420        RequestPayload::GuestFilesystemCall(inner) => {
421            generated_protocol::RequestPayload::GuestFilesystemCallRequest(inner.clone())
422        }
423        RequestPayload::SnapshotRootFilesystem(_) => {
424            generated_protocol::RequestPayload::SnapshotRootFilesystemRequest
425        }
426        RequestPayload::Execute(inner) => {
427            generated_protocol::RequestPayload::ExecuteRequest(inner.clone())
428        }
429        RequestPayload::WriteStdin(inner) => {
430            generated_protocol::RequestPayload::WriteStdinRequest(inner.clone())
431        }
432        RequestPayload::CloseStdin(inner) => {
433            generated_protocol::RequestPayload::CloseStdinRequest(inner.clone())
434        }
435        RequestPayload::KillProcess(inner) => {
436            generated_protocol::RequestPayload::KillProcessRequest(inner.clone())
437        }
438        RequestPayload::GetProcessSnapshot(_) => {
439            generated_protocol::RequestPayload::GetProcessSnapshotRequest
440        }
441        RequestPayload::FindListener(inner) => {
442            generated_protocol::RequestPayload::FindListenerRequest(inner.clone())
443        }
444        RequestPayload::FindBoundUdp(inner) => {
445            generated_protocol::RequestPayload::FindBoundUdpRequest(inner.clone())
446        }
447        RequestPayload::VmFetch(inner) => {
448            generated_protocol::RequestPayload::VmFetchRequest(inner.clone())
449        }
450        RequestPayload::GetSignalState(inner) => {
451            generated_protocol::RequestPayload::GetSignalStateRequest(inner.clone())
452        }
453        RequestPayload::GetZombieTimerCount(_) => {
454            generated_protocol::RequestPayload::GetZombieTimerCountRequest
455        }
456        RequestPayload::HostFilesystemCall(inner) => {
457            generated_protocol::RequestPayload::HostFilesystemCallRequest(
458                generated_protocol::HostFilesystemCallRequest {
459                    operation: to_generated_filesystem_operation(&inner.operation),
460                    path: inner.path.clone(),
461                    payload_size_bytes: inner.payload_size_bytes,
462                },
463            )
464        }
465        RequestPayload::PersistenceLoad(inner) => {
466            generated_protocol::RequestPayload::PersistenceLoadRequest(inner.clone())
467        }
468        RequestPayload::PersistenceFlush(inner) => {
469            generated_protocol::RequestPayload::PersistenceFlushRequest(inner.clone())
470        }
471        RequestPayload::Ext(inner) => {
472            generated_protocol::RequestPayload::ExtEnvelope(to_generated_ext_envelope(inner))
473        }
474    })
475}
476
477fn from_generated_request_payload(
478    payload: generated_protocol::RequestPayload,
479) -> Result<RequestPayload, ProtocolCodecError> {
480    Ok(match payload {
481        generated_protocol::RequestPayload::AuthenticateRequest(inner) => {
482            RequestPayload::Authenticate(inner)
483        }
484        generated_protocol::RequestPayload::OpenSessionRequest(inner) => {
485            RequestPayload::OpenSession(inner)
486        }
487        generated_protocol::RequestPayload::CreateVmRequest(inner) => {
488            RequestPayload::CreateVm(inner)
489        }
490        generated_protocol::RequestPayload::DisposeVmRequest(inner) => {
491            RequestPayload::DisposeVm(DisposeVmRequest {
492                reason: from_generated_dispose_reason(inner.reason),
493            })
494        }
495        generated_protocol::RequestPayload::BootstrapRootFilesystemRequest(inner) => {
496            RequestPayload::BootstrapRootFilesystem(inner)
497        }
498        generated_protocol::RequestPayload::ConfigureVmRequest(inner) => {
499            RequestPayload::ConfigureVm(inner)
500        }
501        generated_protocol::RequestPayload::RegisterHostCallbacksRequest(inner) => {
502            RequestPayload::RegisterHostCallbacks(inner)
503        }
504        generated_protocol::RequestPayload::CreateLayerRequest => {
505            RequestPayload::CreateLayer(CreateLayerRequest {})
506        }
507        generated_protocol::RequestPayload::SealLayerRequest(inner) => {
508            RequestPayload::SealLayer(inner)
509        }
510        generated_protocol::RequestPayload::ImportSnapshotRequest(inner) => {
511            RequestPayload::ImportSnapshot(inner)
512        }
513        generated_protocol::RequestPayload::ExportSnapshotRequest(inner) => {
514            RequestPayload::ExportSnapshot(inner)
515        }
516        generated_protocol::RequestPayload::CreateOverlayRequest(inner) => {
517            RequestPayload::CreateOverlay(inner)
518        }
519        generated_protocol::RequestPayload::GuestFilesystemCallRequest(inner) => {
520            RequestPayload::GuestFilesystemCall(inner)
521        }
522        generated_protocol::RequestPayload::SnapshotRootFilesystemRequest => {
523            RequestPayload::SnapshotRootFilesystem(SnapshotRootFilesystemRequest {})
524        }
525        generated_protocol::RequestPayload::ExecuteRequest(inner) => RequestPayload::Execute(inner),
526        generated_protocol::RequestPayload::WriteStdinRequest(inner) => {
527            RequestPayload::WriteStdin(inner)
528        }
529        generated_protocol::RequestPayload::CloseStdinRequest(inner) => {
530            RequestPayload::CloseStdin(inner)
531        }
532        generated_protocol::RequestPayload::KillProcessRequest(inner) => {
533            RequestPayload::KillProcess(inner)
534        }
535        generated_protocol::RequestPayload::GetProcessSnapshotRequest => {
536            RequestPayload::GetProcessSnapshot(GetProcessSnapshotRequest {})
537        }
538        generated_protocol::RequestPayload::FindListenerRequest(inner) => {
539            RequestPayload::FindListener(inner)
540        }
541        generated_protocol::RequestPayload::FindBoundUdpRequest(inner) => {
542            RequestPayload::FindBoundUdp(inner)
543        }
544        generated_protocol::RequestPayload::VmFetchRequest(inner) => RequestPayload::VmFetch(inner),
545        generated_protocol::RequestPayload::GetSignalStateRequest(inner) => {
546            RequestPayload::GetSignalState(inner)
547        }
548        generated_protocol::RequestPayload::GetZombieTimerCountRequest => {
549            RequestPayload::GetZombieTimerCount(GetZombieTimerCountRequest {})
550        }
551        generated_protocol::RequestPayload::HostFilesystemCallRequest(inner) => {
552            RequestPayload::HostFilesystemCall(HostFilesystemCallRequest {
553                operation: from_generated_filesystem_operation(inner.operation),
554                path: inner.path,
555                payload_size_bytes: inner.payload_size_bytes,
556            })
557        }
558        generated_protocol::RequestPayload::PersistenceLoadRequest(inner) => {
559            RequestPayload::PersistenceLoad(inner)
560        }
561        generated_protocol::RequestPayload::PersistenceFlushRequest(inner) => {
562            RequestPayload::PersistenceFlush(inner)
563        }
564        generated_protocol::RequestPayload::ExtEnvelope(inner) => {
565            RequestPayload::Ext(from_generated_ext_envelope(inner))
566        }
567    })
568}
569
570fn to_generated_response_payload(
571    payload: &ResponsePayload,
572) -> Result<generated_protocol::ResponsePayload, ProtocolCodecError> {
573    Ok(match payload {
574        ResponsePayload::Authenticated(inner) => {
575            generated_protocol::ResponsePayload::AuthenticatedResponse(inner.clone())
576        }
577        ResponsePayload::SessionOpened(inner) => {
578            generated_protocol::ResponsePayload::SessionOpenedResponse(inner.clone())
579        }
580        ResponsePayload::VmCreated(inner) => {
581            generated_protocol::ResponsePayload::VmCreatedResponse(inner.clone())
582        }
583        ResponsePayload::VmDisposed(inner) => {
584            generated_protocol::ResponsePayload::VmDisposedResponse(inner.clone())
585        }
586        ResponsePayload::RootFilesystemBootstrapped(inner) => {
587            generated_protocol::ResponsePayload::RootFilesystemBootstrappedResponse(inner.clone())
588        }
589        ResponsePayload::VmConfigured(inner) => {
590            generated_protocol::ResponsePayload::VmConfiguredResponse(inner.clone())
591        }
592        ResponsePayload::HostCallbacksRegistered(inner) => {
593            generated_protocol::ResponsePayload::HostCallbacksRegisteredResponse(inner.clone())
594        }
595        ResponsePayload::LayerCreated(inner) => {
596            generated_protocol::ResponsePayload::LayerCreatedResponse(inner.clone())
597        }
598        ResponsePayload::LayerSealed(inner) => {
599            generated_protocol::ResponsePayload::LayerSealedResponse(inner.clone())
600        }
601        ResponsePayload::SnapshotImported(inner) => {
602            generated_protocol::ResponsePayload::SnapshotImportedResponse(inner.clone())
603        }
604        ResponsePayload::SnapshotExported(inner) => {
605            generated_protocol::ResponsePayload::SnapshotExportedResponse(
606                generated_protocol::SnapshotExportedResponse {
607                    layer_id: inner.layer_id.clone(),
608                    entries: inner.entries.clone(),
609                },
610            )
611        }
612        ResponsePayload::OverlayCreated(inner) => {
613            generated_protocol::ResponsePayload::OverlayCreatedResponse(inner.clone())
614        }
615        ResponsePayload::GuestFilesystemResult(inner) => {
616            generated_protocol::ResponsePayload::GuestFilesystemResultResponse(
617                generated_protocol::GuestFilesystemResultResponse {
618                    operation: to_generated_guest_filesystem_operation(&inner.operation),
619                    path: inner.path.clone(),
620                    content: inner.content.clone(),
621                    encoding: inner
622                        .encoding
623                        .as_ref()
624                        .map(to_generated_root_filesystem_entry_encoding),
625                    entries: inner.entries.clone(),
626                    stat: inner.stat.as_ref().map(to_generated_guest_filesystem_stat),
627                    exists: inner.exists,
628                    target: inner.target.clone(),
629                },
630            )
631        }
632        ResponsePayload::RootFilesystemSnapshot(inner) => {
633            generated_protocol::ResponsePayload::RootFilesystemSnapshotResponse(
634                generated_protocol::RootFilesystemSnapshotResponse {
635                    entries: inner.entries.clone(),
636                },
637            )
638        }
639        ResponsePayload::ProcessStarted(inner) => {
640            generated_protocol::ResponsePayload::ProcessStartedResponse(inner.clone())
641        }
642        ResponsePayload::StdinWritten(inner) => {
643            generated_protocol::ResponsePayload::StdinWrittenResponse(inner.clone())
644        }
645        ResponsePayload::StdinClosed(inner) => {
646            generated_protocol::ResponsePayload::StdinClosedResponse(inner.clone())
647        }
648        ResponsePayload::ProcessKilled(inner) => {
649            generated_protocol::ResponsePayload::ProcessKilledResponse(inner.clone())
650        }
651        ResponsePayload::ProcessSnapshot(inner) => {
652            generated_protocol::ResponsePayload::ProcessSnapshotResponse(
653                generated_protocol::ProcessSnapshotResponse {
654                    processes: inner
655                        .processes
656                        .iter()
657                        .map(to_generated_process_snapshot_entry)
658                        .collect(),
659                },
660            )
661        }
662        ResponsePayload::ListenerSnapshot(inner) => {
663            generated_protocol::ResponsePayload::ListenerSnapshotResponse(inner.clone())
664        }
665        ResponsePayload::BoundUdpSnapshot(inner) => {
666            generated_protocol::ResponsePayload::BoundUdpSnapshotResponse(inner.clone())
667        }
668        ResponsePayload::VmFetchResult(inner) => {
669            generated_protocol::ResponsePayload::VmFetchResponse(inner.clone())
670        }
671        ResponsePayload::SignalState(inner) => {
672            generated_protocol::ResponsePayload::SignalStateResponse(inner.clone())
673        }
674        ResponsePayload::ZombieTimerCount(inner) => {
675            generated_protocol::ResponsePayload::ZombieTimerCountResponse(inner.clone())
676        }
677        ResponsePayload::FilesystemResult(inner) => {
678            generated_protocol::ResponsePayload::FilesystemResultResponse(
679                generated_protocol::FilesystemResultResponse {
680                    operation: to_generated_filesystem_operation(&inner.operation),
681                    status: inner.status.clone(),
682                    payload_size_bytes: inner.payload_size_bytes,
683                },
684            )
685        }
686        ResponsePayload::PermissionDecision(inner) => {
687            generated_protocol::ResponsePayload::PermissionDecisionResponse(
688                generated_protocol::PermissionDecisionResponse {
689                    capability: inner.capability.clone(),
690                    decision: to_generated_permission_mode(&inner.decision),
691                },
692            )
693        }
694        ResponsePayload::PersistenceState(inner) => {
695            generated_protocol::ResponsePayload::PersistenceStateResponse(inner.clone())
696        }
697        ResponsePayload::PersistenceFlushed(inner) => {
698            generated_protocol::ResponsePayload::PersistenceFlushedResponse(inner.clone())
699        }
700        ResponsePayload::Rejected(inner) => {
701            generated_protocol::ResponsePayload::RejectedResponse(inner.clone())
702        }
703        ResponsePayload::ExtResult(inner) => {
704            generated_protocol::ResponsePayload::ExtEnvelope(to_generated_ext_envelope(inner))
705        }
706    })
707}
708
709fn from_generated_response_payload(
710    payload: generated_protocol::ResponsePayload,
711) -> Result<ResponsePayload, ProtocolCodecError> {
712    Ok(match payload {
713        generated_protocol::ResponsePayload::AuthenticatedResponse(inner) => {
714            ResponsePayload::Authenticated(inner)
715        }
716        generated_protocol::ResponsePayload::SessionOpenedResponse(inner) => {
717            ResponsePayload::SessionOpened(inner)
718        }
719        generated_protocol::ResponsePayload::VmCreatedResponse(inner) => {
720            ResponsePayload::VmCreated(inner)
721        }
722        generated_protocol::ResponsePayload::VmDisposedResponse(inner) => {
723            ResponsePayload::VmDisposed(inner)
724        }
725        generated_protocol::ResponsePayload::RootFilesystemBootstrappedResponse(inner) => {
726            ResponsePayload::RootFilesystemBootstrapped(inner)
727        }
728        generated_protocol::ResponsePayload::VmConfiguredResponse(inner) => {
729            ResponsePayload::VmConfigured(inner)
730        }
731        generated_protocol::ResponsePayload::HostCallbacksRegisteredResponse(inner) => {
732            ResponsePayload::HostCallbacksRegistered(inner)
733        }
734        generated_protocol::ResponsePayload::LayerCreatedResponse(inner) => {
735            ResponsePayload::LayerCreated(inner)
736        }
737        generated_protocol::ResponsePayload::LayerSealedResponse(inner) => {
738            ResponsePayload::LayerSealed(inner)
739        }
740        generated_protocol::ResponsePayload::SnapshotImportedResponse(inner) => {
741            ResponsePayload::SnapshotImported(inner)
742        }
743        generated_protocol::ResponsePayload::SnapshotExportedResponse(inner) => {
744            ResponsePayload::SnapshotExported(SnapshotExportedResponse {
745                layer_id: inner.layer_id,
746                entries: inner.entries,
747            })
748        }
749        generated_protocol::ResponsePayload::OverlayCreatedResponse(inner) => {
750            ResponsePayload::OverlayCreated(inner)
751        }
752        generated_protocol::ResponsePayload::GuestFilesystemResultResponse(inner) => {
753            ResponsePayload::GuestFilesystemResult(GuestFilesystemResultResponse {
754                operation: from_generated_guest_filesystem_operation(inner.operation),
755                path: inner.path,
756                content: inner.content,
757                encoding: inner
758                    .encoding
759                    .map(from_generated_root_filesystem_entry_encoding),
760                entries: inner.entries,
761                stat: inner.stat.map(from_generated_guest_filesystem_stat),
762                exists: inner.exists,
763                target: inner.target,
764            })
765        }
766        generated_protocol::ResponsePayload::RootFilesystemSnapshotResponse(inner) => {
767            ResponsePayload::RootFilesystemSnapshot(RootFilesystemSnapshotResponse {
768                entries: inner.entries,
769            })
770        }
771        generated_protocol::ResponsePayload::ProcessStartedResponse(inner) => {
772            ResponsePayload::ProcessStarted(inner)
773        }
774        generated_protocol::ResponsePayload::StdinWrittenResponse(inner) => {
775            ResponsePayload::StdinWritten(inner)
776        }
777        generated_protocol::ResponsePayload::StdinClosedResponse(inner) => {
778            ResponsePayload::StdinClosed(inner)
779        }
780        generated_protocol::ResponsePayload::ProcessKilledResponse(inner) => {
781            ResponsePayload::ProcessKilled(inner)
782        }
783        generated_protocol::ResponsePayload::ProcessSnapshotResponse(inner) => {
784            ResponsePayload::ProcessSnapshot(ProcessSnapshotResponse {
785                processes: inner
786                    .processes
787                    .into_iter()
788                    .map(from_generated_process_snapshot_entry)
789                    .collect(),
790            })
791        }
792        generated_protocol::ResponsePayload::ListenerSnapshotResponse(inner) => {
793            ResponsePayload::ListenerSnapshot(inner)
794        }
795        generated_protocol::ResponsePayload::BoundUdpSnapshotResponse(inner) => {
796            ResponsePayload::BoundUdpSnapshot(inner)
797        }
798        generated_protocol::ResponsePayload::VmFetchResponse(inner) => {
799            ResponsePayload::VmFetchResult(inner)
800        }
801        generated_protocol::ResponsePayload::SignalStateResponse(inner) => {
802            ResponsePayload::SignalState(inner)
803        }
804        generated_protocol::ResponsePayload::ZombieTimerCountResponse(inner) => {
805            ResponsePayload::ZombieTimerCount(inner)
806        }
807        generated_protocol::ResponsePayload::FilesystemResultResponse(inner) => {
808            ResponsePayload::FilesystemResult(FilesystemResultResponse {
809                operation: from_generated_filesystem_operation(inner.operation),
810                status: inner.status,
811                payload_size_bytes: inner.payload_size_bytes,
812            })
813        }
814        generated_protocol::ResponsePayload::PermissionDecisionResponse(inner) => {
815            ResponsePayload::PermissionDecision(PermissionDecisionResponse {
816                capability: inner.capability,
817                decision: from_generated_permission_mode(inner.decision),
818            })
819        }
820        generated_protocol::ResponsePayload::PersistenceStateResponse(inner) => {
821            ResponsePayload::PersistenceState(inner)
822        }
823        generated_protocol::ResponsePayload::PersistenceFlushedResponse(inner) => {
824            ResponsePayload::PersistenceFlushed(inner)
825        }
826        generated_protocol::ResponsePayload::RejectedResponse(inner) => {
827            ResponsePayload::Rejected(inner)
828        }
829        generated_protocol::ResponsePayload::ExtEnvelope(inner) => {
830            ResponsePayload::ExtResult(from_generated_ext_envelope(inner))
831        }
832    })
833}
834
835fn to_generated_event_payload(payload: &EventPayload) -> generated_protocol::EventPayload {
836    match payload {
837        EventPayload::VmLifecycle(inner) => generated_protocol::EventPayload::VmLifecycleEvent(
838            generated_protocol::VmLifecycleEvent {
839                state: to_generated_vm_lifecycle_state(&inner.state),
840            },
841        ),
842        EventPayload::ProcessOutput(inner) => generated_protocol::EventPayload::ProcessOutputEvent(
843            generated_protocol::ProcessOutputEvent {
844                process_id: inner.process_id.clone(),
845                channel: to_generated_stream_channel(&inner.channel),
846                chunk: inner.chunk.clone(),
847            },
848        ),
849        EventPayload::ProcessExited(inner) => {
850            generated_protocol::EventPayload::ProcessExitedEvent(inner.clone())
851        }
852        EventPayload::Structured(inner) => {
853            generated_protocol::EventPayload::StructuredEvent(inner.clone())
854        }
855        EventPayload::Ext(inner) => {
856            generated_protocol::EventPayload::ExtEnvelope(to_generated_ext_envelope(inner))
857        }
858    }
859}
860
861fn from_generated_event_payload(payload: generated_protocol::EventPayload) -> EventPayload {
862    match payload {
863        generated_protocol::EventPayload::VmLifecycleEvent(inner) => {
864            EventPayload::VmLifecycle(VmLifecycleEvent {
865                state: from_generated_vm_lifecycle_state(inner.state),
866            })
867        }
868        generated_protocol::EventPayload::ProcessOutputEvent(inner) => {
869            EventPayload::ProcessOutput(ProcessOutputEvent {
870                process_id: inner.process_id,
871                channel: from_generated_stream_channel(inner.channel),
872                chunk: inner.chunk,
873            })
874        }
875        generated_protocol::EventPayload::ProcessExitedEvent(inner) => {
876            EventPayload::ProcessExited(inner)
877        }
878        generated_protocol::EventPayload::StructuredEvent(inner) => EventPayload::Structured(inner),
879        generated_protocol::EventPayload::ExtEnvelope(inner) => {
880            EventPayload::Ext(from_generated_ext_envelope(inner))
881        }
882    }
883}
884
885fn to_generated_sidecar_request_payload(
886    payload: &SidecarRequestPayload,
887) -> Result<generated_protocol::SidecarRequestPayload, ProtocolCodecError> {
888    Ok(match payload {
889        SidecarRequestPayload::HostCallback(inner) => {
890            generated_protocol::SidecarRequestPayload::HostCallbackRequest(inner.clone())
891        }
892        SidecarRequestPayload::JsBridgeCall(inner) => {
893            generated_protocol::SidecarRequestPayload::JsBridgeCallRequest(inner.clone())
894        }
895        SidecarRequestPayload::Ext(inner) => {
896            generated_protocol::SidecarRequestPayload::ExtEnvelope(to_generated_ext_envelope(inner))
897        }
898    })
899}
900
901fn from_generated_sidecar_request_payload(
902    payload: generated_protocol::SidecarRequestPayload,
903) -> Result<SidecarRequestPayload, ProtocolCodecError> {
904    Ok(match payload {
905        generated_protocol::SidecarRequestPayload::HostCallbackRequest(inner) => {
906            SidecarRequestPayload::HostCallback(inner)
907        }
908        generated_protocol::SidecarRequestPayload::JsBridgeCallRequest(inner) => {
909            SidecarRequestPayload::JsBridgeCall(inner)
910        }
911        generated_protocol::SidecarRequestPayload::ExtEnvelope(inner) => {
912            SidecarRequestPayload::Ext(from_generated_ext_envelope(inner))
913        }
914    })
915}
916
917fn to_generated_sidecar_response_payload(
918    payload: &SidecarResponsePayload,
919) -> Result<generated_protocol::SidecarResponsePayload, ProtocolCodecError> {
920    Ok(match payload {
921        SidecarResponsePayload::HostCallbackResult(inner) => {
922            generated_protocol::SidecarResponsePayload::HostCallbackResultResponse(inner.clone())
923        }
924        SidecarResponsePayload::JsBridgeResult(inner) => {
925            generated_protocol::SidecarResponsePayload::JsBridgeResultResponse(inner.clone())
926        }
927        SidecarResponsePayload::ExtResult(inner) => {
928            generated_protocol::SidecarResponsePayload::ExtEnvelope(to_generated_ext_envelope(
929                inner,
930            ))
931        }
932    })
933}
934
935fn from_generated_sidecar_response_payload(
936    payload: generated_protocol::SidecarResponsePayload,
937) -> Result<SidecarResponsePayload, ProtocolCodecError> {
938    Ok(match payload {
939        generated_protocol::SidecarResponsePayload::HostCallbackResultResponse(inner) => {
940            SidecarResponsePayload::HostCallbackResult(inner)
941        }
942        generated_protocol::SidecarResponsePayload::JsBridgeResultResponse(inner) => {
943            SidecarResponsePayload::JsBridgeResult(inner)
944        }
945        generated_protocol::SidecarResponsePayload::ExtEnvelope(inner) => {
946            SidecarResponsePayload::ExtResult(from_generated_ext_envelope(inner))
947        }
948    })
949}
950
951macro_rules! impl_bare_newtype_union_enum {
952    (
953        $name:ident,
954        $json_name:ident,
955        $(#[$json_attr:meta])*
956        {
957            $($variant:ident($ty:ty) = $tag:literal),+ $(,)?
958        }
959    ) => {
960        #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
961        $(#[$json_attr])*
962        enum $json_name {
963            $($variant($ty)),+
964        }
965
966        impl From<&$name> for $json_name {
967            fn from(value: &$name) -> Self {
968                match value {
969                    $($name::$variant(inner) => Self::$variant(inner.clone()),)+
970                }
971            }
972        }
973
974        impl From<$json_name> for $name {
975            fn from(value: $json_name) -> Self {
976                match value {
977                    $($json_name::$variant(inner) => Self::$variant(inner),)+
978                }
979            }
980        }
981
982        impl Serialize for $name {
983            fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
984            where
985                S: Serializer,
986            {
987                if serializer.is_human_readable() {
988                    $json_name::from(self).serialize(serializer)
989                } else {
990                    match self {
991                        $(Self::$variant(inner) => serialize_bare_newtype_tag(serializer, $tag, inner),)+
992                    }
993                }
994            }
995        }
996
997        impl<'de> Deserialize<'de> for $name {
998            fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
999            where
1000                D: Deserializer<'de>,
1001            {
1002                if deserializer.is_human_readable() {
1003                    Ok($json_name::deserialize(deserializer)?.into())
1004                } else {
1005                    struct UnionVisitor;
1006
1007                    impl<'de> Visitor<'de> for UnionVisitor {
1008                        type Value = $name;
1009
1010                        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1011                            write!(formatter, "a {} BARE union", stringify!($name))
1012                        }
1013
1014                        fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
1015                        where
1016                            A: SeqAccess<'de>,
1017                        {
1018                            let serde_bare::Uint(tag) = seq
1019                                .next_element()?
1020                                .ok_or_else(|| de::Error::custom(concat!("missing ", stringify!($name), " tag")))?;
1021                            match tag {
1022                                $(
1023                                    $tag => {
1024                                        let payload = seq.next_element::<$ty>()?.ok_or_else(|| {
1025                                            de::Error::custom(format!(
1026                                                "missing {} payload for tag {}",
1027                                                stringify!($variant),
1028                                                $tag
1029                                            ))
1030                                        })?;
1031                                        Ok($name::$variant(payload))
1032                                    }
1033                                )+
1034                                _ => Err(de::Error::custom(format!(
1035                                    "unknown {} tag: {}",
1036                                    stringify!($name),
1037                                    tag
1038                                ))),
1039                            }
1040                        }
1041                    }
1042
1043                    deserializer.deserialize_tuple(2, UnionVisitor)
1044                }
1045            }
1046        }
1047    };
1048}
1049
1050pub type ProtocolSchema = crate::wire::ProtocolSchema;
1051
1052pub type OwnershipScope = crate::wire::OwnershipScope;
1053
1054#[derive(Debug, Clone, PartialEq, Eq)]
1055pub enum ProtocolFrame {
1056    Request(RequestFrame),
1057    Response(ResponseFrame),
1058    Event(EventFrame),
1059    SidecarRequest(SidecarRequestFrame),
1060    SidecarResponse(SidecarResponseFrame),
1061}
1062
1063#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1064pub struct RequestFrame {
1065    pub schema: ProtocolSchema,
1066    pub request_id: RequestId,
1067    pub ownership: OwnershipScope,
1068    pub payload: RequestPayload,
1069}
1070
1071impl RequestFrame {
1072    pub fn new(request_id: RequestId, ownership: OwnershipScope, payload: RequestPayload) -> Self {
1073        Self {
1074            schema: ProtocolSchema::current(),
1075            request_id,
1076            ownership,
1077            payload,
1078        }
1079    }
1080}
1081
1082#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1083pub struct ResponseFrame {
1084    pub schema: ProtocolSchema,
1085    pub request_id: RequestId,
1086    pub ownership: OwnershipScope,
1087    pub payload: ResponsePayload,
1088}
1089
1090impl ResponseFrame {
1091    pub fn new(request_id: RequestId, ownership: OwnershipScope, payload: ResponsePayload) -> Self {
1092        Self {
1093            schema: ProtocolSchema::current(),
1094            request_id,
1095            ownership,
1096            payload,
1097        }
1098    }
1099}
1100
1101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1102pub struct SidecarRequestFrame {
1103    pub schema: ProtocolSchema,
1104    pub request_id: RequestId,
1105    pub ownership: OwnershipScope,
1106    pub payload: SidecarRequestPayload,
1107}
1108
1109impl SidecarRequestFrame {
1110    pub fn new(
1111        request_id: RequestId,
1112        ownership: OwnershipScope,
1113        payload: SidecarRequestPayload,
1114    ) -> Self {
1115        Self {
1116            schema: ProtocolSchema::current(),
1117            request_id,
1118            ownership,
1119            payload,
1120        }
1121    }
1122}
1123
1124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1125pub struct SidecarResponseFrame {
1126    pub schema: ProtocolSchema,
1127    pub request_id: RequestId,
1128    pub ownership: OwnershipScope,
1129    pub payload: SidecarResponsePayload,
1130}
1131
1132impl SidecarResponseFrame {
1133    pub fn new(
1134        request_id: RequestId,
1135        ownership: OwnershipScope,
1136        payload: SidecarResponsePayload,
1137    ) -> Self {
1138        Self {
1139            schema: ProtocolSchema::current(),
1140            request_id,
1141            ownership,
1142            payload,
1143        }
1144    }
1145}
1146
1147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1148pub struct EventFrame {
1149    pub schema: ProtocolSchema,
1150    pub ownership: OwnershipScope,
1151    pub payload: EventPayload,
1152}
1153
1154impl EventFrame {
1155    pub fn new(ownership: OwnershipScope, payload: EventPayload) -> Self {
1156        Self {
1157            schema: ProtocolSchema::current(),
1158            ownership,
1159            payload,
1160        }
1161    }
1162}
1163
1164#[derive(Debug, Clone, PartialEq, Eq)]
1165pub enum RequestPayload {
1166    Authenticate(AuthenticateRequest),
1167    OpenSession(OpenSessionRequest),
1168    CreateVm(CreateVmRequest),
1169    DisposeVm(DisposeVmRequest),
1170    BootstrapRootFilesystem(BootstrapRootFilesystemRequest),
1171    ConfigureVm(ConfigureVmRequest),
1172    RegisterHostCallbacks(RegisterHostCallbacksRequest),
1173    CreateLayer(CreateLayerRequest),
1174    SealLayer(SealLayerRequest),
1175    ImportSnapshot(ImportSnapshotRequest),
1176    ExportSnapshot(ExportSnapshotRequest),
1177    CreateOverlay(CreateOverlayRequest),
1178    GuestFilesystemCall(GuestFilesystemCallRequest),
1179    SnapshotRootFilesystem(SnapshotRootFilesystemRequest),
1180    Execute(ExecuteRequest),
1181    WriteStdin(WriteStdinRequest),
1182    CloseStdin(CloseStdinRequest),
1183    KillProcess(KillProcessRequest),
1184    GetProcessSnapshot(GetProcessSnapshotRequest),
1185    FindListener(FindListenerRequest),
1186    FindBoundUdp(FindBoundUdpRequest),
1187    VmFetch(VmFetchRequest),
1188    GetSignalState(GetSignalStateRequest),
1189    GetZombieTimerCount(GetZombieTimerCountRequest),
1190    HostFilesystemCall(HostFilesystemCallRequest),
1191    PersistenceLoad(PersistenceLoadRequest),
1192    PersistenceFlush(PersistenceFlushRequest),
1193    Ext(ExtEnvelope),
1194}
1195
1196#[derive(Debug, Clone, PartialEq, Eq)]
1197pub enum ResponsePayload {
1198    Authenticated(AuthenticatedResponse),
1199    SessionOpened(SessionOpenedResponse),
1200    VmCreated(VmCreatedResponse),
1201    VmDisposed(VmDisposedResponse),
1202    RootFilesystemBootstrapped(RootFilesystemBootstrappedResponse),
1203    VmConfigured(VmConfiguredResponse),
1204    HostCallbacksRegistered(HostCallbacksRegisteredResponse),
1205    LayerCreated(LayerCreatedResponse),
1206    LayerSealed(LayerSealedResponse),
1207    SnapshotImported(SnapshotImportedResponse),
1208    SnapshotExported(SnapshotExportedResponse),
1209    OverlayCreated(OverlayCreatedResponse),
1210    GuestFilesystemResult(GuestFilesystemResultResponse),
1211    RootFilesystemSnapshot(RootFilesystemSnapshotResponse),
1212    ProcessStarted(ProcessStartedResponse),
1213    StdinWritten(StdinWrittenResponse),
1214    StdinClosed(StdinClosedResponse),
1215    ProcessKilled(ProcessKilledResponse),
1216    ProcessSnapshot(ProcessSnapshotResponse),
1217    ListenerSnapshot(ListenerSnapshotResponse),
1218    BoundUdpSnapshot(BoundUdpSnapshotResponse),
1219    VmFetchResult(VmFetchResponse),
1220    SignalState(SignalStateResponse),
1221    ZombieTimerCount(ZombieTimerCountResponse),
1222    FilesystemResult(FilesystemResultResponse),
1223    PermissionDecision(PermissionDecisionResponse),
1224    PersistenceState(PersistenceStateResponse),
1225    PersistenceFlushed(PersistenceFlushedResponse),
1226    Rejected(RejectedResponse),
1227    ExtResult(ExtEnvelope),
1228}
1229
1230#[derive(Debug, Clone, PartialEq, Eq)]
1231pub enum SidecarRequestPayload {
1232    HostCallback(HostCallbackRequest),
1233    JsBridgeCall(JsBridgeCallRequest),
1234    Ext(ExtEnvelope),
1235}
1236
1237#[derive(Debug, Clone, PartialEq, Eq)]
1238pub enum SidecarResponsePayload {
1239    HostCallbackResult(HostCallbackResultResponse),
1240    JsBridgeResult(JsBridgeResultResponse),
1241    ExtResult(ExtEnvelope),
1242}
1243
1244#[derive(Debug, Clone, PartialEq, Eq)]
1245pub enum EventPayload {
1246    VmLifecycle(VmLifecycleEvent),
1247    ProcessOutput(ProcessOutputEvent),
1248    ProcessExited(ProcessExitedEvent),
1249    Structured(StructuredEvent),
1250    Ext(ExtEnvelope),
1251}
1252
1253pub type SidecarPlacement = crate::wire::SidecarPlacement;
1254
1255pub type SidecarPlacementShared = crate::wire::SidecarPlacementShared;
1256
1257pub type SidecarPlacementExplicit = crate::wire::SidecarPlacementExplicit;
1258
1259pub type GuestRuntimeKind = crate::wire::GuestRuntimeKind;
1260
1261pub type DisposeReason = crate::wire::DisposeReason;
1262
1263pub type FilesystemOperation = crate::wire::FilesystemOperation;
1264
1265pub type GuestFilesystemOperation = crate::wire::GuestFilesystemOperation;
1266
1267pub type PermissionMode = crate::wire::PermissionMode;
1268
1269pub type FsPermissionRule = crate::wire::FsPermissionRule;
1270
1271pub type PatternPermissionRule = crate::wire::PatternPermissionRule;
1272
1273pub type FsPermissionRuleSet = crate::wire::FsPermissionRuleSet;
1274
1275pub type PatternPermissionRuleSet = crate::wire::PatternPermissionRuleSet;
1276
1277pub type FsPermissionScope = crate::wire::FsPermissionScope;
1278
1279pub type PatternPermissionScope = crate::wire::PatternPermissionScope;
1280
1281pub type PermissionsPolicy = crate::wire::PermissionsPolicy;
1282
1283pub type RootFilesystemEntryKind = crate::wire::RootFilesystemEntryKind;
1284
1285pub type RootFilesystemMode = crate::wire::RootFilesystemMode;
1286
1287pub type RootFilesystemLowerDescriptor = crate::wire::RootFilesystemLowerDescriptor;
1288
1289pub type SnapshotRootFilesystemLower = crate::wire::SnapshotRootFilesystemLower;
1290
1291pub type StreamChannel = crate::wire::StreamChannel;
1292
1293pub type VmLifecycleState = crate::wire::VmLifecycleState;
1294
1295pub type AuthenticateRequest = crate::wire::AuthenticateRequest;
1296
1297pub type OpenSessionRequest = crate::wire::OpenSessionRequest;
1298
1299pub type CreateVmRequest = crate::wire::CreateVmRequest;
1300
1301#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1302pub struct DisposeVmRequest {
1303    pub reason: DisposeReason,
1304}
1305
1306pub type BootstrapRootFilesystemRequest = crate::wire::BootstrapRootFilesystemRequest;
1307
1308pub type RootFilesystemDescriptor = crate::wire::RootFilesystemDescriptor;
1309
1310pub type RootFilesystemEntryEncoding = crate::wire::RootFilesystemEntryEncoding;
1311
1312pub type RootFilesystemEntry = crate::wire::RootFilesystemEntry;
1313
1314pub type ConfigureVmRequest = crate::wire::ConfigureVmRequest;
1315
1316#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
1317pub struct CreateLayerRequest {}
1318
1319pub type SealLayerRequest = crate::wire::SealLayerRequest;
1320
1321pub type ImportSnapshotRequest = crate::wire::ImportSnapshotRequest;
1322
1323pub type ExportSnapshotRequest = crate::wire::ExportSnapshotRequest;
1324
1325pub type CreateOverlayRequest = crate::wire::CreateOverlayRequest;
1326
1327pub type GuestFilesystemCallRequest = crate::wire::GuestFilesystemCallRequest;
1328
1329#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
1330pub struct SnapshotRootFilesystemRequest {}
1331
1332pub type MountDescriptor = crate::wire::MountDescriptor;
1333
1334pub type MountPluginDescriptor = crate::wire::MountPluginDescriptor;
1335
1336pub type SoftwareDescriptor = crate::wire::SoftwareDescriptor;
1337
1338pub type ProjectedModuleDescriptor = crate::wire::ProjectedModuleDescriptor;
1339
1340pub type WasmPermissionTier = crate::wire::WasmPermissionTier;
1341
1342pub type ExecuteRequest = crate::wire::ExecuteRequest;
1343
1344pub type WriteStdinRequest = crate::wire::WriteStdinRequest;
1345
1346pub type CloseStdinRequest = crate::wire::CloseStdinRequest;
1347
1348pub type KillProcessRequest = crate::wire::KillProcessRequest;
1349
1350#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
1351pub struct GetProcessSnapshotRequest {}
1352
1353pub type FindListenerRequest = crate::wire::FindListenerRequest;
1354
1355pub type FindBoundUdpRequest = crate::wire::FindBoundUdpRequest;
1356
1357pub type VmFetchRequest = crate::wire::VmFetchRequest;
1358
1359pub type GetSignalStateRequest = crate::wire::GetSignalStateRequest;
1360
1361#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
1362pub struct GetZombieTimerCountRequest {}
1363
1364pub type HostFilesystemCallRequest = crate::wire::HostFilesystemCallRequest;
1365
1366pub type PersistenceLoadRequest = crate::wire::PersistenceLoadRequest;
1367
1368pub type PersistenceFlushRequest = crate::wire::PersistenceFlushRequest;
1369
1370pub type RegisterHostCallbacksRequest = crate::wire::RegisterHostCallbacksRequest;
1371
1372pub type RegisteredHostCallbackDefinition = crate::wire::RegisteredHostCallbackDefinition;
1373
1374pub type RegisteredHostCallbackExample = crate::wire::RegisteredHostCallbackExample;
1375
1376pub type HostCallbackRequest = crate::wire::HostCallbackRequest;
1377
1378pub type JsBridgeCallRequest = crate::wire::JsBridgeCallRequest;
1379
1380pub type AuthenticatedResponse = crate::wire::AuthenticatedResponse;
1381
1382pub type SessionOpenedResponse = crate::wire::SessionOpenedResponse;
1383
1384pub type VmCreatedResponse = crate::wire::VmCreatedResponse;
1385
1386pub type VmDisposedResponse = crate::wire::VmDisposedResponse;
1387
1388pub type RootFilesystemBootstrappedResponse = crate::wire::RootFilesystemBootstrappedResponse;
1389
1390pub type VmConfiguredResponse = crate::wire::VmConfiguredResponse;
1391
1392pub type HostCallbacksRegisteredResponse = crate::wire::HostCallbacksRegisteredResponse;
1393
1394pub type GuestFilesystemStat = crate::wire::GuestFilesystemStat;
1395
1396pub type GuestFilesystemResultResponse = crate::wire::GuestFilesystemResultResponse;
1397
1398pub type RootFilesystemSnapshotResponse = crate::wire::RootFilesystemSnapshotResponse;
1399
1400pub type LayerCreatedResponse = crate::wire::LayerCreatedResponse;
1401
1402pub type LayerSealedResponse = crate::wire::LayerSealedResponse;
1403
1404pub type SnapshotImportedResponse = crate::wire::SnapshotImportedResponse;
1405
1406pub type SnapshotExportedResponse = crate::wire::SnapshotExportedResponse;
1407
1408pub type OverlayCreatedResponse = crate::wire::OverlayCreatedResponse;
1409
1410pub type ProcessStartedResponse = crate::wire::ProcessStartedResponse;
1411
1412pub type StdinWrittenResponse = crate::wire::StdinWrittenResponse;
1413
1414pub type StdinClosedResponse = crate::wire::StdinClosedResponse;
1415
1416pub type ProcessKilledResponse = crate::wire::ProcessKilledResponse;
1417
1418pub type ProcessSnapshotStatus = crate::wire::ProcessSnapshotStatus;
1419
1420pub type ProcessSnapshotEntry = crate::wire::ProcessSnapshotEntry;
1421
1422pub type ProcessSnapshotResponse = crate::wire::ProcessSnapshotResponse;
1423
1424pub type SocketStateEntry = crate::wire::SocketStateEntry;
1425
1426pub type ListenerSnapshotResponse = crate::wire::ListenerSnapshotResponse;
1427
1428pub type BoundUdpSnapshotResponse = crate::wire::BoundUdpSnapshotResponse;
1429
1430pub type VmFetchResponse = crate::wire::VmFetchResponse;
1431
1432pub type SignalDispositionAction = crate::wire::SignalDispositionAction;
1433
1434pub type SignalHandlerRegistration = crate::wire::SignalHandlerRegistration;
1435
1436pub type SignalStateResponse = crate::wire::SignalStateResponse;
1437
1438pub type ZombieTimerCountResponse = crate::wire::ZombieTimerCountResponse;
1439
1440pub type FilesystemResultResponse = crate::wire::FilesystemResultResponse;
1441
1442pub type PermissionDecisionResponse = crate::wire::PermissionDecisionResponse;
1443
1444pub type PersistenceStateResponse = crate::wire::PersistenceStateResponse;
1445
1446pub type PersistenceFlushedResponse = crate::wire::PersistenceFlushedResponse;
1447
1448pub type HostCallbackResultResponse = crate::wire::HostCallbackResultResponse;
1449
1450pub type JsBridgeResultResponse = crate::wire::JsBridgeResultResponse;
1451
1452pub type RejectedResponse = crate::wire::RejectedResponse;
1453
1454pub type VmLifecycleEvent = crate::wire::VmLifecycleEvent;
1455
1456pub type ProcessOutputEvent = crate::wire::ProcessOutputEvent;
1457
1458pub type ProcessExitedEvent = crate::wire::ProcessExitedEvent;
1459
1460pub type StructuredEvent = crate::wire::StructuredEvent;
1461
1462impl_bare_newtype_union_enum!(
1463    ProtocolFrame,
1464    JsonProtocolFrame,
1465    #[serde(tag = "frame_type", rename_all = "snake_case")]
1466    {
1467        Request(RequestFrame) = 0,
1468        Response(ResponseFrame) = 1,
1469        Event(EventFrame) = 2,
1470        SidecarRequest(SidecarRequestFrame) = 3,
1471        SidecarResponse(SidecarResponseFrame) = 4,
1472    }
1473);
1474
1475impl_bare_newtype_union_enum!(
1476    RequestPayload,
1477    JsonRequestPayload,
1478    #[serde(tag = "type", rename_all = "snake_case")]
1479    {
1480        Authenticate(AuthenticateRequest) = 0,
1481        OpenSession(OpenSessionRequest) = 1,
1482        CreateVm(CreateVmRequest) = 2,
1483        DisposeVm(DisposeVmRequest) = 3,
1484        BootstrapRootFilesystem(BootstrapRootFilesystemRequest) = 4,
1485        ConfigureVm(ConfigureVmRequest) = 5,
1486        RegisterHostCallbacks(RegisterHostCallbacksRequest) = 6,
1487        CreateLayer(CreateLayerRequest) = 7,
1488        SealLayer(SealLayerRequest) = 8,
1489        ImportSnapshot(ImportSnapshotRequest) = 9,
1490        ExportSnapshot(ExportSnapshotRequest) = 10,
1491        CreateOverlay(CreateOverlayRequest) = 11,
1492        GuestFilesystemCall(GuestFilesystemCallRequest) = 12,
1493        SnapshotRootFilesystem(SnapshotRootFilesystemRequest) = 13,
1494        Execute(ExecuteRequest) = 14,
1495        WriteStdin(WriteStdinRequest) = 15,
1496        CloseStdin(CloseStdinRequest) = 16,
1497        KillProcess(KillProcessRequest) = 17,
1498        GetProcessSnapshot(GetProcessSnapshotRequest) = 18,
1499        FindListener(FindListenerRequest) = 19,
1500        FindBoundUdp(FindBoundUdpRequest) = 20,
1501        GetSignalState(GetSignalStateRequest) = 21,
1502        GetZombieTimerCount(GetZombieTimerCountRequest) = 22,
1503        HostFilesystemCall(HostFilesystemCallRequest) = 23,
1504        PersistenceLoad(PersistenceLoadRequest) = 24,
1505        PersistenceFlush(PersistenceFlushRequest) = 25,
1506        VmFetch(VmFetchRequest) = 26,
1507        Ext(ExtEnvelope) = 27,
1508    }
1509);
1510
1511impl_bare_newtype_union_enum!(
1512    ResponsePayload,
1513    JsonResponsePayload,
1514    #[serde(tag = "type", rename_all = "snake_case")]
1515    {
1516        Authenticated(AuthenticatedResponse) = 0,
1517        SessionOpened(SessionOpenedResponse) = 1,
1518        VmCreated(VmCreatedResponse) = 2,
1519        VmDisposed(VmDisposedResponse) = 3,
1520        RootFilesystemBootstrapped(RootFilesystemBootstrappedResponse) = 4,
1521        VmConfigured(VmConfiguredResponse) = 5,
1522        HostCallbacksRegistered(HostCallbacksRegisteredResponse) = 6,
1523        LayerCreated(LayerCreatedResponse) = 7,
1524        LayerSealed(LayerSealedResponse) = 8,
1525        SnapshotImported(SnapshotImportedResponse) = 9,
1526        SnapshotExported(SnapshotExportedResponse) = 10,
1527        OverlayCreated(OverlayCreatedResponse) = 11,
1528        GuestFilesystemResult(GuestFilesystemResultResponse) = 12,
1529        RootFilesystemSnapshot(RootFilesystemSnapshotResponse) = 13,
1530        ProcessStarted(ProcessStartedResponse) = 14,
1531        StdinWritten(StdinWrittenResponse) = 15,
1532        StdinClosed(StdinClosedResponse) = 16,
1533        ProcessKilled(ProcessKilledResponse) = 17,
1534        ProcessSnapshot(ProcessSnapshotResponse) = 18,
1535        ListenerSnapshot(ListenerSnapshotResponse) = 19,
1536        BoundUdpSnapshot(BoundUdpSnapshotResponse) = 20,
1537        SignalState(SignalStateResponse) = 21,
1538        ZombieTimerCount(ZombieTimerCountResponse) = 22,
1539        FilesystemResult(FilesystemResultResponse) = 23,
1540        PermissionDecision(PermissionDecisionResponse) = 24,
1541        PersistenceState(PersistenceStateResponse) = 25,
1542        PersistenceFlushed(PersistenceFlushedResponse) = 26,
1543        Rejected(RejectedResponse) = 27,
1544        VmFetchResult(VmFetchResponse) = 28,
1545        ExtResult(ExtEnvelope) = 29,
1546    }
1547);
1548
1549impl_bare_newtype_union_enum!(
1550    SidecarRequestPayload,
1551    JsonSidecarRequestPayload,
1552    #[serde(tag = "type", rename_all = "snake_case")]
1553    {
1554        HostCallback(HostCallbackRequest) = 0,
1555        JsBridgeCall(JsBridgeCallRequest) = 1,
1556        Ext(ExtEnvelope) = 2,
1557    }
1558);
1559
1560impl_bare_newtype_union_enum!(
1561    SidecarResponsePayload,
1562    JsonSidecarResponsePayload,
1563    #[allow(clippy::enum_variant_names)]
1564    #[serde(tag = "type", rename_all = "snake_case")]
1565    {
1566        HostCallbackResult(HostCallbackResultResponse) = 0,
1567        JsBridgeResult(JsBridgeResultResponse) = 1,
1568        ExtResult(ExtEnvelope) = 2,
1569    }
1570);
1571
1572impl_bare_newtype_union_enum!(
1573    EventPayload,
1574    JsonEventPayload,
1575    #[serde(tag = "type", rename_all = "snake_case")]
1576    {
1577        VmLifecycle(VmLifecycleEvent) = 0,
1578        ProcessOutput(ProcessOutputEvent) = 1,
1579        ProcessExited(ProcessExitedEvent) = 2,
1580        Structured(StructuredEvent) = 3,
1581        Ext(ExtEnvelope) = 4,
1582    }
1583);
1584
1585fn serialize_payload(
1586    frame: &ProtocolFrame,
1587    payload_codec: NativePayloadCodec,
1588) -> Result<Vec<u8>, ProtocolCodecError> {
1589    match payload_codec {
1590        NativePayloadCodec::Json => serde_json::to_vec(frame)
1591            .map_err(|error| ProtocolCodecError::SerializeFailure(error.to_string())),
1592        NativePayloadCodec::Bare => serde_bare::to_vec(&to_generated_protocol_frame(frame)?)
1593            .map_err(|error| ProtocolCodecError::SerializeFailure(error.to_string())),
1594    }
1595}
1596
1597fn deserialize_payload(
1598    payload: &[u8],
1599    payload_codec: NativePayloadCodec,
1600) -> Result<ProtocolFrame, ProtocolCodecError> {
1601    match payload_codec {
1602        NativePayloadCodec::Json => serde_json::from_slice(payload)
1603            .map_err(|error| ProtocolCodecError::DeserializeFailure(error.to_string())),
1604        NativePayloadCodec::Bare => {
1605            let frame: generated_protocol::ProtocolFrame = serde_bare::from_slice(payload)
1606                .map_err(|error| ProtocolCodecError::DeserializeFailure(error.to_string()))?;
1607            from_generated_protocol_frame(frame)
1608        }
1609    }
1610}
1611
1612#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1613pub enum NativePayloadCodec {
1614    Json,
1615    Bare,
1616}
1617
1618impl NativePayloadCodec {
1619    pub fn sniff(payload: &[u8]) -> Self {
1620        match payload.first() {
1621            Some(b'{') => Self::Json,
1622            _ => Self::Bare,
1623        }
1624    }
1625
1626    pub fn alternate(self) -> Self {
1627        match self {
1628            Self::Json => Self::Bare,
1629            Self::Bare => Self::Json,
1630        }
1631    }
1632}
1633
1634#[derive(Debug, Clone)]
1635pub struct NativeFrameCodec {
1636    max_frame_bytes: usize,
1637    payload_codec: NativePayloadCodec,
1638}
1639
1640impl NativeFrameCodec {
1641    pub fn new(max_frame_bytes: usize) -> Self {
1642        Self::with_payload_codec(max_frame_bytes, NativePayloadCodec::Json)
1643    }
1644
1645    pub fn with_payload_codec(max_frame_bytes: usize, payload_codec: NativePayloadCodec) -> Self {
1646        Self {
1647            max_frame_bytes,
1648            payload_codec,
1649        }
1650    }
1651
1652    pub fn max_frame_bytes(&self) -> usize {
1653        self.max_frame_bytes
1654    }
1655
1656    pub fn payload_codec(&self) -> NativePayloadCodec {
1657        self.payload_codec
1658    }
1659
1660    pub fn encode(&self, frame: &ProtocolFrame) -> Result<Vec<u8>, ProtocolCodecError> {
1661        self.encode_with_codec(frame, self.payload_codec)
1662    }
1663
1664    pub fn encode_with_codec(
1665        &self,
1666        frame: &ProtocolFrame,
1667        payload_codec: NativePayloadCodec,
1668    ) -> Result<Vec<u8>, ProtocolCodecError> {
1669        validate_frame(frame)?;
1670
1671        let payload = serialize_payload(frame, payload_codec)?;
1672        if payload.len() > self.max_frame_bytes {
1673            return Err(ProtocolCodecError::FrameTooLarge {
1674                size: payload.len(),
1675                max: self.max_frame_bytes,
1676            });
1677        }
1678
1679        let length =
1680            u32::try_from(payload.len()).map_err(|_| ProtocolCodecError::FrameTooLarge {
1681                size: payload.len(),
1682                max: u32::MAX as usize,
1683            })?;
1684
1685        let mut encoded = Vec::with_capacity(4 + payload.len());
1686        encoded.extend_from_slice(&length.to_be_bytes());
1687        encoded.extend_from_slice(&payload);
1688        Ok(encoded)
1689    }
1690
1691    pub fn decode(&self, bytes: &[u8]) -> Result<ProtocolFrame, ProtocolCodecError> {
1692        self.decode_detected(bytes).map(|(frame, _)| frame)
1693    }
1694
1695    pub fn decode_with_codec(
1696        &self,
1697        bytes: &[u8],
1698        payload_codec: NativePayloadCodec,
1699    ) -> Result<ProtocolFrame, ProtocolCodecError> {
1700        let payload = self.checked_payload(bytes)?;
1701        let frame = deserialize_payload(payload, payload_codec)?;
1702        validate_frame(&frame)?;
1703        Ok(frame)
1704    }
1705
1706    pub fn decode_detected(
1707        &self,
1708        bytes: &[u8],
1709    ) -> Result<(ProtocolFrame, NativePayloadCodec), ProtocolCodecError> {
1710        let payload = self.checked_payload(bytes)?;
1711        let primary = NativePayloadCodec::sniff(payload);
1712
1713        match deserialize_payload(payload, primary) {
1714            Ok(frame) => {
1715                validate_frame(&frame)?;
1716                Ok((frame, primary))
1717            }
1718            Err(primary_error) => {
1719                let alternate = primary.alternate();
1720                let frame = deserialize_payload(payload, alternate).map_err(|_| primary_error)?;
1721                validate_frame(&frame)?;
1722                Ok((frame, alternate))
1723            }
1724        }
1725    }
1726
1727    fn checked_payload<'a>(&self, bytes: &'a [u8]) -> Result<&'a [u8], ProtocolCodecError> {
1728        if bytes.len() < 4 {
1729            return Err(ProtocolCodecError::TruncatedFrame {
1730                actual: bytes.len(),
1731            });
1732        }
1733
1734        let declared =
1735            u32::from_be_bytes(bytes[..4].try_into().expect("length prefix is four bytes"))
1736                as usize;
1737        if declared > self.max_frame_bytes {
1738            return Err(ProtocolCodecError::FrameTooLarge {
1739                size: declared,
1740                max: self.max_frame_bytes,
1741            });
1742        }
1743
1744        let actual = bytes.len() - 4;
1745        if declared != actual {
1746            return Err(ProtocolCodecError::LengthPrefixMismatch { declared, actual });
1747        }
1748        Ok(&bytes[4..])
1749    }
1750}
1751
1752impl Default for NativeFrameCodec {
1753    fn default() -> Self {
1754        Self::new(DEFAULT_MAX_FRAME_BYTES)
1755    }
1756}
1757
1758#[derive(Debug)]
1759pub struct ResponseTracker {
1760    pending: HashMap<RequestId, PendingRequest>,
1761    completed: HashSet<RequestId>,
1762    completed_order: VecDeque<RequestId>,
1763    completed_cap: usize,
1764}
1765
1766#[derive(Debug)]
1767pub struct SidecarResponseTracker {
1768    pending: HashMap<RequestId, PendingSidecarRequest>,
1769    completed: HashSet<RequestId>,
1770    completed_order: VecDeque<RequestId>,
1771    completed_cap: usize,
1772}
1773
1774impl ResponseTracker {
1775    pub fn with_completed_cap(completed_cap: usize) -> Self {
1776        Self {
1777            pending: HashMap::new(),
1778            completed: HashSet::new(),
1779            completed_order: VecDeque::new(),
1780            completed_cap: completed_cap.max(1),
1781        }
1782    }
1783
1784    pub fn completed_count(&self) -> usize {
1785        self.completed.len()
1786    }
1787
1788    pub fn register_request(&mut self, request: &RequestFrame) -> Result<(), ResponseTrackerError> {
1789        if self.pending.contains_key(&request.request_id)
1790            || self.completed.contains(&request.request_id)
1791        {
1792            return Err(ResponseTrackerError::DuplicateRequestId {
1793                request_id: request.request_id,
1794            });
1795        }
1796
1797        self.pending.insert(
1798            request.request_id,
1799            PendingRequest {
1800                ownership: request.ownership.clone(),
1801                expected_response: request.payload.expected_response(),
1802            },
1803        );
1804        Ok(())
1805    }
1806
1807    pub fn accept_response(
1808        &mut self,
1809        response: &ResponseFrame,
1810    ) -> Result<(), ResponseTrackerError> {
1811        if self.completed.contains(&response.request_id) {
1812            return Err(ResponseTrackerError::DuplicateResponse {
1813                request_id: response.request_id,
1814            });
1815        }
1816
1817        let pending = self.pending.get(&response.request_id).ok_or(
1818            ResponseTrackerError::UnmatchedResponse {
1819                request_id: response.request_id,
1820            },
1821        )?;
1822
1823        if pending.ownership != response.ownership {
1824            return Err(ResponseTrackerError::OwnershipMismatch {
1825                request_id: response.request_id,
1826                expected: Box::new(pending.ownership.clone()),
1827                actual: Box::new(response.ownership.clone()),
1828            });
1829        }
1830
1831        if !pending.expected_response.matches(&response.payload) {
1832            return Err(ResponseTrackerError::ResponseKindMismatch {
1833                request_id: response.request_id,
1834                expected: pending.expected_response.as_str().to_string(),
1835                actual: response.payload.kind_name().to_string(),
1836            });
1837        }
1838
1839        self.pending
1840            .remove(&response.request_id)
1841            .expect("pending response should still exist after validation");
1842        self.completed.insert(response.request_id);
1843        self.completed_order.push_back(response.request_id);
1844        while self.completed.len() > self.completed_cap {
1845            if let Some(evicted) = self.completed_order.pop_front() {
1846                self.completed.remove(&evicted);
1847            }
1848        }
1849        Ok(())
1850    }
1851}
1852
1853impl Default for ResponseTracker {
1854    fn default() -> Self {
1855        Self::with_completed_cap(DEFAULT_COMPLETED_RESPONSE_CAP)
1856    }
1857}
1858
1859impl SidecarResponseTracker {
1860    pub fn with_completed_cap(completed_cap: usize) -> Self {
1861        Self {
1862            pending: HashMap::new(),
1863            completed: HashSet::new(),
1864            completed_order: VecDeque::new(),
1865            completed_cap: completed_cap.max(1),
1866        }
1867    }
1868
1869    pub fn pending_count(&self) -> usize {
1870        self.pending.len()
1871    }
1872
1873    pub fn completed_count(&self) -> usize {
1874        self.completed.len()
1875    }
1876
1877    pub fn register_request(
1878        &mut self,
1879        request: &SidecarRequestFrame,
1880    ) -> Result<(), SidecarResponseTrackerError> {
1881        if self.pending.contains_key(&request.request_id)
1882            || self.completed.contains(&request.request_id)
1883        {
1884            return Err(SidecarResponseTrackerError::DuplicateRequestId {
1885                request_id: request.request_id,
1886            });
1887        }
1888
1889        self.pending.insert(
1890            request.request_id,
1891            PendingSidecarRequest {
1892                ownership: request.ownership.clone(),
1893                expected_response: request.payload.expected_response(),
1894            },
1895        );
1896        Ok(())
1897    }
1898
1899    pub fn accept_response(
1900        &mut self,
1901        response: &SidecarResponseFrame,
1902    ) -> Result<(), SidecarResponseTrackerError> {
1903        if self.completed.contains(&response.request_id) {
1904            return Err(SidecarResponseTrackerError::DuplicateResponse {
1905                request_id: response.request_id,
1906            });
1907        }
1908
1909        let pending = self.pending.get(&response.request_id).ok_or(
1910            SidecarResponseTrackerError::UnmatchedResponse {
1911                request_id: response.request_id,
1912            },
1913        )?;
1914
1915        if pending.ownership != response.ownership {
1916            return Err(SidecarResponseTrackerError::OwnershipMismatch {
1917                request_id: response.request_id,
1918                expected: Box::new(pending.ownership.clone()),
1919                actual: Box::new(response.ownership.clone()),
1920            });
1921        }
1922
1923        if !pending.expected_response.matches(&response.payload) {
1924            return Err(SidecarResponseTrackerError::ResponseKindMismatch {
1925                request_id: response.request_id,
1926                expected: pending.expected_response.as_str().to_string(),
1927                actual: response.payload.kind_name().to_string(),
1928            });
1929        }
1930
1931        self.pending
1932            .remove(&response.request_id)
1933            .expect("pending sidecar response should still exist after validation");
1934        self.completed.insert(response.request_id);
1935        self.completed_order.push_back(response.request_id);
1936        while self.completed.len() > self.completed_cap {
1937            if let Some(evicted) = self.completed_order.pop_front() {
1938                self.completed.remove(&evicted);
1939            }
1940        }
1941        Ok(())
1942    }
1943}
1944
1945impl Default for SidecarResponseTracker {
1946    fn default() -> Self {
1947        Self::with_completed_cap(DEFAULT_COMPLETED_RESPONSE_CAP)
1948    }
1949}
1950
1951#[derive(Debug, Clone, PartialEq, Eq)]
1952pub enum ResponseTrackerError {
1953    DuplicateRequestId {
1954        request_id: RequestId,
1955    },
1956    UnmatchedResponse {
1957        request_id: RequestId,
1958    },
1959    DuplicateResponse {
1960        request_id: RequestId,
1961    },
1962    OwnershipMismatch {
1963        request_id: RequestId,
1964        expected: Box<OwnershipScope>,
1965        actual: Box<OwnershipScope>,
1966    },
1967    ResponseKindMismatch {
1968        request_id: RequestId,
1969        expected: String,
1970        actual: String,
1971    },
1972}
1973
1974impl fmt::Display for ResponseTrackerError {
1975    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1976        match self {
1977            Self::DuplicateRequestId { request_id } => {
1978                write!(f, "request id {request_id} is already tracked")
1979            }
1980            Self::UnmatchedResponse { request_id } => {
1981                write!(
1982                    f,
1983                    "response id {request_id} does not match any pending request"
1984                )
1985            }
1986            Self::DuplicateResponse { request_id } => {
1987                write!(f, "response id {request_id} has already been completed")
1988            }
1989            Self::OwnershipMismatch {
1990                request_id,
1991                expected,
1992                actual,
1993            } => write!(
1994                f,
1995                "response id {request_id} used ownership {:?}, expected {:?}",
1996                actual, expected
1997            ),
1998            Self::ResponseKindMismatch {
1999                request_id,
2000                expected,
2001                actual,
2002            } => write!(
2003                f,
2004                "response id {request_id} carried {actual}, expected {expected}",
2005            ),
2006        }
2007    }
2008}
2009
2010impl Error for ResponseTrackerError {}
2011
2012#[derive(Debug, Clone, PartialEq, Eq)]
2013pub enum SidecarResponseTrackerError {
2014    DuplicateRequestId {
2015        request_id: RequestId,
2016    },
2017    UnmatchedResponse {
2018        request_id: RequestId,
2019    },
2020    DuplicateResponse {
2021        request_id: RequestId,
2022    },
2023    OwnershipMismatch {
2024        request_id: RequestId,
2025        expected: Box<OwnershipScope>,
2026        actual: Box<OwnershipScope>,
2027    },
2028    ResponseKindMismatch {
2029        request_id: RequestId,
2030        expected: String,
2031        actual: String,
2032    },
2033}
2034
2035impl fmt::Display for SidecarResponseTrackerError {
2036    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2037        match self {
2038            Self::DuplicateRequestId { request_id } => {
2039                write!(f, "sidecar request id {request_id} is already tracked")
2040            }
2041            Self::UnmatchedResponse { request_id } => {
2042                write!(
2043                    f,
2044                    "sidecar response id {request_id} does not match any pending request"
2045                )
2046            }
2047            Self::DuplicateResponse { request_id } => {
2048                write!(
2049                    f,
2050                    "sidecar response id {request_id} has already been completed"
2051                )
2052            }
2053            Self::OwnershipMismatch {
2054                request_id,
2055                expected,
2056                actual,
2057            } => write!(
2058                f,
2059                "sidecar response id {request_id} used ownership {:?}, expected {:?}",
2060                actual, expected
2061            ),
2062            Self::ResponseKindMismatch {
2063                request_id,
2064                expected,
2065                actual,
2066            } => write!(
2067                f,
2068                "sidecar response id {request_id} carried {actual}, expected {expected}",
2069            ),
2070        }
2071    }
2072}
2073
2074impl Error for SidecarResponseTrackerError {}
2075
2076#[derive(Debug, Clone, PartialEq, Eq)]
2077struct PendingRequest {
2078    ownership: OwnershipScope,
2079    expected_response: ExpectedResponseKind,
2080}
2081
2082#[derive(Debug, Clone, PartialEq, Eq)]
2083struct PendingSidecarRequest {
2084    ownership: OwnershipScope,
2085    expected_response: ExpectedSidecarResponseKind,
2086}
2087
2088#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2089enum ExpectedResponseKind {
2090    Authenticated,
2091    SessionOpened,
2092    VmCreated,
2093    VmDisposed,
2094    RootFilesystemBootstrapped,
2095    VmConfigured,
2096    HostCallbacksRegistered,
2097    LayerCreated,
2098    LayerSealed,
2099    SnapshotImported,
2100    SnapshotExported,
2101    OverlayCreated,
2102    GuestFilesystemResult,
2103    RootFilesystemSnapshot,
2104    ProcessStarted,
2105    StdinWritten,
2106    StdinClosed,
2107    ProcessKilled,
2108    ProcessSnapshot,
2109    ListenerSnapshot,
2110    BoundUdpSnapshot,
2111    VmFetchResult,
2112    SignalState,
2113    ZombieTimerCount,
2114    FilesystemResult,
2115    // `PermissionDecision` is a sidecar-initiated callback response, so no host
2116    // request maps to it in `expected_response_kind`; kept for protocol symmetry.
2117    #[allow(dead_code)]
2118    PermissionDecision,
2119    PersistenceState,
2120    PersistenceFlushed,
2121    ExtResult,
2122}
2123
2124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2125enum ExpectedSidecarResponseKind {
2126    HostCallback,
2127    JsBridge,
2128    Ext,
2129}
2130
2131impl ExpectedResponseKind {
2132    fn as_str(self) -> &'static str {
2133        match self {
2134            Self::Authenticated => "authenticated",
2135            Self::SessionOpened => "session_opened",
2136            Self::VmCreated => "vm_created",
2137            Self::VmDisposed => "vm_disposed",
2138            Self::RootFilesystemBootstrapped => "root_filesystem_bootstrapped",
2139            Self::VmConfigured => "vm_configured",
2140            Self::HostCallbacksRegistered => "host_callbacks_registered",
2141            Self::LayerCreated => "layer_created",
2142            Self::LayerSealed => "layer_sealed",
2143            Self::SnapshotImported => "snapshot_imported",
2144            Self::SnapshotExported => "snapshot_exported",
2145            Self::OverlayCreated => "overlay_created",
2146            Self::GuestFilesystemResult => "guest_filesystem_result",
2147            Self::RootFilesystemSnapshot => "root_filesystem_snapshot",
2148            Self::ProcessStarted => "process_started",
2149            Self::StdinWritten => "stdin_written",
2150            Self::StdinClosed => "stdin_closed",
2151            Self::ProcessKilled => "process_killed",
2152            Self::ProcessSnapshot => "process_snapshot",
2153            Self::ListenerSnapshot => "listener_snapshot",
2154            Self::BoundUdpSnapshot => "bound_udp_snapshot",
2155            Self::VmFetchResult => "vm_fetch_result",
2156            Self::SignalState => "signal_state",
2157            Self::ZombieTimerCount => "zombie_timer_count",
2158            Self::FilesystemResult => "filesystem_result",
2159            Self::PermissionDecision => "permission_decision",
2160            Self::PersistenceState => "persistence_state",
2161            Self::PersistenceFlushed => "persistence_flushed",
2162            Self::ExtResult => "ext_result",
2163        }
2164    }
2165
2166    fn matches(self, payload: &ResponsePayload) -> bool {
2167        match payload {
2168            ResponsePayload::Rejected(_) => true,
2169            _ => payload.kind_name() == self.as_str(),
2170        }
2171    }
2172}
2173
2174impl ExpectedSidecarResponseKind {
2175    fn as_str(self) -> &'static str {
2176        match self {
2177            Self::HostCallback => "host_callback_result",
2178            Self::JsBridge => "js_bridge_result",
2179            Self::Ext => "ext_result",
2180        }
2181    }
2182
2183    fn matches(self, payload: &SidecarResponsePayload) -> bool {
2184        payload.kind_name() == self.as_str()
2185    }
2186}
2187
2188impl RequestPayload {
2189    fn ownership_requirement(&self) -> OwnershipRequirement {
2190        match self {
2191            Self::Authenticate(_) | Self::OpenSession(_) => OwnershipRequirement::Connection,
2192            Self::CreateVm(_) | Self::PersistenceLoad(_) | Self::PersistenceFlush(_) => {
2193                OwnershipRequirement::Session
2194            }
2195            Self::DisposeVm(_)
2196            | Self::BootstrapRootFilesystem(_)
2197            | Self::ConfigureVm(_)
2198            | Self::RegisterHostCallbacks(_)
2199            | Self::CreateLayer(_)
2200            | Self::SealLayer(_)
2201            | Self::ImportSnapshot(_)
2202            | Self::ExportSnapshot(_)
2203            | Self::CreateOverlay(_)
2204            | Self::GuestFilesystemCall(_)
2205            | Self::SnapshotRootFilesystem(_)
2206            | Self::Execute(_)
2207            | Self::WriteStdin(_)
2208            | Self::CloseStdin(_)
2209            | Self::KillProcess(_)
2210            | Self::GetProcessSnapshot(_)
2211            | Self::FindListener(_)
2212            | Self::FindBoundUdp(_)
2213            | Self::VmFetch(_)
2214            | Self::GetSignalState(_)
2215            | Self::GetZombieTimerCount(_)
2216            | Self::HostFilesystemCall(_) => OwnershipRequirement::Vm,
2217            Self::Ext(_) => OwnershipRequirement::Any,
2218        }
2219    }
2220
2221    fn expected_response(&self) -> ExpectedResponseKind {
2222        match self {
2223            Self::Authenticate(_) => ExpectedResponseKind::Authenticated,
2224            Self::OpenSession(_) => ExpectedResponseKind::SessionOpened,
2225            Self::CreateVm(_) => ExpectedResponseKind::VmCreated,
2226            Self::DisposeVm(_) => ExpectedResponseKind::VmDisposed,
2227            Self::BootstrapRootFilesystem(_) => ExpectedResponseKind::RootFilesystemBootstrapped,
2228            Self::ConfigureVm(_) => ExpectedResponseKind::VmConfigured,
2229            Self::RegisterHostCallbacks(_) => ExpectedResponseKind::HostCallbacksRegistered,
2230            Self::CreateLayer(_) => ExpectedResponseKind::LayerCreated,
2231            Self::SealLayer(_) => ExpectedResponseKind::LayerSealed,
2232            Self::ImportSnapshot(_) => ExpectedResponseKind::SnapshotImported,
2233            Self::ExportSnapshot(_) => ExpectedResponseKind::SnapshotExported,
2234            Self::CreateOverlay(_) => ExpectedResponseKind::OverlayCreated,
2235            Self::GuestFilesystemCall(_) => ExpectedResponseKind::GuestFilesystemResult,
2236            Self::SnapshotRootFilesystem(_) => ExpectedResponseKind::RootFilesystemSnapshot,
2237            Self::Execute(_) => ExpectedResponseKind::ProcessStarted,
2238            Self::WriteStdin(_) => ExpectedResponseKind::StdinWritten,
2239            Self::CloseStdin(_) => ExpectedResponseKind::StdinClosed,
2240            Self::KillProcess(_) => ExpectedResponseKind::ProcessKilled,
2241            Self::GetProcessSnapshot(_) => ExpectedResponseKind::ProcessSnapshot,
2242            Self::FindListener(_) => ExpectedResponseKind::ListenerSnapshot,
2243            Self::FindBoundUdp(_) => ExpectedResponseKind::BoundUdpSnapshot,
2244            Self::VmFetch(_) => ExpectedResponseKind::VmFetchResult,
2245            Self::GetSignalState(_) => ExpectedResponseKind::SignalState,
2246            Self::GetZombieTimerCount(_) => ExpectedResponseKind::ZombieTimerCount,
2247            Self::HostFilesystemCall(_) => ExpectedResponseKind::FilesystemResult,
2248            Self::PersistenceLoad(_) => ExpectedResponseKind::PersistenceState,
2249            Self::PersistenceFlush(_) => ExpectedResponseKind::PersistenceFlushed,
2250            Self::Ext(_) => ExpectedResponseKind::ExtResult,
2251        }
2252    }
2253}
2254
2255impl SidecarRequestPayload {
2256    fn ownership_requirement(&self) -> OwnershipRequirement {
2257        OwnershipRequirement::Vm
2258    }
2259
2260    fn expected_response(&self) -> ExpectedSidecarResponseKind {
2261        match self {
2262            Self::HostCallback(_) => ExpectedSidecarResponseKind::HostCallback,
2263            Self::JsBridgeCall(_) => ExpectedSidecarResponseKind::JsBridge,
2264            Self::Ext(_) => ExpectedSidecarResponseKind::Ext,
2265        }
2266    }
2267}
2268
2269impl ResponsePayload {
2270    fn ownership_requirement(&self) -> OwnershipRequirement {
2271        match self {
2272            Self::Authenticated(_) | Self::SessionOpened(_) => OwnershipRequirement::Connection,
2273            Self::VmCreated(_) | Self::PersistenceState(_) | Self::PersistenceFlushed(_) => {
2274                OwnershipRequirement::Session
2275            }
2276            Self::Rejected(_) => OwnershipRequirement::Any,
2277            Self::VmDisposed(_)
2278            | Self::RootFilesystemBootstrapped(_)
2279            | Self::VmConfigured(_)
2280            | Self::HostCallbacksRegistered(_)
2281            | Self::LayerCreated(_)
2282            | Self::LayerSealed(_)
2283            | Self::SnapshotImported(_)
2284            | Self::SnapshotExported(_)
2285            | Self::OverlayCreated(_)
2286            | Self::GuestFilesystemResult(_)
2287            | Self::RootFilesystemSnapshot(_)
2288            | Self::ProcessStarted(_)
2289            | Self::StdinWritten(_)
2290            | Self::StdinClosed(_)
2291            | Self::ProcessKilled(_)
2292            | Self::ProcessSnapshot(_)
2293            | Self::ListenerSnapshot(_)
2294            | Self::BoundUdpSnapshot(_)
2295            | Self::VmFetchResult(_)
2296            | Self::SignalState(_)
2297            | Self::ZombieTimerCount(_)
2298            | Self::FilesystemResult(_)
2299            | Self::PermissionDecision(_) => OwnershipRequirement::Vm,
2300            Self::ExtResult(_) => OwnershipRequirement::Any,
2301        }
2302    }
2303
2304    fn kind_name(&self) -> &'static str {
2305        match self {
2306            Self::Authenticated(_) => "authenticated",
2307            Self::SessionOpened(_) => "session_opened",
2308            Self::VmCreated(_) => "vm_created",
2309            Self::VmDisposed(_) => "vm_disposed",
2310            Self::RootFilesystemBootstrapped(_) => "root_filesystem_bootstrapped",
2311            Self::VmConfigured(_) => "vm_configured",
2312            Self::HostCallbacksRegistered(_) => "host_callbacks_registered",
2313            Self::LayerCreated(_) => "layer_created",
2314            Self::LayerSealed(_) => "layer_sealed",
2315            Self::SnapshotImported(_) => "snapshot_imported",
2316            Self::SnapshotExported(_) => "snapshot_exported",
2317            Self::OverlayCreated(_) => "overlay_created",
2318            Self::GuestFilesystemResult(_) => "guest_filesystem_result",
2319            Self::RootFilesystemSnapshot(_) => "root_filesystem_snapshot",
2320            Self::ProcessStarted(_) => "process_started",
2321            Self::StdinWritten(_) => "stdin_written",
2322            Self::StdinClosed(_) => "stdin_closed",
2323            Self::ProcessKilled(_) => "process_killed",
2324            Self::ProcessSnapshot(_) => "process_snapshot",
2325            Self::ListenerSnapshot(_) => "listener_snapshot",
2326            Self::BoundUdpSnapshot(_) => "bound_udp_snapshot",
2327            Self::VmFetchResult(_) => "vm_fetch_result",
2328            Self::SignalState(_) => "signal_state",
2329            Self::ZombieTimerCount(_) => "zombie_timer_count",
2330            Self::FilesystemResult(_) => "filesystem_result",
2331            Self::PermissionDecision(_) => "permission_decision",
2332            Self::PersistenceState(_) => "persistence_state",
2333            Self::PersistenceFlushed(_) => "persistence_flushed",
2334            Self::Rejected(_) => "rejected",
2335            Self::ExtResult(_) => "ext_result",
2336        }
2337    }
2338}
2339
2340impl SidecarResponsePayload {
2341    fn ownership_requirement(&self) -> OwnershipRequirement {
2342        OwnershipRequirement::Vm
2343    }
2344
2345    fn kind_name(&self) -> &'static str {
2346        match self {
2347            Self::HostCallbackResult(_) => "host_callback_result",
2348            Self::JsBridgeResult(_) => "js_bridge_result",
2349            Self::ExtResult(_) => "ext_result",
2350        }
2351    }
2352}
2353
2354impl EventPayload {
2355    fn ownership_requirement(&self) -> OwnershipRequirement {
2356        match self {
2357            Self::Structured(_) => OwnershipRequirement::SessionOrVm,
2358            Self::VmLifecycle(_) | Self::ProcessOutput(_) | Self::ProcessExited(_) => {
2359                OwnershipRequirement::Vm
2360            }
2361            Self::Ext(_) => OwnershipRequirement::Any,
2362        }
2363    }
2364}
2365
2366pub fn validate_frame(frame: &ProtocolFrame) -> Result<(), ProtocolCodecError> {
2367    match frame {
2368        ProtocolFrame::Request(request) => validate_request(request),
2369        ProtocolFrame::Response(response) => validate_response(response),
2370        ProtocolFrame::Event(event) => validate_event(event),
2371        ProtocolFrame::SidecarRequest(request) => validate_sidecar_request(request),
2372        ProtocolFrame::SidecarResponse(response) => validate_sidecar_response(response),
2373    }
2374}
2375
2376fn validate_request(request: &RequestFrame) -> Result<(), ProtocolCodecError> {
2377    validate_schema(&request.schema)?;
2378    validate_request_id_direction(request.request_id, RequestDirection::Host)?;
2379
2380    validate_ownership(&request.ownership)?;
2381    validate_requirement(request.payload.ownership_requirement(), &request.ownership)?;
2382    if let RequestPayload::Authenticate(authenticate) = &request.payload {
2383        if authenticate.auth_token.is_empty() {
2384            return Err(ProtocolCodecError::EmptyAuthToken);
2385        }
2386    }
2387
2388    Ok(())
2389}
2390
2391fn validate_response(response: &ResponseFrame) -> Result<(), ProtocolCodecError> {
2392    validate_schema(&response.schema)?;
2393    validate_request_id_direction(response.request_id, RequestDirection::Host)?;
2394
2395    validate_ownership(&response.ownership)?;
2396    validate_requirement(
2397        response.payload.ownership_requirement(),
2398        &response.ownership,
2399    )?;
2400    Ok(())
2401}
2402
2403fn validate_sidecar_request(request: &SidecarRequestFrame) -> Result<(), ProtocolCodecError> {
2404    validate_schema(&request.schema)?;
2405    validate_request_id_direction(request.request_id, RequestDirection::Sidecar)?;
2406    validate_ownership(&request.ownership)?;
2407    validate_requirement(request.payload.ownership_requirement(), &request.ownership)?;
2408    Ok(())
2409}
2410
2411fn validate_sidecar_response(response: &SidecarResponseFrame) -> Result<(), ProtocolCodecError> {
2412    validate_schema(&response.schema)?;
2413    validate_request_id_direction(response.request_id, RequestDirection::Sidecar)?;
2414    validate_ownership(&response.ownership)?;
2415    validate_requirement(
2416        response.payload.ownership_requirement(),
2417        &response.ownership,
2418    )?;
2419    Ok(())
2420}
2421
2422fn validate_event(event: &EventFrame) -> Result<(), ProtocolCodecError> {
2423    validate_schema(&event.schema)?;
2424    validate_ownership(&event.ownership)?;
2425    validate_requirement(event.payload.ownership_requirement(), &event.ownership)?;
2426    Ok(())
2427}
2428
2429fn validate_schema(schema: &ProtocolSchema) -> Result<(), ProtocolCodecError> {
2430    if schema.name != PROTOCOL_NAME || schema.version != PROTOCOL_VERSION {
2431        return Err(ProtocolCodecError::UnsupportedSchema {
2432            name: schema.name.clone(),
2433            version: schema.version,
2434        });
2435    }
2436
2437    Ok(())
2438}
2439
2440fn validate_ownership(ownership: &OwnershipScope) -> Result<(), ProtocolCodecError> {
2441    match ownership {
2442        OwnershipScope::ConnectionOwnership(inner) => {
2443            validate_non_empty("connection_id", &inner.connection_id)
2444        }
2445        OwnershipScope::SessionOwnership(inner) => {
2446            validate_non_empty("connection_id", &inner.connection_id)?;
2447            validate_non_empty("session_id", &inner.session_id)
2448        }
2449        OwnershipScope::VmOwnership(inner) => {
2450            validate_non_empty("connection_id", &inner.connection_id)?;
2451            validate_non_empty("session_id", &inner.session_id)?;
2452            validate_non_empty("vm_id", &inner.vm_id)
2453        }
2454    }
2455}
2456
2457fn validate_non_empty(field: &'static str, value: &str) -> Result<(), ProtocolCodecError> {
2458    if value.is_empty() {
2459        return Err(ProtocolCodecError::EmptyOwnershipField { field });
2460    }
2461
2462    Ok(())
2463}
2464
2465fn validate_request_id_direction(
2466    request_id: RequestId,
2467    direction: RequestDirection,
2468) -> Result<(), ProtocolCodecError> {
2469    if request_id == 0 {
2470        return Err(ProtocolCodecError::InvalidRequestId);
2471    }
2472
2473    let matches_direction = match direction {
2474        RequestDirection::Host => request_id > 0,
2475        RequestDirection::Sidecar => request_id < 0,
2476    };
2477    if matches_direction {
2478        Ok(())
2479    } else {
2480        Err(ProtocolCodecError::InvalidRequestDirection {
2481            request_id,
2482            expected: direction,
2483        })
2484    }
2485}
2486
2487fn validate_requirement(
2488    required: OwnershipRequirement,
2489    ownership: &OwnershipScope,
2490) -> Result<(), ProtocolCodecError> {
2491    let actual = match ownership {
2492        OwnershipScope::ConnectionOwnership(..) => OwnershipRequirement::Connection,
2493        OwnershipScope::SessionOwnership(..) => OwnershipRequirement::Session,
2494        OwnershipScope::VmOwnership(..) => OwnershipRequirement::Vm,
2495    };
2496
2497    let valid = match required {
2498        OwnershipRequirement::Any => true,
2499        OwnershipRequirement::Connection => {
2500            matches!(ownership, OwnershipScope::ConnectionOwnership(..))
2501        }
2502        OwnershipRequirement::Session => matches!(ownership, OwnershipScope::SessionOwnership(..)),
2503        OwnershipRequirement::Vm => matches!(ownership, OwnershipScope::VmOwnership(..)),
2504        OwnershipRequirement::SessionOrVm => {
2505            matches!(
2506                ownership,
2507                OwnershipScope::SessionOwnership(..) | OwnershipScope::VmOwnership(..)
2508            )
2509        }
2510    };
2511
2512    if valid {
2513        Ok(())
2514    } else {
2515        Err(ProtocolCodecError::InvalidOwnershipScope { required, actual })
2516    }
2517}
2518
2519// ---------------------------------------------------------------------------
2520// JavaScript sync-RPC request types (deserialized from guest Node.js processes)
2521// ---------------------------------------------------------------------------
2522
2523#[derive(Debug, Deserialize, Default)]
2524pub struct JavascriptChildProcessSpawnOptions {
2525    #[serde(default)]
2526    pub cwd: Option<String>,
2527    #[serde(default)]
2528    pub env: BTreeMap<String, String>,
2529    #[serde(rename = "internalBootstrapEnv", default)]
2530    pub internal_bootstrap_env: BTreeMap<String, String>,
2531    #[serde(default)]
2532    pub input: Option<Value>,
2533    #[serde(default)]
2534    pub shell: bool,
2535    #[serde(default)]
2536    pub detached: bool,
2537    #[serde(default)]
2538    pub stdio: Vec<String>,
2539    #[serde(default)]
2540    pub timeout: Option<u64>,
2541    #[serde(rename = "killSignal", default)]
2542    pub kill_signal: Option<String>,
2543}
2544
2545#[derive(Debug, Deserialize)]
2546pub struct JavascriptChildProcessSpawnRequest {
2547    pub command: String,
2548    #[serde(default)]
2549    pub args: Vec<String>,
2550    #[serde(default)]
2551    pub options: JavascriptChildProcessSpawnOptions,
2552}
2553
2554#[derive(Debug, Deserialize)]
2555pub struct JavascriptNetConnectRequest {
2556    #[serde(default)]
2557    pub host: Option<String>,
2558    #[serde(default)]
2559    pub port: Option<u16>,
2560    #[serde(default)]
2561    pub path: Option<String>,
2562    #[serde(rename = "localAddress", default)]
2563    pub local_address: Option<String>,
2564    #[serde(rename = "localPort", default)]
2565    pub local_port: Option<u16>,
2566    #[serde(rename = "localReservation", default)]
2567    pub local_reservation: Option<String>,
2568}
2569
2570#[derive(Debug, Deserialize)]
2571pub struct JavascriptNetReserveTcpPortRequest {
2572    #[serde(default)]
2573    pub host: Option<String>,
2574    #[serde(default)]
2575    pub port: Option<u16>,
2576}
2577
2578#[derive(Debug, Deserialize)]
2579pub struct JavascriptNetListenRequest {
2580    #[serde(default)]
2581    pub host: Option<String>,
2582    #[serde(default)]
2583    pub port: Option<u16>,
2584    #[serde(default)]
2585    pub path: Option<String>,
2586    #[serde(default)]
2587    pub backlog: Option<u32>,
2588    #[serde(rename = "localReservation", default)]
2589    pub local_reservation: Option<String>,
2590}
2591
2592#[derive(Debug, Deserialize)]
2593pub struct JavascriptDgramCreateSocketRequest {
2594    #[serde(rename = "type")]
2595    pub socket_type: String,
2596}
2597
2598#[derive(Debug, Deserialize)]
2599pub struct JavascriptDgramBindRequest {
2600    #[serde(default)]
2601    pub address: Option<String>,
2602    #[serde(default)]
2603    pub port: u16,
2604}
2605
2606#[derive(Debug, Deserialize)]
2607pub struct JavascriptDgramSendRequest {
2608    #[serde(default)]
2609    pub address: Option<String>,
2610    pub port: u16,
2611}
2612
2613#[derive(Debug, Deserialize)]
2614pub struct JavascriptDnsLookupRequest {
2615    pub hostname: String,
2616    #[serde(default)]
2617    pub family: Option<u8>,
2618}
2619
2620#[derive(Debug, Deserialize)]
2621pub struct JavascriptDnsResolveRequest {
2622    pub hostname: String,
2623    #[serde(default)]
2624    pub rrtype: Option<String>,
2625}