1use std::{sync::Arc, thread, time::Duration};
2
3use sim_kernel::{CapabilityName, Cx, Error, Result, Symbol};
4
5use crate::{
6 EvalSite, Server, ServerAddress, ServerRuntime, ThreadMode, pool::default_worker_pool,
7};
8
9mod backends;
10mod framing;
11#[cfg(feature = "server-net-http")]
12mod http_transport;
13#[allow(dead_code)]
14pub(crate) mod port_io;
15mod site;
16mod socket;
17#[cfg(feature = "server-net-http")]
18mod sse_transport;
19#[cfg(test)]
20mod tests;
21#[cfg(feature = "server-net-http")]
22mod ws_transport;
23
24pub use backends::{
25 LocalTransport, LoopbackTransportEndpoint, RegistryTransport, WasmConnectionTransport,
26};
27pub use framing::{decode_transport_frame, encode_transport_frame};
28#[cfg(feature = "server-net-http")]
29pub use http_transport::{HttpConnectionTransport, HttpServerTransport};
30pub use site::TransportEvalSite;
31pub use socket::{TcpConnectionTransport, TcpServerTransport};
32#[cfg(unix)]
33#[allow(unused_imports)]
34pub use socket::{UnixConnectionTransport, UnixServerTransport};
35#[cfg(feature = "server-net-http")]
36pub use sse_transport::{SseConnectionTransport, SseServerTransport};
37#[cfg(feature = "server-net-http")]
38pub use ws_transport::{WsConnectionTransport, WsServerTransport};
39
40pub(crate) use backends::TransportEndpoint;
41use backends::{has_registered_endpoint, register_endpoint, unregister_endpoint};
42#[cfg(feature = "server-net-http")]
43use framing::io_to_host;
44use framing::{
45 answer_or_negotiate, error_frame_from_error, is_timeout, read_frame_from, route_frame_bytes,
46 update_negotiated_codec_from_reply, write_frame_to,
47};
48
49pub(crate) const MAX_TRANSPORT_FRAME_BYTES: usize = 8 * 1024 * 1024;
50pub(crate) const SERVER_CONNECTION_IO_TIMEOUT_MS: u64 = 250;
51pub(crate) const DEFAULT_MAX_INFLIGHT_FRAMES: usize = 8;
52fn bound_transport_services()
53-> std::result::Result<sim_transport_ports::TransportServices, sim_transport_ports::TransportError>
54{
55 match sim_transport_ports::services() {
56 Ok(value) => Ok(value),
57 #[cfg(test)]
58 Err(_) => {
59 static TEST_SERVICES: std::sync::OnceLock<sim_transport_ports::TransportServices> =
60 std::sync::OnceLock::new();
61 let services = TEST_SERVICES
62 .get_or_init(|| {
63 let mut model = sim_transport_ports::model::ModelPorts::new(Default::default());
64 model.ipc = true;
65 let model = Arc::new(model);
66 sim_transport_ports::TransportServices {
67 sockets: model.clone(),
68 dns: model.clone(),
69 ipc: Some(model),
70 }
71 })
72 .clone();
73 sim_transport_ports::bind_services(services.clone())?;
74 Ok(services)
75 }
76 #[cfg(not(test))]
77 Err(error) => Err(error),
78 }
79}
80pub(crate) const WEBHOOK_SERVE_CAPABILITY: &str = "webhook-serve";
81#[cfg(feature = "server-net-http")]
82pub(crate) const HTTP_TRANSPORT_PATH: &str = "/sim/frame";
83#[cfg(feature = "server-net-http")]
84pub(crate) const SSE_TRANSPORT_PATH: &str = "/sim/stream";
85#[cfg(feature = "server-net-http")]
86pub(crate) const WS_TRANSPORT_PATH: &str = "/sim/ws";
87
88pub trait ServerTransport: Send + Sync {
90 fn address(&self) -> &ServerAddress;
92 fn accept(&self, cx: &mut Cx) -> Result<Box<dyn ConnectionTransport>>;
94 fn shutdown(&self, cx: &mut Cx) -> Result<()>;
96
97 fn accept_timeout(
99 &self,
100 cx: &mut Cx,
101 timeout: Duration,
102 ) -> Result<Option<Box<dyn ConnectionTransport>>>;
103}
104
105pub trait ConnectionTransport: Send + Sync {
107 fn send_frame(&mut self, cx: &mut Cx, frame: crate::ServerFrame) -> Result<()>;
109 fn recv_frame(
111 &mut self,
112 cx: &mut Cx,
113 timeout: Option<Duration>,
114 ) -> Result<Option<crate::ServerFrame>>;
115 fn close(&mut self, cx: &mut Cx) -> Result<()>;
117 fn as_any(&self) -> &dyn std::any::Any;
119
120 fn serve_connection(
125 &mut self,
126 _runtime: &Arc<ServerRuntime>,
127 _site: &Arc<dyn EvalSite>,
128 ) -> Result<()> {
129 Err(Error::Eval(
130 "transport does not support server-side serving".to_owned(),
131 ))
132 }
133}
134
135pub fn start_server_transport(server: &Server) -> Result<()> {
136 if !server.address().transport_available() {
137 return Err(Error::Eval(format!(
138 "no transport for address kind {}",
139 server.address().kind_symbol()
140 )));
141 }
142 match server.address() {
143 ServerAddress::Local | ServerAddress::Any => Ok(()),
144 ServerAddress::Tcp { .. }
145 | ServerAddress::Unix { .. }
146 | ServerAddress::Http { .. }
147 | ServerAddress::Sse { .. }
148 | ServerAddress::Ws { .. } => {
149 let Some(runtime) = server.runtime().cloned() else {
150 return Ok(());
151 };
152 register_endpoint(TransportEndpoint {
153 address: server.address().clone(),
154 site: server.site().clone(),
155 })?;
156 let site = server.site().clone();
157 match server.thread() {
158 ThreadMode::Main => {
159 run_accept_loop(runtime, site);
160 Ok(())
161 }
162 ThreadMode::Coroutine(_) => Ok(()),
163 ThreadMode::Coop | ThreadMode::Spawn | ThreadMode::Pool => {
164 let accept_runtime = runtime.clone();
165 let handle = thread::spawn(move || run_accept_loop(accept_runtime, site));
166 runtime.set_accept_thread(handle)
167 }
168 }
169 }
170 ServerAddress::Wasm { region } => require_wasm_region(region),
171 _ => register_endpoint(TransportEndpoint {
172 address: server.address().clone(),
173 site: server.site().clone(),
174 }),
175 }
176}
177
178pub fn shutdown_server_transport(server: &Server) -> Result<()> {
179 match server.address() {
180 ServerAddress::Local | ServerAddress::Any => Ok(()),
181 ServerAddress::Tcp { .. }
182 | ServerAddress::Unix { .. }
183 | ServerAddress::Http { .. }
184 | ServerAddress::Sse { .. }
185 | ServerAddress::Ws { .. } => {
186 if let Some(runtime) = server.runtime() {
187 runtime.begin_stop();
188 runtime.join_accept_thread()?;
189 runtime.join_worker_threads()?;
190 runtime.with_cx(|cx| runtime.transport().shutdown(cx))?;
191 runtime.clear_sessions()?;
192 }
193 unregister_endpoint(server.address())?;
194 Ok(())
195 }
196 ServerAddress::Wasm { .. } => Ok(()),
197 _ => unregister_endpoint(server.address()),
198 }
199}
200
201pub fn require_start_capabilities(cx: &Cx, address: &ServerAddress) -> Result<()> {
202 match address {
203 ServerAddress::Tcp { .. } | ServerAddress::Unix { .. } => require_network_capability(cx),
204 ServerAddress::Http { .. } | ServerAddress::Sse { .. } | ServerAddress::Ws { .. } => {
205 require_network_capability(cx)?;
206 cx.require(&CapabilityName::new(WEBHOOK_SERVE_CAPABILITY))
207 }
208 _ => Ok(()),
209 }
210}
211
212pub fn require_connect_capabilities(cx: &Cx, address: &ServerAddress) -> Result<()> {
213 match address {
214 ServerAddress::Tcp { .. }
215 | ServerAddress::Unix { .. }
216 | ServerAddress::Http { .. }
217 | ServerAddress::Sse { .. }
218 | ServerAddress::Ws { .. } => require_network_capability(cx),
219 _ => Ok(()),
220 }
221}
222
223fn require_network_capability(cx: &Cx) -> Result<()> {
224 require_with_aliases(cx, net_http_capability(), net_http_aliases())
225}
226
227fn net_http_capability() -> CapabilityName {
228 CapabilityName::new("net/http")
229}
230
231fn net_http_aliases() -> &'static [&'static str] {
232 &["net.http", "net-connect", "network"]
233}
234
235fn require_with_aliases(
236 cx: &Cx,
237 canonical: CapabilityName,
238 aliases: &'static [&'static str],
239) -> Result<()> {
240 if cx.capabilities().contains(&canonical)
241 || aliases
242 .iter()
243 .any(|alias| cx.capabilities().contains(&CapabilityName::new(*alias)))
244 {
245 Ok(())
246 } else {
247 Err(Error::CapabilityDenied {
248 capability: canonical,
249 })
250 }
251}
252
253pub fn connect_transport_site(
258 cx: &mut Cx,
259 address: ServerAddress,
260 offered_codecs: Vec<Symbol>,
261) -> Result<(Arc<dyn EvalSite>, Symbol)> {
262 connect_transport_site_with_loopback(cx, address, offered_codecs, false)
263}
264
265pub fn connect_transport_site_with_loopback(
270 cx: &mut Cx,
271 address: ServerAddress,
272 offered_codecs: Vec<Symbol>,
273 allow_loopback: bool,
274) -> Result<(Arc<dyn EvalSite>, Symbol)> {
275 require_connect_capabilities(cx, &address)?;
276 TransportEvalSite::connect_with_loopback(cx, address, offered_codecs, allow_loopback)
277}
278
279pub fn register_loopback_transport_endpoint(
284 address: ServerAddress,
285 site: Arc<dyn EvalSite>,
286) -> Result<LoopbackTransportEndpoint> {
287 backends::register_loopback_endpoint(address, site)
288}
289
290#[cfg(unix)]
291fn open_unix_connection_transport(
292 address: &ServerAddress,
293 allow_loopback: bool,
294) -> Result<Box<dyn ConnectionTransport>> {
295 match socket::UnixConnectionTransport::connect(address) {
296 Ok(transport) => Ok(Box::new(transport)),
297 Err(_error) if allow_loopback && has_registered_endpoint(address)? => {
298 Ok(Box::new(RegistryTransport::new(address.clone())))
299 }
300 Err(error) => Err(error),
301 }
302}
303
304#[cfg(not(unix))]
305fn open_unix_connection_transport(
306 _address: &ServerAddress,
307 _allow_loopback: bool,
308) -> Result<Box<dyn ConnectionTransport>> {
309 Err(Error::Eval(
310 "unix sockets are not available on this target".to_owned(),
311 ))
312}
313
314#[cfg(feature = "server-net-http")]
315fn open_http_connection_transport(
316 address: &ServerAddress,
317 allow_loopback: bool,
318) -> Result<Box<dyn ConnectionTransport>> {
319 match HttpConnectionTransport::connect(address) {
320 Ok(transport) => Ok(Box::new(transport)),
321 Err(_error) if allow_loopback && has_registered_endpoint(address)? => {
322 Ok(Box::new(RegistryTransport::new(address.clone())))
323 }
324 Err(error) => Err(error),
325 }
326}
327
328#[cfg(not(feature = "server-net-http"))]
329fn open_http_connection_transport(
330 _address: &ServerAddress,
331 _allow_loopback: bool,
332) -> Result<Box<dyn ConnectionTransport>> {
333 Err(http_transport_disabled_error())
334}
335
336#[cfg(feature = "server-net-http")]
337fn open_sse_connection_transport(
338 address: &ServerAddress,
339 allow_loopback: bool,
340) -> Result<Box<dyn ConnectionTransport>> {
341 match SseConnectionTransport::connect(address) {
342 Ok(transport) => Ok(Box::new(transport)),
343 Err(_error) if allow_loopback && has_registered_endpoint(address)? => {
344 Ok(Box::new(RegistryTransport::new(address.clone())))
345 }
346 Err(error) => Err(error),
347 }
348}
349
350#[cfg(not(feature = "server-net-http"))]
351fn open_sse_connection_transport(
352 _address: &ServerAddress,
353 _allow_loopback: bool,
354) -> Result<Box<dyn ConnectionTransport>> {
355 Err(http_transport_disabled_error())
356}
357
358#[cfg(feature = "server-net-http")]
359fn open_ws_connection_transport(
360 address: &ServerAddress,
361 allow_loopback: bool,
362) -> Result<Box<dyn ConnectionTransport>> {
363 match WsConnectionTransport::connect(address) {
364 Ok(transport) => Ok(Box::new(transport)),
365 Err(_error) if allow_loopback && has_registered_endpoint(address)? => {
366 Ok(Box::new(RegistryTransport::new(address.clone())))
367 }
368 Err(error) => Err(error),
369 }
370}
371
372#[cfg(not(feature = "server-net-http"))]
373fn open_ws_connection_transport(
374 _address: &ServerAddress,
375 _allow_loopback: bool,
376) -> Result<Box<dyn ConnectionTransport>> {
377 Err(http_transport_disabled_error())
378}
379
380fn open_connection_transport(
381 address: &ServerAddress,
382 allow_loopback: bool,
383) -> Result<Box<dyn ConnectionTransport>> {
384 match address {
385 ServerAddress::Local | ServerAddress::Any => Err(Error::Eval(
386 "local addresses require a direct site or server value".to_owned(),
387 )),
388 ServerAddress::InProcess { .. } | ServerAddress::Coroutine { .. } => {
389 Ok(Box::new(RegistryTransport::new(address.clone())))
390 }
391 ServerAddress::Wasm { .. } => Ok(Box::new(WasmConnectionTransport::connect(address)?)),
392 ServerAddress::Http { .. } => open_http_connection_transport(address, allow_loopback),
393 ServerAddress::Sse { .. } => open_sse_connection_transport(address, allow_loopback),
394 ServerAddress::Ws { .. } => open_ws_connection_transport(address, allow_loopback),
395 ServerAddress::Tcp { .. } => match TcpConnectionTransport::connect(address) {
396 Ok(transport) => Ok(Box::new(transport)),
397 Err(_error) if allow_loopback && has_registered_endpoint(address)? => {
398 Ok(Box::new(RegistryTransport::new(address.clone())))
399 }
400 Err(error) => Err(error),
401 },
402 ServerAddress::Unix { .. } => open_unix_connection_transport(address, allow_loopback),
403 _ => Err(Error::Eval(format!(
404 "no connection transport for address kind {}",
405 address.kind_symbol()
406 ))),
407 }
408}
409
410#[cfg(unix)]
411fn open_unix_server_transport(address: ServerAddress) -> Result<Option<Arc<dyn ServerTransport>>> {
412 Ok(Some(Arc::new(socket::UnixServerTransport::bind(address)?)))
413}
414
415#[cfg(not(unix))]
416fn open_unix_server_transport(_address: ServerAddress) -> Result<Option<Arc<dyn ServerTransport>>> {
417 Err(Error::Eval(
418 "unix sockets are not available on this target".to_owned(),
419 ))
420}
421
422#[cfg(feature = "server-net-http")]
423fn open_http_server_transport(address: ServerAddress) -> Result<Option<Arc<dyn ServerTransport>>> {
424 Ok(Some(Arc::new(HttpServerTransport::bind(address)?)))
425}
426
427#[cfg(not(feature = "server-net-http"))]
428fn open_http_server_transport(_address: ServerAddress) -> Result<Option<Arc<dyn ServerTransport>>> {
429 Err(http_transport_disabled_error())
430}
431
432#[cfg(feature = "server-net-http")]
433fn open_sse_server_transport(address: ServerAddress) -> Result<Option<Arc<dyn ServerTransport>>> {
434 Ok(Some(Arc::new(SseServerTransport::bind(address)?)))
435}
436
437#[cfg(not(feature = "server-net-http"))]
438fn open_sse_server_transport(_address: ServerAddress) -> Result<Option<Arc<dyn ServerTransport>>> {
439 Err(http_transport_disabled_error())
440}
441
442#[cfg(feature = "server-net-http")]
443fn open_ws_server_transport(address: ServerAddress) -> Result<Option<Arc<dyn ServerTransport>>> {
444 Ok(Some(Arc::new(WsServerTransport::bind(address)?)))
445}
446
447#[cfg(not(feature = "server-net-http"))]
448fn open_ws_server_transport(_address: ServerAddress) -> Result<Option<Arc<dyn ServerTransport>>> {
449 Err(http_transport_disabled_error())
450}
451
452pub fn open_server_transport(address: ServerAddress) -> Result<Option<Arc<dyn ServerTransport>>> {
453 match &address {
454 ServerAddress::Local | ServerAddress::Any | ServerAddress::Coroutine { .. } => Ok(None),
455 ServerAddress::Tcp { .. } => Ok(Some(Arc::new(TcpServerTransport::bind(address)?))),
456 ServerAddress::Unix { .. } => open_unix_server_transport(address),
457 ServerAddress::InProcess { .. } => Ok(Some(
458 Arc::new(RegistryTransport::new(address)) as Arc<dyn ServerTransport>
459 )),
460 ServerAddress::Wasm { region } => require_wasm_region(region).map(|()| None),
461 ServerAddress::Http { .. } => open_http_server_transport(address),
462 ServerAddress::Sse { .. } => open_sse_server_transport(address),
463 ServerAddress::Ws { .. } => open_ws_server_transport(address),
464 _ => Ok(None),
465 }
466}
467
468pub(crate) fn transport_kind(address: &ServerAddress) -> &'static str {
469 match address {
470 ServerAddress::InProcess { .. } => "in-proc",
471 ServerAddress::Coroutine { .. } => "coroutine",
472 ServerAddress::Tcp { .. } => "tcp",
473 ServerAddress::Unix { .. } => "unix",
474 ServerAddress::Wasm { .. } => "wasm-shmem",
475 ServerAddress::Http { .. } => "http",
476 ServerAddress::Sse { .. } => "sse",
477 ServerAddress::Ws { .. } => "ws",
478 _ => "transport",
479 }
480}
481
482#[cfg(feature = "wasm")]
483fn require_wasm_region(region: &str) -> Result<()> {
484 let _ = crate::wasm::lookup_wasm_region(region)?;
485 Ok(())
486}
487
488#[cfg(not(feature = "wasm"))]
489fn require_wasm_region(_region: &str) -> Result<()> {
490 Err(Error::Eval("server wasm feature disabled".to_owned()))
491}
492
493#[cfg(not(feature = "server-net-http"))]
494fn http_transport_disabled_error() -> Error {
495 Error::Eval("http transport requires the server-net-http feature".to_owned())
496}
497
498include!("transport/accept_loop.rs");