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