Skip to main content

secure_exec_sidecar/
state.rs

1//! Shared state types used across sidecar domain modules.
2//!
3//! Contains VM state, session state, configuration types, active process/socket
4//! types, and other shared data structures extracted from service.rs.
5
6use crate::protocol::{
7    EventFrame, GuestRuntimeKind, MountDescriptor, PermissionsPolicy, ProjectedModuleDescriptor,
8    RegisterHostCallbacksRequest, ResponseFrame, SidecarRequestFrame, SidecarRequestPayload,
9    SidecarResponseFrame, SidecarResponsePayload, SignalHandlerRegistration, SoftwareDescriptor,
10    WasmPermissionTier,
11};
12use crate::wire::DEFAULT_MAX_FRAME_BYTES;
13use rusqlite::Connection;
14use rustls::{ClientConnection, ServerConnection, StreamOwned};
15use secure_exec_bridge::{BridgeTypes, FilesystemSnapshot};
16use secure_exec_execution::{
17    JavascriptExecution, JavascriptSyncRpcRequest, PythonExecution, PythonVfsRpcRequest,
18    WasmExecution,
19};
20use secure_exec_kernel::kernel::{KernelProcessHandle, KernelVm};
21use secure_exec_kernel::mount_table::MountTable;
22use secure_exec_kernel::root_fs::{RootFileSystem, RootFilesystemMode, RootFilesystemSnapshot};
23use secure_exec_kernel::socket_table::SocketId;
24use secure_exec_vm_config as vm_config;
25use serde::{Deserialize, Serialize};
26use serde_json::Value;
27use std::collections::{BTreeMap, BTreeSet, VecDeque};
28use std::error::Error;
29use std::fmt;
30use std::fs::File;
31use std::net::{IpAddr, SocketAddr, TcpListener, TcpStream, UdpSocket};
32use std::os::unix::net::{UnixListener, UnixStream};
33use std::path::PathBuf;
34use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
35use std::sync::mpsc::{Receiver, Sender};
36use std::sync::{Arc, Condvar, Mutex};
37use std::time::{Duration, Instant};
38use tokio::sync::mpsc::UnboundedSender;
39
40// ---------------------------------------------------------------------------
41// Type aliases
42// ---------------------------------------------------------------------------
43
44pub(crate) type BridgeError<B> = <B as BridgeTypes>::Error;
45pub(crate) type SidecarKernel = KernelVm<MountTable>;
46
47// ---------------------------------------------------------------------------
48// Constants
49// ---------------------------------------------------------------------------
50
51pub(crate) const EXECUTION_DRIVER_NAME: &str = "secure-exec-sidecar-execution";
52pub(crate) const JAVASCRIPT_COMMAND: &str = "node";
53pub(crate) const PYTHON_COMMAND: &str = "python";
54pub(crate) const WASM_COMMAND: &str = "wasm";
55pub(crate) const PYTHON_VFS_RPC_GUEST_ROOT: &str = "/workspace";
56pub(crate) const EXECUTION_SANDBOX_ROOT_ENV: &str = "AGENTOS_SANDBOX_ROOT";
57pub(crate) const WASM_STDIO_SYNC_RPC_ENV: &str = "AGENTOS_WASI_STDIO_SYNC_RPC";
58#[cfg(test)]
59#[allow(dead_code)]
60pub(crate) const HOST_REALPATH_MAX_SYMLINK_DEPTH: usize = 40;
61pub(crate) const DISPOSE_VM_SIGTERM_GRACE: std::time::Duration =
62    std::time::Duration::from_millis(100);
63pub(crate) const DISPOSE_VM_SIGKILL_GRACE: std::time::Duration =
64    std::time::Duration::from_millis(100);
65pub(crate) const VM_DNS_SERVERS_METADATA_KEY: &str = "network.dns.servers";
66#[cfg(test)]
67#[allow(dead_code)]
68pub(crate) const VM_LISTEN_PORT_MIN_METADATA_KEY: &str = "network.listen.port_min";
69#[cfg(test)]
70#[allow(dead_code)]
71pub(crate) const VM_LISTEN_PORT_MAX_METADATA_KEY: &str = "network.listen.port_max";
72pub(crate) const VM_LISTEN_ALLOW_PRIVILEGED_METADATA_KEY: &str = "network.listen.allow_privileged";
73pub(crate) const DEFAULT_JAVASCRIPT_NET_BACKLOG: u32 = 511;
74pub(crate) const LOOPBACK_EXEMPT_PORTS_ENV: &str = "AGENTOS_LOOPBACK_EXEMPT_PORTS";
75pub(crate) const TOOL_DRIVER_NAME: &str = "secure-exec-host-callbacks";
76pub(crate) const MAPPED_HOST_FD_START: u32 = 1_000_000_000;
77
78// ---------------------------------------------------------------------------
79// Public API types
80// ---------------------------------------------------------------------------
81
82#[derive(Debug, Clone)]
83pub struct NativeSidecarConfig {
84    pub sidecar_id: String,
85    pub max_frame_bytes: usize,
86    pub compile_cache_root: Option<PathBuf>,
87    pub expected_auth_token: Option<String>,
88    pub acp_termination_grace: Duration,
89}
90
91impl Default for NativeSidecarConfig {
92    fn default() -> Self {
93        Self {
94            sidecar_id: String::from("secure-exec-sidecar"),
95            max_frame_bytes: DEFAULT_MAX_FRAME_BYTES,
96            compile_cache_root: None,
97            expected_auth_token: None,
98            acp_termination_grace: Duration::from_secs(3),
99        }
100    }
101}
102
103#[derive(Debug, Clone)]
104pub struct DispatchResult {
105    pub response: ResponseFrame,
106    pub events: Vec<EventFrame>,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
110pub enum SidecarError {
111    InvalidState(String),
112    ProtocolVersionMismatch(String),
113    BridgeVersionMismatch(String),
114    Conflict(String),
115    Unauthorized(String),
116    Unsupported(String),
117    FrameTooLarge(String),
118    Kernel(String),
119    Plugin(String),
120    Execution(String),
121    Bridge(String),
122    Io(String),
123}
124
125impl fmt::Display for SidecarError {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        match self {
128            Self::InvalidState(message)
129            | Self::ProtocolVersionMismatch(message)
130            | Self::BridgeVersionMismatch(message)
131            | Self::Conflict(message)
132            | Self::Unauthorized(message)
133            | Self::Unsupported(message)
134            | Self::FrameTooLarge(message)
135            | Self::Kernel(message)
136            | Self::Plugin(message)
137            | Self::Execution(message)
138            | Self::Bridge(message)
139            | Self::Io(message) => f.write_str(message),
140        }
141    }
142}
143
144impl Error for SidecarError {}
145
146pub trait SidecarRequestTransport: Send + Sync {
147    fn send_request(
148        &self,
149        request: SidecarRequestFrame,
150        timeout: Duration,
151    ) -> Result<SidecarResponseFrame, SidecarError>;
152}
153
154#[derive(Clone)]
155pub(crate) struct SharedSidecarRequestClient {
156    transport: Option<Arc<dyn SidecarRequestTransport>>,
157    next_request_id: Arc<AtomicI64>,
158}
159
160impl Default for SharedSidecarRequestClient {
161    fn default() -> Self {
162        Self {
163            transport: None,
164            next_request_id: Arc::new(AtomicI64::new(-1)),
165        }
166    }
167}
168
169impl SharedSidecarRequestClient {
170    pub(crate) fn set_transport(&mut self, transport: Arc<dyn SidecarRequestTransport>) {
171        self.transport = Some(transport);
172    }
173
174    pub(crate) fn invoke(
175        &self,
176        ownership: crate::protocol::OwnershipScope,
177        payload: SidecarRequestPayload,
178        timeout: Duration,
179    ) -> Result<SidecarResponsePayload, SidecarError> {
180        let transport = self.transport.as_ref().ok_or_else(|| {
181            SidecarError::Unsupported(String::from("sidecar request transport is not configured"))
182        })?;
183        let request_id = self.next_request_id.fetch_sub(1, Ordering::Relaxed);
184        let request = SidecarRequestFrame::new(request_id, ownership.clone(), payload);
185        let response = transport.send_request(request, timeout)?;
186        if response.request_id != request_id {
187            return Err(SidecarError::InvalidState(format!(
188                "sidecar response {} did not match request {request_id}",
189                response.request_id
190            )));
191        }
192        if response.ownership != ownership {
193            return Err(SidecarError::InvalidState(String::from(
194                "sidecar response ownership did not match request ownership",
195            )));
196        }
197        Ok(response.payload)
198    }
199}
200
201// ---------------------------------------------------------------------------
202// Bridge wrapper
203// ---------------------------------------------------------------------------
204
205pub(crate) struct SharedBridge<B> {
206    pub(crate) inner: Arc<Mutex<B>>,
207    pub(crate) permissions: Arc<Mutex<BTreeMap<String, PermissionsPolicy>>>,
208    #[cfg(test)]
209    pub(crate) set_vm_permissions_outcomes: Arc<Mutex<VecDeque<Option<SidecarError>>>>,
210}
211
212impl<B> Clone for SharedBridge<B> {
213    fn clone(&self) -> Self {
214        Self {
215            inner: Arc::clone(&self.inner),
216            permissions: Arc::clone(&self.permissions),
217            #[cfg(test)]
218            set_vm_permissions_outcomes: Arc::clone(&self.set_vm_permissions_outcomes),
219        }
220    }
221}
222
223// ---------------------------------------------------------------------------
224// Connection / session / VM state
225// ---------------------------------------------------------------------------
226
227#[allow(dead_code)]
228#[derive(Debug)]
229pub(crate) struct ConnectionState {
230    pub(crate) auth_token: String,
231    pub(crate) sessions: BTreeSet<String>,
232}
233
234#[allow(dead_code)]
235#[derive(Debug)]
236pub(crate) struct SessionState {
237    pub(crate) connection_id: String,
238    pub(crate) placement: crate::protocol::SidecarPlacement,
239    pub(crate) metadata: BTreeMap<String, String>,
240    pub(crate) vm_ids: BTreeSet<String>,
241}
242
243#[allow(dead_code)]
244#[derive(Debug, Default, Clone)]
245pub(crate) struct VmConfiguration {
246    pub(crate) mounts: Vec<MountDescriptor>,
247    pub(crate) software: Vec<SoftwareDescriptor>,
248    pub(crate) permissions: PermissionsPolicy,
249    pub(crate) module_access_cwd: Option<String>,
250    pub(crate) instructions: Vec<String>,
251    pub(crate) projected_modules: Vec<ProjectedModuleDescriptor>,
252    pub(crate) command_permissions: BTreeMap<String, WasmPermissionTier>,
253    /// Guest JavaScript host-environment config (platform / module resolution /
254    /// builtin allow-list). Set at `create_vm` from `CreateVmConfig.jsRuntime`
255    /// and preserved across `configure_vm`. `None` => full Node.js emulation.
256    pub(crate) js_runtime: Option<vm_config::JsRuntimeConfig>,
257    pub(crate) loopback_exempt_ports: Vec<u16>,
258}
259
260#[allow(dead_code)]
261pub(crate) struct VmLayerStore {
262    pub(crate) next_layer_id: u64,
263    pub(crate) layers: BTreeMap<String, VmLayer>,
264}
265
266impl Default for VmLayerStore {
267    fn default() -> Self {
268        Self {
269            next_layer_id: 1,
270            layers: BTreeMap::new(),
271        }
272    }
273}
274
275#[allow(dead_code)]
276#[derive(Debug)]
277pub(crate) enum VmLayer {
278    Writable(RootFileSystem),
279    Snapshot(RootFilesystemSnapshot),
280    Overlay(VmOverlayLayer),
281}
282
283#[allow(dead_code)]
284#[derive(Debug, Clone)]
285pub(crate) struct VmOverlayLayer {
286    pub(crate) mode: RootFilesystemMode,
287    pub(crate) upper_layer_id: Option<String>,
288    pub(crate) lower_layer_ids: Vec<String>,
289}
290
291#[allow(dead_code)]
292pub(crate) struct VmState {
293    pub(crate) connection_id: String,
294    pub(crate) session_id: String,
295    /// Operator-tunable VM-scoped runtime limits. Immutable for the VM's lifetime;
296    /// `ConfigureVm` does not mutate limits.
297    pub(crate) limits: crate::limits::VmLimits,
298    pub(crate) dns: VmDnsConfig,
299    pub(crate) listen_policy: VmListenPolicy,
300    pub(crate) create_loopback_exempt_ports: BTreeSet<u16>,
301    pub(crate) guest_env: BTreeMap<String, String>,
302    pub(crate) requested_runtime: GuestRuntimeKind,
303    pub(crate) root_filesystem_mode: RootFilesystemMode,
304    pub(crate) guest_cwd: String,
305    pub(crate) cwd: PathBuf,
306    pub(crate) host_cwd: PathBuf,
307    pub(crate) kernel: SidecarKernel,
308    pub(crate) loaded_snapshot: Option<FilesystemSnapshot>,
309    pub(crate) configuration: VmConfiguration,
310    pub(crate) layers: VmLayerStore,
311    pub(crate) command_guest_paths: BTreeMap<String, String>,
312    pub(crate) command_permissions: BTreeMap<String, WasmPermissionTier>,
313    pub(crate) toolkits: BTreeMap<String, RegisterHostCallbacksRequest>,
314    pub(crate) active_processes: BTreeMap<String, ActiveProcess>,
315    pub(crate) exited_process_snapshots: VecDeque<ExitedProcessSnapshot>,
316    pub(crate) detached_child_processes: BTreeSet<String>,
317    pub(crate) signal_states: BTreeMap<String, BTreeMap<u32, SignalHandlerRegistration>>,
318}
319
320#[derive(Debug, Clone)]
321pub(crate) struct ExitedProcessSnapshot {
322    pub(crate) captured_at: Instant,
323    pub(crate) process: crate::protocol::ProcessSnapshotEntry,
324}
325
326// ---------------------------------------------------------------------------
327// DNS configuration
328// ---------------------------------------------------------------------------
329
330#[derive(Debug, Clone, Default)]
331pub(crate) struct VmDnsConfig {
332    pub(crate) name_servers: Vec<SocketAddr>,
333    pub(crate) overrides: BTreeMap<String, Vec<IpAddr>>,
334}
335
336#[derive(Debug, Clone)]
337pub(crate) struct JavascriptSocketPathContext {
338    pub(crate) sandbox_root: PathBuf,
339    pub(crate) mounts: Vec<MountDescriptor>,
340    pub(crate) listen_policy: VmListenPolicy,
341    pub(crate) loopback_exempt_ports: BTreeSet<u16>,
342    pub(crate) tcp_loopback_guest_to_host_ports: BTreeMap<(JavascriptSocketFamily, u16), u16>,
343    pub(crate) http_loopback_targets:
344        BTreeMap<(JavascriptSocketFamily, u16), JavascriptHttpLoopbackTarget>,
345    pub(crate) udp_loopback_guest_to_host_ports: BTreeMap<(JavascriptSocketFamily, u16), u16>,
346    pub(crate) udp_loopback_host_to_guest_ports: BTreeMap<(JavascriptSocketFamily, u16), u16>,
347    pub(crate) used_tcp_guest_ports: BTreeMap<JavascriptSocketFamily, BTreeSet<u16>>,
348    pub(crate) used_udp_guest_ports: BTreeMap<JavascriptSocketFamily, BTreeSet<u16>>,
349}
350
351#[derive(Debug, Clone)]
352pub(crate) struct JavascriptHttpLoopbackTarget {
353    pub(crate) process_id: String,
354    pub(crate) server_id: u64,
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
358pub(crate) enum JavascriptSocketFamily {
359    Ipv4,
360    Ipv6,
361}
362
363impl JavascriptSocketFamily {
364    pub(crate) fn from_ip(ip: IpAddr) -> Self {
365        match ip {
366            IpAddr::V4(_) => Self::Ipv4,
367            IpAddr::V6(_) => Self::Ipv6,
368        }
369    }
370}
371
372impl From<JavascriptUdpFamily> for JavascriptSocketFamily {
373    fn from(value: JavascriptUdpFamily) -> Self {
374        match value {
375            JavascriptUdpFamily::Ipv4 => Self::Ipv4,
376            JavascriptUdpFamily::Ipv6 => Self::Ipv6,
377        }
378    }
379}
380
381#[derive(Debug, Clone, Copy)]
382pub(crate) struct VmListenPolicy {
383    pub(crate) port_min: u16,
384    pub(crate) port_max: u16,
385    pub(crate) allow_privileged: bool,
386}
387
388impl Default for VmListenPolicy {
389    fn default() -> Self {
390        Self {
391            port_min: 1,
392            port_max: u16::MAX,
393            allow_privileged: false,
394        }
395    }
396}
397
398// ---------------------------------------------------------------------------
399// Active process state
400// ---------------------------------------------------------------------------
401
402#[allow(dead_code)]
403pub(crate) struct ActiveProcess {
404    pub(crate) kernel_pid: u32,
405    pub(crate) kernel_handle: KernelProcessHandle,
406    pub(crate) kernel_stdin_writer_fd: Option<u32>,
407    pub(crate) runtime: GuestRuntimeKind,
408    pub(crate) detached: bool,
409    pub(crate) execution: ActiveExecution,
410    pub(crate) guest_cwd: String,
411    pub(crate) env: BTreeMap<String, String>,
412    pub(crate) host_cwd: PathBuf,
413    pub(crate) mapped_host_fds: BTreeMap<u32, ActiveMappedHostFd>,
414    pub(crate) next_mapped_host_fd: u32,
415    pub(crate) pending_execution_events: VecDeque<ActiveExecutionEvent>,
416    pub(crate) pending_self_signal_exit: Option<i32>,
417    pub(crate) child_processes: BTreeMap<String, ActiveProcess>,
418    pub(crate) next_child_process_id: usize,
419    pub(crate) http_servers: BTreeMap<u64, ActiveHttpServer>,
420    pub(crate) pending_http_requests: BTreeMap<(u64, u64), Option<String>>,
421    pub(crate) http2: ActiveHttp2State,
422    pub(crate) tcp_listeners: BTreeMap<String, ActiveTcpListener>,
423    pub(crate) next_tcp_listener_id: usize,
424    pub(crate) tcp_sockets: BTreeMap<String, ActiveTcpSocket>,
425    pub(crate) next_tcp_socket_id: usize,
426    pub(crate) tcp_port_reservations: BTreeMap<String, (JavascriptSocketFamily, u16)>,
427    pub(crate) next_tcp_port_reservation_id: usize,
428    pub(crate) unix_listeners: BTreeMap<String, ActiveUnixListener>,
429    pub(crate) next_unix_listener_id: usize,
430    pub(crate) unix_sockets: BTreeMap<String, ActiveUnixSocket>,
431    pub(crate) next_unix_socket_id: usize,
432    pub(crate) udp_sockets: BTreeMap<String, ActiveUdpSocket>,
433    pub(crate) next_udp_socket_id: usize,
434    pub(crate) cipher_sessions: BTreeMap<u64, ActiveCipherSession>,
435    pub(crate) next_cipher_session_id: u64,
436    pub(crate) diffie_hellman_sessions: BTreeMap<u64, ActiveDiffieHellmanSession>,
437    pub(crate) next_diffie_hellman_session_id: u64,
438    pub(crate) sqlite_databases: BTreeMap<u64, ActiveSqliteDatabase>,
439    pub(crate) next_sqlite_database_id: u64,
440    pub(crate) sqlite_statements: BTreeMap<u64, ActiveSqliteStatement>,
441    pub(crate) next_sqlite_statement_id: u64,
442    /// Per-process module resolution cache, persisted across module sync-RPCs
443    /// (`__resolve_module` / `__load_file` / `__module_format` /
444    /// `__batch_resolve_modules`) for the lifetime of this process so cold-start
445    /// resolution does not rebuild it on every dispatch. The resolver reads the
446    /// kernel VFS; the node_modules tree is mounted read-only, so cached
447    /// stat/exists/package.json results under it stay valid for the process run.
448    pub(crate) module_resolution_cache: secure_exec_execution::LocalModuleResolutionCache,
449}
450
451pub(crate) struct ActiveMappedHostFd {
452    pub(crate) file: File,
453    pub(crate) path: PathBuf,
454}
455
456pub(crate) struct ActiveCipherSession {
457    pub(crate) algorithm: String,
458    pub(crate) auth_tag_len: usize,
459    pub(crate) context: openssl::symm::Crypter,
460}
461
462pub(crate) struct ActiveSqliteDatabase {
463    pub(crate) connection: Connection,
464    pub(crate) host_path: Option<PathBuf>,
465    pub(crate) vm_path: Option<String>,
466    pub(crate) dirty: bool,
467    pub(crate) transaction_depth: usize,
468    pub(crate) read_only: bool,
469}
470
471#[derive(Clone)]
472pub(crate) struct ActiveSqliteStatement {
473    pub(crate) database_id: u64,
474    pub(crate) sql: String,
475    pub(crate) return_arrays: bool,
476    pub(crate) read_bigints: bool,
477    pub(crate) allow_bare_named_parameters: bool,
478    pub(crate) allow_unknown_named_parameters: bool,
479}
480
481pub(crate) enum ActiveDiffieHellmanSession {
482    Dh(ActiveDhSession),
483    Ecdh(ActiveEcdhSession),
484}
485
486pub(crate) struct ActiveDhSession {
487    pub(crate) params: openssl::dh::Dh<openssl::pkey::Params>,
488    pub(crate) key_pair: Option<openssl::dh::Dh<openssl::pkey::Private>>,
489}
490
491pub(crate) struct ActiveEcdhSession {
492    pub(crate) curve: String,
493    pub(crate) key_pair: Option<openssl::ec::EcKey<openssl::pkey::Private>>,
494}
495
496#[derive(Debug, Clone, Copy, Default)]
497pub(crate) struct NetworkResourceCounts {
498    pub(crate) sockets: usize,
499    pub(crate) connections: usize,
500}
501
502#[derive(Debug)]
503pub(crate) struct ActiveHttpServer {
504    pub(crate) listener: TcpListener,
505    pub(crate) guest_local_addr: SocketAddr,
506    pub(crate) next_request_id: u64,
507}
508
509#[derive(Clone, Default)]
510pub(crate) struct ActiveHttp2State {
511    pub(crate) shared: Arc<Mutex<Http2SharedState>>,
512}
513
514#[derive(Default)]
515pub(crate) struct Http2SharedState {
516    pub(crate) next_session_id: u64,
517    pub(crate) next_stream_id: u64,
518    pub(crate) servers: BTreeMap<u64, ActiveHttp2Server>,
519    pub(crate) sessions: BTreeMap<u64, ActiveHttp2Session>,
520    pub(crate) streams: BTreeMap<u64, ActiveHttp2Stream>,
521    pub(crate) server_events: BTreeMap<u64, VecDeque<Http2BridgeEvent>>,
522    pub(crate) session_events: BTreeMap<u64, VecDeque<Http2BridgeEvent>>,
523}
524
525#[derive(Debug)]
526pub(crate) struct ActiveHttp2Server {
527    pub(crate) actual_local_addr: SocketAddr,
528    pub(crate) guest_local_addr: SocketAddr,
529    pub(crate) secure: bool,
530    pub(crate) tls: Option<JavascriptTlsBridgeOptions>,
531    pub(crate) closed: Arc<AtomicBool>,
532}
533
534#[derive(Debug, Clone)]
535pub(crate) struct ActiveHttp2Session {
536    pub(crate) command_tx: UnboundedSender<Http2SessionCommand>,
537}
538
539#[derive(Debug, Clone)]
540pub(crate) struct ActiveHttp2Stream {
541    pub(crate) session_id: u64,
542    pub(crate) paused: Arc<AtomicBool>,
543}
544
545#[derive(Debug, Clone, Default, Serialize, Deserialize)]
546#[serde(default, rename_all = "camelCase")]
547pub(crate) struct Http2SocketSnapshot {
548    pub(crate) encrypted: bool,
549    pub(crate) allow_half_open: bool,
550    pub(crate) local_address: Option<String>,
551    pub(crate) local_port: Option<u16>,
552    pub(crate) local_family: Option<String>,
553    pub(crate) remote_address: Option<String>,
554    pub(crate) remote_port: Option<u16>,
555    pub(crate) remote_family: Option<String>,
556    pub(crate) servername: Option<String>,
557    pub(crate) alpn_protocol: Option<String>,
558}
559
560#[derive(Debug, Clone, Default, Serialize, Deserialize)]
561#[serde(default, rename_all = "camelCase")]
562pub(crate) struct Http2RuntimeSnapshot {
563    pub(crate) effective_local_window_size: u32,
564    pub(crate) local_window_size: u32,
565    pub(crate) remote_window_size: u32,
566    pub(crate) next_stream_id: u32,
567    pub(crate) outbound_queue_size: u32,
568    pub(crate) deflate_dynamic_table_size: u32,
569    pub(crate) inflate_dynamic_table_size: u32,
570}
571
572#[derive(Debug, Clone, Default, Serialize, Deserialize)]
573#[serde(default, rename_all = "camelCase")]
574pub(crate) struct Http2SessionSnapshot {
575    pub(crate) encrypted: bool,
576    pub(crate) alpn_protocol: Option<String>,
577    pub(crate) origin_set: Vec<String>,
578    pub(crate) local_settings: BTreeMap<String, Value>,
579    pub(crate) remote_settings: BTreeMap<String, Value>,
580    pub(crate) state: Http2RuntimeSnapshot,
581    pub(crate) socket: Http2SocketSnapshot,
582}
583
584#[derive(Debug, Clone, Default, Serialize, Deserialize)]
585#[serde(default, rename_all = "camelCase")]
586pub(crate) struct Http2BridgeEvent {
587    pub(crate) kind: String,
588    pub(crate) id: u64,
589    #[serde(skip_serializing_if = "Option::is_none")]
590    pub(crate) data: Option<String>,
591    #[serde(skip_serializing_if = "Option::is_none")]
592    pub(crate) extra: Option<String>,
593    #[serde(skip_serializing_if = "Option::is_none")]
594    pub(crate) extra_number: Option<u64>,
595    #[serde(skip_serializing_if = "Option::is_none")]
596    pub(crate) extra_headers: Option<String>,
597    #[serde(skip_serializing_if = "Option::is_none")]
598    pub(crate) flags: Option<u64>,
599}
600
601pub(crate) enum Http2SessionCommand {
602    Request {
603        headers_json: String,
604        options_json: String,
605        respond_to: Sender<Result<Value, String>>,
606    },
607    Settings {
608        settings_json: String,
609        respond_to: Sender<Result<Value, String>>,
610    },
611    SetLocalWindowSize {
612        size: u32,
613        respond_to: Sender<Result<Value, String>>,
614    },
615    Goaway {
616        error_code: u32,
617        last_stream_id: u32,
618        opaque_data: Option<Vec<u8>>,
619        respond_to: Sender<Result<Value, String>>,
620    },
621    Close {
622        abrupt: bool,
623        respond_to: Sender<Result<Value, String>>,
624    },
625    StreamRespond {
626        stream_id: u64,
627        headers_json: String,
628        respond_to: Sender<Result<Value, String>>,
629    },
630    StreamPush {
631        stream_id: u64,
632        headers_json: String,
633        respond_to: Sender<Result<Value, String>>,
634    },
635    StreamWrite {
636        stream_id: u64,
637        chunk: Vec<u8>,
638        end_stream: bool,
639        respond_to: Sender<Result<Value, String>>,
640    },
641    StreamClose {
642        stream_id: u64,
643        error_code: Option<u32>,
644        respond_to: Sender<Result<Value, String>>,
645    },
646    StreamRespondWithFile {
647        stream_id: u64,
648        body: Vec<u8>,
649        headers_json: String,
650        options_json: String,
651        respond_to: Sender<Result<Value, String>>,
652    },
653}
654
655// ---------------------------------------------------------------------------
656// TCP types
657// ---------------------------------------------------------------------------
658
659#[derive(Debug)]
660pub(crate) enum JavascriptTcpListenerEvent {
661    Connection(PendingTcpSocket),
662    Error {
663        code: Option<String>,
664        message: String,
665    },
666}
667
668#[derive(Debug)]
669pub(crate) struct PendingTcpSocket {
670    pub(crate) stream: Option<TcpStream>,
671    pub(crate) kernel_socket_id: Option<SocketId>,
672    pub(crate) preallocated: bool,
673    pub(crate) guest_local_addr: SocketAddr,
674    pub(crate) guest_remote_addr: SocketAddr,
675}
676
677#[derive(Debug)]
678pub(crate) enum JavascriptTcpSocketEvent {
679    Data(Vec<u8>),
680    End,
681    Close {
682        had_error: bool,
683    },
684    Error {
685        code: Option<String>,
686        message: String,
687    },
688}
689
690#[derive(Debug)]
691pub(crate) struct ActiveTcpSocket {
692    pub(crate) stream: Option<Arc<Mutex<TcpStream>>>,
693    pub(crate) pending_read_stream: Option<Arc<Mutex<Option<TcpStream>>>>,
694    pub(crate) events: Option<Receiver<JavascriptTcpSocketEvent>>,
695    pub(crate) event_sender: Option<Sender<JavascriptTcpSocketEvent>>,
696    pub(crate) kernel_socket_id: Option<SocketId>,
697    pub(crate) no_delay: bool,
698    pub(crate) keep_alive: bool,
699    pub(crate) keep_alive_initial_delay_secs: Option<u64>,
700    pub(crate) guest_local_addr: SocketAddr,
701    pub(crate) guest_remote_addr: SocketAddr,
702    pub(crate) listener_id: Option<String>,
703    pub(crate) tls_mode: Arc<AtomicBool>,
704    pub(crate) tls_stream: Arc<Mutex<Option<ActiveTlsStream>>>,
705    pub(crate) tls_state: Arc<Mutex<Option<ActiveTlsState>>>,
706    pub(crate) saw_local_shutdown: Arc<AtomicBool>,
707    pub(crate) saw_remote_end: Arc<AtomicBool>,
708    pub(crate) close_notified: Arc<AtomicBool>,
709}
710
711pub(crate) struct LoopbackTlsTransportPair {
712    pub(crate) state: Mutex<LoopbackTlsTransportPairState>,
713    pub(crate) ready: Condvar,
714}
715
716#[derive(Debug, Default)]
717pub(crate) struct LoopbackTlsTransportPairState {
718    pub(crate) lower_to_higher: VecDeque<u8>,
719    pub(crate) higher_to_lower: VecDeque<u8>,
720    pub(crate) lower_write_closed: bool,
721    pub(crate) higher_write_closed: bool,
722    pub(crate) lower_closed: bool,
723    pub(crate) higher_closed: bool,
724}
725
726pub(crate) struct LoopbackTlsEndpoint {
727    pub(crate) pair: Arc<LoopbackTlsTransportPair>,
728    pub(crate) is_lower_socket: bool,
729}
730
731impl fmt::Debug for LoopbackTlsEndpoint {
732    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
733        f.debug_struct("LoopbackTlsEndpoint")
734            .field("is_lower_socket", &self.is_lower_socket)
735            .finish()
736    }
737}
738
739#[derive(Debug)]
740pub(crate) enum ActiveTlsStream {
741    Client(StreamOwned<ClientConnection, TcpStream>),
742    Server(StreamOwned<ServerConnection, TcpStream>),
743    LoopbackClient(StreamOwned<ClientConnection, LoopbackTlsEndpoint>),
744    LoopbackServer(StreamOwned<ServerConnection, LoopbackTlsEndpoint>),
745}
746
747#[derive(Debug, Clone, Default, Serialize, Deserialize)]
748#[serde(default, rename_all = "camelCase")]
749pub(crate) struct JavascriptTlsClientHello {
750    #[serde(skip_serializing_if = "Option::is_none")]
751    pub(crate) servername: Option<String>,
752    #[serde(
753        rename = "ALPNProtocols",
754        alias = "ALPNProtocols",
755        skip_serializing_if = "Option::is_none"
756    )]
757    pub(crate) alpn_protocols: Option<Vec<String>>,
758}
759
760#[derive(Debug, Clone, Default, Deserialize)]
761#[serde(default, rename_all = "camelCase")]
762pub(crate) struct JavascriptTlsBridgeOptions {
763    pub(crate) is_server: bool,
764    pub(crate) servername: Option<String>,
765    pub(crate) reject_unauthorized: Option<bool>,
766    pub(crate) request_cert: Option<bool>,
767    pub(crate) session: Option<String>,
768    pub(crate) key: Option<JavascriptTlsMaterial>,
769    pub(crate) cert: Option<JavascriptTlsMaterial>,
770    pub(crate) ca: Option<JavascriptTlsMaterial>,
771    pub(crate) passphrase: Option<String>,
772    pub(crate) ciphers: Option<String>,
773    #[serde(alias = "ALPNProtocols")]
774    pub(crate) alpn_protocols: Option<Vec<String>>,
775    pub(crate) min_version: Option<String>,
776    pub(crate) max_version: Option<String>,
777}
778
779#[derive(Debug, Clone, Deserialize)]
780#[serde(untagged)]
781pub(crate) enum JavascriptTlsMaterial {
782    Single(JavascriptTlsDataValue),
783    Many(Vec<JavascriptTlsDataValue>),
784}
785
786#[derive(Debug, Clone, Deserialize)]
787#[serde(tag = "kind", rename_all = "camelCase")]
788pub(crate) enum JavascriptTlsDataValue {
789    Buffer { data: String },
790    String { data: String },
791}
792
793#[derive(Debug, Clone, Default)]
794pub(crate) struct ActiveTlsState {
795    pub(crate) client_hello: Option<JavascriptTlsClientHello>,
796    pub(crate) local_certificates: Vec<Vec<u8>>,
797    pub(crate) session_reused: bool,
798}
799
800#[derive(Debug, Clone, Copy)]
801pub(crate) struct ResolvedTcpConnectAddr {
802    pub(crate) actual_addr: SocketAddr,
803    pub(crate) guest_remote_addr: SocketAddr,
804    pub(crate) use_kernel_loopback: bool,
805}
806
807#[derive(Debug)]
808pub(crate) struct ActiveTcpListener {
809    pub(crate) listener: Option<TcpListener>,
810    pub(crate) kernel_socket_id: Option<SocketId>,
811    pub(crate) local_addr: Option<SocketAddr>,
812    pub(crate) guest_local_addr: SocketAddr,
813    pub(crate) backlog: usize,
814    pub(crate) active_connection_ids: BTreeSet<String>,
815}
816
817// ---------------------------------------------------------------------------
818// Unix socket types
819// ---------------------------------------------------------------------------
820
821#[derive(Debug)]
822pub(crate) enum JavascriptUnixListenerEvent {
823    Connection(PendingUnixSocket),
824    Error {
825        code: Option<String>,
826        message: String,
827    },
828}
829
830#[derive(Debug)]
831pub(crate) struct PendingUnixSocket {
832    pub(crate) stream: UnixStream,
833    pub(crate) local_path: Option<String>,
834    pub(crate) remote_path: Option<String>,
835}
836
837#[derive(Debug)]
838pub(crate) struct ActiveUnixSocket {
839    pub(crate) stream: Arc<Mutex<UnixStream>>,
840    pub(crate) events: Receiver<JavascriptTcpSocketEvent>,
841    pub(crate) event_sender: Sender<JavascriptTcpSocketEvent>,
842    pub(crate) listener_id: Option<String>,
843    pub(crate) local_path: Option<String>,
844    pub(crate) remote_path: Option<String>,
845    pub(crate) saw_local_shutdown: Arc<AtomicBool>,
846    pub(crate) saw_remote_end: Arc<AtomicBool>,
847    pub(crate) close_notified: Arc<AtomicBool>,
848}
849
850#[derive(Debug)]
851pub(crate) struct ActiveUnixListener {
852    pub(crate) listener: UnixListener,
853    pub(crate) path: String,
854    pub(crate) backlog: usize,
855    pub(crate) active_connection_ids: BTreeSet<String>,
856}
857
858// ---------------------------------------------------------------------------
859// UDP types
860// ---------------------------------------------------------------------------
861
862#[derive(Debug, Clone, Copy, PartialEq, Eq)]
863pub(crate) enum JavascriptUdpFamily {
864    Ipv4,
865    Ipv6,
866}
867
868impl JavascriptUdpFamily {
869    pub(crate) fn from_socket_type(value: &str) -> Result<Self, SidecarError> {
870        match value {
871            "udp4" => Ok(Self::Ipv4),
872            "udp6" => Ok(Self::Ipv6),
873            other => Err(SidecarError::InvalidState(format!(
874                "unsupported dgram socket type {other}"
875            ))),
876        }
877    }
878
879    pub(crate) fn socket_type(self) -> &'static str {
880        match self {
881            Self::Ipv4 => "udp4",
882            Self::Ipv6 => "udp6",
883        }
884    }
885
886    pub(crate) fn matches_addr(self, addr: &SocketAddr) -> bool {
887        matches!(
888            (self, addr),
889            (Self::Ipv4, SocketAddr::V4(_)) | (Self::Ipv6, SocketAddr::V6(_))
890        )
891    }
892}
893
894#[derive(Debug)]
895pub(crate) enum JavascriptUdpSocketEvent {
896    Message {
897        data: Vec<u8>,
898        remote_addr: SocketAddr,
899    },
900    Error {
901        code: Option<String>,
902        message: String,
903    },
904}
905
906#[derive(Debug)]
907pub(crate) struct ActiveUdpSocket {
908    pub(crate) family: JavascriptUdpFamily,
909    pub(crate) socket: Option<UdpSocket>,
910    pub(crate) kernel_socket_id: Option<SocketId>,
911    pub(crate) guest_local_addr: Option<SocketAddr>,
912    pub(crate) recv_buffer_size: usize,
913    pub(crate) send_buffer_size: usize,
914}
915
916// ---------------------------------------------------------------------------
917// Execution types
918// ---------------------------------------------------------------------------
919
920#[derive(Debug)]
921pub(crate) enum ActiveExecution {
922    Javascript(JavascriptExecution),
923    Python(PythonExecution),
924    Wasm(Box<WasmExecution>),
925    Tool(ToolExecution),
926}
927
928#[derive(Debug, Clone)]
929pub(crate) struct ToolExecution {
930    pub(crate) cancelled: Arc<AtomicBool>,
931    pub(crate) pending_events: Arc<Mutex<VecDeque<ActiveExecutionEvent>>>,
932    pub(crate) events_overflowed: Arc<AtomicBool>,
933}
934
935impl Default for ToolExecution {
936    fn default() -> Self {
937        Self {
938            cancelled: Arc::new(AtomicBool::new(false)),
939            pending_events: Arc::new(Mutex::new(VecDeque::new())),
940            events_overflowed: Arc::new(AtomicBool::new(false)),
941        }
942    }
943}
944
945#[derive(Debug)]
946pub(crate) enum ActiveExecutionEvent {
947    Stdout(Vec<u8>),
948    Stderr(Vec<u8>),
949    JavascriptSyncRpcRequest(JavascriptSyncRpcRequest),
950    PythonVfsRpcRequest(Box<PythonVfsRpcRequest>),
951    SignalState {
952        signal: u32,
953        registration: SignalHandlerRegistration,
954    },
955    Exited(i32),
956}
957
958#[derive(Debug)]
959pub(crate) struct ProcessEventEnvelope {
960    pub(crate) connection_id: String,
961    pub(crate) session_id: String,
962    pub(crate) vm_id: String,
963    pub(crate) process_id: String,
964    pub(crate) event: ActiveExecutionEvent,
965}
966
967#[derive(Debug, Clone, Copy, PartialEq, Eq)]
968pub(crate) enum SocketQueryKind {
969    TcpListener,
970    UdpBound,
971}
972
973// ---------------------------------------------------------------------------
974// Command resolution
975// ---------------------------------------------------------------------------
976
977#[derive(Debug)]
978pub(crate) struct ResolvedChildProcessExecution {
979    pub(crate) command: String,
980    pub(crate) process_args: Vec<String>,
981    pub(crate) runtime: GuestRuntimeKind,
982    pub(crate) entrypoint: String,
983    pub(crate) execution_args: Vec<String>,
984    pub(crate) env: BTreeMap<String, String>,
985    pub(crate) guest_cwd: String,
986    pub(crate) host_cwd: PathBuf,
987    pub(crate) wasm_permission_tier: Option<WasmPermissionTier>,
988    pub(crate) tool_command: bool,
989}
990
991// ---------------------------------------------------------------------------
992// Utility types
993// ---------------------------------------------------------------------------
994
995#[derive(Debug)]
996pub(crate) struct ProcNetEntry {
997    pub(crate) local_host: String,
998    pub(crate) local_port: u16,
999    pub(crate) state: String,
1000    pub(crate) inode: u64,
1001}