wasmtime_wasi/sockets/mod.rs
1use crate::runtime::{AbortOnDropJoinHandle, poll_noop};
2use crate::{NamedId, WasiCtxNamedView};
3use core::fmt;
4use core::future::Future;
5use core::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
6use core::ops::Deref;
7use rustix::fd::AsFd;
8use rustix::io::Errno;
9use rustix::net::sockopt;
10use std::marker;
11use std::pin::Pin;
12use std::sync::Arc;
13use std::task::Poll;
14use tracing::debug;
15use wasmtime::component::{HasData, ResourceTable};
16
17pub(crate) mod ip_name_lookup;
18mod tcp;
19mod udp;
20pub use tcp::TcpSocket;
21pub(crate) use tcp::{TcpListenStream, TcpReceiveStream, TcpSendStream};
22pub use udp::UdpSocket;
23
24/// A helper struct which implements [`HasData`] for the `wasi:sockets` APIs.
25///
26/// This can be useful when directly calling `add_to_linker` functions directly,
27/// such as [`wasmtime_wasi::p2::bindings::sockets::tcp::add_to_linker`] as the
28/// `D` type parameter. See [`HasData`] for more information about the type
29/// parameter's purpose.
30///
31/// When using this type you can skip the [`WasiSocketsView`] trait, for
32/// example.
33///
34/// [`wasmtime_wasi::p2::bindings::sockets::tcp::add_to_linker`]: crate::p2::bindings::sockets::tcp::add_to_linker
35///
36/// # Examples
37///
38/// ```
39/// use wasmtime::component::{Linker, ResourceTable};
40/// use wasmtime::{Engine, Result};
41/// use wasmtime_wasi::sockets::*;
42///
43/// struct MyStoreState {
44/// table: ResourceTable,
45/// sockets: WasiSocketsCtx,
46/// }
47///
48/// fn main() -> Result<()> {
49/// let engine = Engine::default();
50/// let mut linker = Linker::new(&engine);
51///
52/// wasmtime_wasi::p2::bindings::sockets::tcp::add_to_linker::<MyStoreState, WasiSockets>(
53/// &mut linker,
54/// |state| WasiSocketsCtxView {
55/// ctx: &mut state.sockets,
56/// table: &mut state.table,
57/// },
58/// )?;
59/// Ok(())
60/// }
61/// ```
62pub struct WasiSockets;
63
64impl HasData for WasiSockets {
65 type Data<'a> = WasiSocketsCtxView<'a>;
66}
67
68#[derive(Clone, Default)]
69pub struct WasiSocketsCtx {
70 pub(crate) socket_addr_check: SocketAddrCheck,
71 pub(crate) allowed_network_uses: AllowedNetworkUses,
72}
73
74pub struct WasiSocketsCtxView<'a> {
75 pub ctx: &'a mut WasiSocketsCtx,
76 pub table: &'a mut ResourceTable,
77}
78
79pub trait WasiSocketsView: Send {
80 fn sockets(&mut self) -> WasiSocketsCtxView<'_>;
81}
82
83#[derive(Copy, Clone, Default)]
84pub(crate) struct AllowedNetworkUses {
85 pub(crate) ip_name_lookup: bool,
86 pub(crate) udp: bool,
87 pub(crate) tcp: bool,
88}
89
90impl AllowedNetworkUses {
91 pub(crate) fn check_allowed_udp(&self) -> std::io::Result<()> {
92 if !self.udp {
93 return Err(std::io::Error::new(
94 std::io::ErrorKind::PermissionDenied,
95 "UDP is not allowed",
96 ));
97 }
98
99 Ok(())
100 }
101
102 pub(crate) fn check_allowed_tcp(&self) -> std::io::Result<()> {
103 if !self.tcp {
104 return Err(std::io::Error::new(
105 std::io::ErrorKind::PermissionDenied,
106 "TCP is not allowed",
107 ));
108 }
109
110 Ok(())
111 }
112}
113
114/// A check that will be called for each socket address that is used of whether the address is permitted.
115#[derive(Clone)]
116pub(crate) struct SocketAddrCheck(
117 Arc<
118 dyn Fn(SocketAddr, SocketAddrUse) -> Pin<Box<dyn Future<Output = bool> + Send + Sync>>
119 + Send
120 + Sync,
121 >,
122);
123
124impl SocketAddrCheck {
125 /// A check that will be called for each socket address that is used.
126 ///
127 /// Returning `true` will permit socket connections to the `SocketAddr`,
128 /// while returning `false` will reject the connection.
129 pub(crate) fn new(
130 f: impl Fn(SocketAddr, SocketAddrUse) -> Pin<Box<dyn Future<Output = bool> + Send + Sync>>
131 + Send
132 + Sync
133 + 'static,
134 ) -> Self {
135 Self(Arc::new(f))
136 }
137
138 pub(crate) async fn check(
139 &self,
140 addr: SocketAddr,
141 reason: SocketAddrUse,
142 ) -> std::io::Result<()> {
143 if (self.0)(addr, reason).await {
144 Ok(())
145 } else {
146 Err(std::io::Error::new(
147 std::io::ErrorKind::PermissionDenied,
148 "An address was not permitted by the socket address check.",
149 ))
150 }
151 }
152}
153
154impl Deref for SocketAddrCheck {
155 type Target = dyn Fn(SocketAddr, SocketAddrUse) -> Pin<Box<dyn Future<Output = bool> + Send + Sync>>
156 + Send
157 + Sync;
158
159 fn deref(&self) -> &Self::Target {
160 self.0.as_ref()
161 }
162}
163
164impl Default for SocketAddrCheck {
165 fn default() -> Self {
166 Self(Arc::new(|_, _| Box::pin(async { false })))
167 }
168}
169
170/// The reason what a socket address is being used for.
171#[derive(Clone, Copy, Debug)]
172pub enum SocketAddrUse {
173 /// Binding TCP socket.
174 ///
175 /// This is invoked for both explicit calls to `bind` as well as implicit
176 /// binds that are about to be performed by the OS as part of
177 /// e.g. `connect` & `listen`.
178 ///
179 /// The address that is passed to the check is the address provided to
180 /// `bind` for explicit binds, or the wildcard address for implicit binds.
181 TcpBind,
182
183 /// Put a TCP socket in listener mode.
184 ///
185 /// If the socket was already bound at the time of the call, the actual
186 /// local address of the socket is passed to the check. If the socket is
187 /// about to be implicitly bound by `listen`, the wildcard address is passed.
188 TcpListen,
189
190 /// Accepting a new client TCP socket.
191 ///
192 /// The address passed to the check is the remote address of the client that
193 /// is being accepted. If the check fails, the client socket will be
194 /// silently dropped before reaching the guest.
195 TcpAccept,
196
197 /// Connecting a TCP socket.
198 ///
199 /// The address passed to the check is the remote address that the socket is
200 /// attempting to connect to.
201 TcpConnect,
202
203 /// Binding UDP socket.
204 ///
205 /// This is invoked for both explicit calls to `bind` as well as implicit
206 /// binds that are about to be performed by the OS as part of
207 /// e.g. `connect` & `send`.
208 ///
209 /// The address that is passed to the check is the address provided to
210 /// `bind` for explicit binds, or the wildcard address for implicit binds.
211 UdpBind,
212
213 /// Sending a datagram on a UDP socket.
214 ///
215 /// The address passed to the check is the remote address that the socket is
216 /// attempting to send to.
217 UdpSend,
218
219 /// Receiving a datagram on a UDP socket.
220 ///
221 /// The address passed to the check is the remote address of the datagram
222 /// that is being received. If the check fails, the datagram will be
223 /// silently dropped before reaching the guest.
224 UdpReceive,
225}
226
227#[derive(Copy, Clone, Eq, PartialEq)]
228pub(crate) enum SocketAddressFamily {
229 Ipv4,
230 Ipv6,
231}
232
233/// A utility type that separates
234/// (1) polling a future for completion and
235/// (2) obtaining the output of a future
236/// into separate operations. This is a common pattern in WASI 0.2.
237pub(crate) enum MaybeReady<T, F = Pin<Box<dyn Future<Output = T> + Send>>> {
238 Pending(F),
239 Ready(T),
240}
241
242pub(crate) type MaybeSpawned<T> = MaybeReady<T, AbortOnDropJoinHandle<T>>;
243
244impl<T> MaybeSpawned<T> {
245 /// Poll the future and attempt to resolve it immediately. If the future is
246 /// not ready yet, it will be moved to a background task.
247 pub(crate) fn poll_or_spawn(fut: impl Future<Output = T> + Send + 'static) -> Self
248 where
249 T: Send + 'static,
250 {
251 let mut fut = Box::pin(fut);
252 match crate::runtime::with_ambient_tokio_runtime(|| poll_noop(fut.as_mut())) {
253 Some(val) => Self::Ready(val),
254 None => Self::new(crate::runtime::spawn(fut)),
255 }
256 }
257}
258
259impl<T, F> MaybeReady<T, F>
260where
261 F: Future<Output = T> + Unpin,
262{
263 pub(crate) fn new(fut: F) -> Self {
264 Self::Pending(fut)
265 }
266
267 pub(crate) fn unwrap_ready(self) -> T {
268 match self {
269 Self::Ready(val) => val,
270 Self::Pending(_) => panic!("future not ready"),
271 }
272 }
273 pub(crate) fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll<&mut T> {
274 match self {
275 Self::Pending(fut) => match Pin::new(fut).as_mut().poll(cx) {
276 Poll::Ready(val) => {
277 *self = Self::Ready(val);
278 Poll::Ready(match self {
279 Self::Ready(val) => val,
280 _ => unreachable!(),
281 })
282 }
283 Poll::Pending => Poll::Pending,
284 },
285 Self::Ready(val) => Poll::Ready(val),
286 }
287 }
288 pub(crate) async fn into_future(self) -> T {
289 match self {
290 Self::Ready(val) => val,
291 Self::Pending(fut) => fut.await,
292 }
293 }
294}
295
296#[derive(Clone, Copy, Debug)]
297pub enum ErrorCode {
298 AccessDenied,
299 NotSupported,
300 InvalidArgument,
301 OutOfMemory,
302 Timeout,
303 InvalidState,
304 AddressNotBindable,
305 AddressInUse,
306 RemoteUnreachable,
307 ConnectionRefused,
308 ConnectionBroken,
309 ConnectionReset,
310 ConnectionAborted,
311 DatagramTooLarge,
312 Other,
313}
314
315impl fmt::Display for ErrorCode {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 fmt::Debug::fmt(self, f)
318 }
319}
320
321impl std::error::Error for ErrorCode {}
322
323impl From<std::io::Error> for ErrorCode {
324 fn from(value: std::io::Error) -> Self {
325 (&value).into()
326 }
327}
328
329impl From<&std::io::Error> for ErrorCode {
330 fn from(value: &std::io::Error) -> Self {
331 // Attempt the more detailed native error code first:
332 if let Some(errno) = Errno::from_io_error(value) {
333 return errno.into();
334 }
335
336 match value.kind() {
337 std::io::ErrorKind::AddrInUse => Self::AddressInUse,
338 std::io::ErrorKind::AddrNotAvailable => Self::AddressNotBindable,
339 std::io::ErrorKind::ConnectionAborted => Self::ConnectionAborted,
340 std::io::ErrorKind::ConnectionRefused => Self::ConnectionRefused,
341 std::io::ErrorKind::ConnectionReset => Self::ConnectionReset,
342 std::io::ErrorKind::InvalidInput => Self::InvalidArgument,
343 std::io::ErrorKind::NotConnected => Self::InvalidState,
344 std::io::ErrorKind::OutOfMemory => Self::OutOfMemory,
345 std::io::ErrorKind::PermissionDenied => Self::AccessDenied,
346 std::io::ErrorKind::TimedOut => Self::Timeout,
347 std::io::ErrorKind::Unsupported => Self::NotSupported,
348 std::io::ErrorKind::HostUnreachable => Self::RemoteUnreachable,
349 std::io::ErrorKind::NetworkUnreachable => Self::RemoteUnreachable,
350 std::io::ErrorKind::NetworkDown => Self::RemoteUnreachable,
351 std::io::ErrorKind::BrokenPipe => Self::ConnectionBroken,
352 _ => {
353 debug!("unknown I/O error: {value}");
354 Self::Other
355 }
356 }
357 }
358}
359
360impl From<Errno> for ErrorCode {
361 fn from(value: Errno) -> Self {
362 (&value).into()
363 }
364}
365
366impl From<&Errno> for ErrorCode {
367 fn from(value: &Errno) -> Self {
368 match *value {
369 #[cfg(not(windows))]
370 Errno::PERM => Self::AccessDenied,
371 Errno::ACCESS => Self::AccessDenied,
372 Errno::ADDRINUSE => Self::AddressInUse,
373 Errno::ADDRNOTAVAIL => Self::AddressNotBindable,
374 Errno::TIMEDOUT => Self::Timeout,
375 #[cfg(not(windows))]
376 Errno::PIPE => Self::ConnectionBroken,
377 Errno::CONNREFUSED => Self::ConnectionRefused,
378 Errno::CONNRESET => Self::ConnectionReset,
379 Errno::CONNABORTED => Self::ConnectionAborted,
380 Errno::INVAL => Self::InvalidArgument,
381 Errno::HOSTUNREACH => Self::RemoteUnreachable,
382 Errno::HOSTDOWN => Self::RemoteUnreachable,
383 Errno::NETDOWN => Self::RemoteUnreachable,
384 Errno::NETUNREACH => Self::RemoteUnreachable,
385 #[cfg(target_os = "linux")]
386 Errno::NONET => Self::RemoteUnreachable,
387 Errno::ISCONN => Self::InvalidState,
388 Errno::NOTCONN => Self::InvalidState,
389 Errno::DESTADDRREQ => Self::InvalidState,
390 Errno::MSGSIZE => Self::DatagramTooLarge,
391 #[cfg(not(windows))]
392 Errno::NOMEM => Self::OutOfMemory,
393 Errno::NOBUFS => Self::OutOfMemory,
394 Errno::OPNOTSUPP => Self::NotSupported,
395 Errno::NOPROTOOPT => Self::NotSupported,
396 Errno::PFNOSUPPORT => Self::NotSupported,
397 Errno::PROTONOSUPPORT => Self::NotSupported,
398 Errno::PROTOTYPE => Self::NotSupported,
399 Errno::SOCKTNOSUPPORT => Self::NotSupported,
400 Errno::AFNOSUPPORT => Self::NotSupported,
401
402 // FYI, EINPROGRESS should have already been handled by connect.
403 _ => {
404 debug!("unknown I/O error: {value}");
405 Self::Other
406 }
407 }
408 }
409}
410
411fn is_deprecated_ipv4_compatible(addr: Ipv6Addr) -> bool {
412 matches!(addr.segments(), [0, 0, 0, 0, 0, 0, _, _])
413 && addr != Ipv6Addr::UNSPECIFIED
414 && addr != Ipv6Addr::LOCALHOST
415}
416
417pub(crate) fn is_valid_address_family(addr: IpAddr, socket_family: SocketAddressFamily) -> bool {
418 match (socket_family, addr) {
419 (SocketAddressFamily::Ipv4, IpAddr::V4(..)) => true,
420 (SocketAddressFamily::Ipv6, IpAddr::V6(ipv6)) => {
421 // Reject IPv4-*compatible* IPv6 addresses. They have been deprecated
422 // since 2006, OS handling of them is inconsistent and our own
423 // validations don't take them into account either.
424 // Note that these are not the same as IPv4-*mapped* IPv6 addresses.
425 !is_deprecated_ipv4_compatible(ipv6) && ipv6.to_ipv4_mapped().is_none()
426 }
427 _ => false,
428 }
429}
430
431pub(crate) fn is_valid_remote_address(addr: SocketAddr) -> bool {
432 !addr.ip().to_canonical().is_unspecified() && addr.port() != 0
433}
434
435pub(crate) fn is_valid_unicast_address(addr: IpAddr) -> bool {
436 match addr.to_canonical() {
437 IpAddr::V4(ipv4) => !ipv4.is_multicast() && !ipv4.is_broadcast(),
438 IpAddr::V6(ipv6) => !ipv6.is_multicast(),
439 }
440}
441
442pub(crate) fn to_ipv4_addr(addr: (u8, u8, u8, u8)) -> Ipv4Addr {
443 let (x0, x1, x2, x3) = addr;
444 Ipv4Addr::new(x0, x1, x2, x3)
445}
446
447pub(crate) fn from_ipv4_addr(addr: Ipv4Addr) -> (u8, u8, u8, u8) {
448 let [x0, x1, x2, x3] = addr.octets();
449 (x0, x1, x2, x3)
450}
451
452pub(crate) fn to_ipv6_addr(addr: (u16, u16, u16, u16, u16, u16, u16, u16)) -> Ipv6Addr {
453 let (x0, x1, x2, x3, x4, x5, x6, x7) = addr;
454 Ipv6Addr::new(x0, x1, x2, x3, x4, x5, x6, x7)
455}
456
457pub(crate) fn from_ipv6_addr(addr: Ipv6Addr) -> (u16, u16, u16, u16, u16, u16, u16, u16) {
458 let [x0, x1, x2, x3, x4, x5, x6, x7] = addr.segments();
459 (x0, x1, x2, x3, x4, x5, x6, x7)
460}
461
462/*
463 * Syscalls wrappers with (opinionated) portability fixes.
464 */
465
466fn normalize_get_buffer_size(value: usize) -> usize {
467 if cfg!(target_os = "linux") {
468 // Linux doubles the value passed to setsockopt to allow space for bookkeeping overhead.
469 // getsockopt returns this internally doubled value.
470 // We'll half the value to at least get it back into the same ballpark that the application requested it in.
471 //
472 // This normalized behavior is tested for in: test-programs/src/bin/preview2_tcp_sockopts.rs
473 value / 2
474 } else {
475 value
476 }
477}
478
479fn normalize_set_buffer_size(value: usize) -> usize {
480 value.clamp(1, i32::MAX as usize)
481}
482
483fn get_ip_ttl(fd: impl AsFd) -> Result<u8, ErrorCode> {
484 let v = sockopt::ip_ttl(fd)?;
485 let Ok(v) = v.try_into() else {
486 return Err(ErrorCode::NotSupported);
487 };
488 Ok(v)
489}
490
491fn get_ipv6_unicast_hops(fd: impl AsFd) -> Result<u8, ErrorCode> {
492 let v = sockopt::ipv6_unicast_hops(fd)?;
493 Ok(v)
494}
495
496pub(crate) fn get_unicast_hop_limit(
497 fd: impl AsFd,
498 family: SocketAddressFamily,
499) -> Result<u8, ErrorCode> {
500 match family {
501 SocketAddressFamily::Ipv4 => get_ip_ttl(fd),
502 SocketAddressFamily::Ipv6 => get_ipv6_unicast_hops(fd),
503 }
504}
505
506pub(crate) fn set_unicast_hop_limit(
507 fd: impl AsFd,
508 family: SocketAddressFamily,
509 value: u8,
510) -> Result<(), ErrorCode> {
511 if value == 0 {
512 // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
513 //
514 // A well-behaved IP application should never send out new packets with TTL 0.
515 // We validate the value ourselves because OS'es are not consistent in this.
516 // On Linux the validation is even inconsistent between their IPv4 and IPv6 implementation.
517 return Err(ErrorCode::InvalidArgument);
518 }
519 match family {
520 SocketAddressFamily::Ipv4 => {
521 sockopt::set_ip_ttl(fd, value.into())?;
522 }
523 SocketAddressFamily::Ipv6 => {
524 sockopt::set_ipv6_unicast_hops(fd, Some(value))?;
525 }
526 }
527 Ok(())
528}
529
530pub(crate) fn get_receive_buffer_size(fd: impl AsFd) -> Result<u64, ErrorCode> {
531 let v = sockopt::socket_recv_buffer_size(fd)?;
532 Ok(normalize_get_buffer_size(v).try_into().unwrap_or(u64::MAX))
533}
534
535pub(crate) fn set_receive_buffer_size(fd: impl AsFd, value: u64) -> Result<usize, ErrorCode> {
536 if value == 0 {
537 // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
538 return Err(ErrorCode::InvalidArgument);
539 }
540 let value = value.try_into().unwrap_or(usize::MAX);
541 let value = normalize_set_buffer_size(value);
542 match sockopt::set_socket_recv_buffer_size(fd, value) {
543 // Most platforms (Linux, Windows, Fuchsia, Solaris, Illumos, Haiku, ESP-IDF, ..and more?) treat the value
544 // passed to SO_SNDBUF/SO_RCVBUF as a performance tuning hint and silently clamp the input if it exceeds
545 // their capability.
546 // As far as I can see, only the *BSD family views this option as a hard requirement and fails when the
547 // value is out of range. We normalize this behavior in favor of the more commonly understood
548 // "performance hint" semantics. In other words; even ENOBUFS is "Ok".
549 // A future improvement could be to query the corresponding sysctl on *BSD platforms and clamp the input
550 // `size` ourselves, to completely close the gap with other platforms.
551 //
552 // This normalized behavior is tested for in: test-programs/src/bin/preview2_tcp_sockopts.rs
553 Err(Errno::NOBUFS) => {}
554 Err(err) => return Err(err.into()),
555 _ => {}
556 };
557 Ok(value)
558}
559
560pub(crate) fn get_send_buffer_size(fd: impl AsFd) -> Result<u64, ErrorCode> {
561 let v = sockopt::socket_send_buffer_size(fd)?;
562 Ok(normalize_get_buffer_size(v).try_into().unwrap_or(u64::MAX))
563}
564
565pub(crate) fn set_send_buffer_size(fd: impl AsFd, value: u64) -> Result<usize, ErrorCode> {
566 if value == 0 {
567 // WIT: "If the provided value is 0, an `invalid-argument` error is returned."
568 return Err(ErrorCode::InvalidArgument);
569 }
570 let value = value.try_into().unwrap_or(usize::MAX);
571 let value = normalize_set_buffer_size(value);
572 match sockopt::set_socket_send_buffer_size(fd, value) {
573 // See comment in `set_receive_buffer_size` for why we ignore NOBUFS.
574 Err(Errno::NOBUFS) => {}
575 Err(err) => return Err(err.into()),
576 _ => {}
577 };
578 Ok(value)
579}
580
581pub(crate) fn unspecified_addr(family: SocketAddressFamily) -> SocketAddr {
582 let ip = match family {
583 SocketAddressFamily::Ipv4 => IpAddr::V4(Ipv4Addr::UNSPECIFIED),
584 SocketAddressFamily::Ipv6 => IpAddr::V6(Ipv6Addr::UNSPECIFIED),
585 };
586 SocketAddr::new(ip, 0)
587}
588
589/// A helper struct which implements [`HasData`] for the `wasi:sockets` APIs
590/// when used in combination with named imports.
591///
592/// This structure is similar in purpose to [`WasiSockets`] and is used
593/// when using the [`named_imports`] module for `wasi:sockets`. This structure
594/// serves as the `D` type parameter for `add_to_linker` functions.
595///
596/// [`named_imports`]: crate::p3::bindings::named_imports::wasi::sockets
597///
598/// # Meaning of the `T` parameter
599///
600/// Here the `T` must be something that implements [`WasiSocketsNamedView`]. The
601/// corresponding `Data` for this type is [`WasiCtxNamedView`] which internally
602/// will contain `&mut T`.
603///
604/// Effectively you're going to implement [`WasiSocketsNamedView`] for something in
605/// your embedding, and that's the `T` you'll fill in here.
606///
607/// # Examples
608///
609/// ```
610/// use wasmtime::component::{Linker, Component, ResourceTable};
611/// use wasmtime::{Engine, Result};
612/// use wasmtime_wasi::{NamedId, WasiCtxNamedView};
613/// use wasmtime_wasi::sockets::*;
614/// use wasmtime_wasi::p2::bindings::named_imports;
615/// use std::collections::HashMap;
616///
617/// struct MyStoreState {
618/// table: ResourceTable,
619/// states: HashMap<NamedId, WasiSocketsCtx>,
620/// }
621///
622/// fn main() -> Result<()> {
623/// let engine = Engine::default();
624/// let mut linker = Linker::new(&engine);
625/// let component = Component::new(&engine, "(component)")?;
626/// let mut name_map = HashMap::new();
627///
628/// named_imports::wasi::sockets::instance_network::add_to_linker::<MyStoreState, WasiSocketsNamed<MyStoreState>>(
629/// &mut linker,
630/// &component,
631/// |name| {
632/// let len = name_map.len();
633/// Ok(NamedId(*name_map.entry(name.to_string()).or_insert(len)))
634/// },
635/// |state| WasiCtxNamedView(state),
636/// )?;
637/// Ok(())
638/// }
639///
640/// impl WasiSocketsNamedView for MyStoreState {
641/// fn sockets(&mut self, id: NamedId) -> WasiSocketsCtxView<'_> {
642/// let ctx = self.states.get_mut(&id).expect("state for id");
643/// WasiSocketsCtxView {
644/// table: &mut self.table,
645/// ctx,
646/// }
647/// }
648/// }
649/// ```
650pub struct WasiSocketsNamed<T>(marker::PhantomData<fn() -> T>);
651
652impl<T> HasData for WasiSocketsNamed<T>
653where
654 T: WasiSocketsNamedView,
655{
656 type Data<'a> = WasiCtxNamedView<'a, T>;
657}
658
659/// A trait used to look up a specific `wasi:sockets` context for a named
660/// import.
661///
662/// This trait is used in conjunction with the [`named_imports`] bindings
663/// generated for all WASI interfaces. The purpose of this trait is for
664/// embedders to define how a [`NamedId`] maps to a particular `wasi:sockets`
665/// context, here returned as [`WasiSocketsCtxView`]. Embedders are responsible
666/// for assigning meaning to [`NamedId`] values themselves. These IDs are
667/// assigned when [`add_named_to_linker`] is called, for example, as the
668/// `lookup` argument to that function.
669///
670/// When using [`add_named_to_linker`] it's sufficient to implement this trait
671/// for the `T` in `Store<T>`. You can also instead implement the
672/// [`WasiNamedView`] trait for `T` which implies an implementation of this
673/// trait.
674///
675/// When using `add_to_linker` in the generated `bindings::named_imports`
676/// module then values implementing this live within the `T` of `Store<T>`, and
677/// be temporarily referenced in [`WasiCtxNamedView`] where internally that'll
678/// hold `WasiCtxNamedView(&mut your_type)`.
679///
680/// [`named_imports`]: crate::p3::bindings::named_imports
681/// [`add_named_to_linker`]: crate::p3::sockets::add_named_to_linker
682/// [`WasiNamedView`]: crate::WasiNamedView
683///
684/// # Examples
685///
686/// ```
687/// use wasmtime::component::{Linker, Component, ResourceTable};
688/// use wasmtime::{Engine, Result};
689/// use wasmtime_wasi::{NamedId, WasiCtxNamedView};
690/// use wasmtime_wasi::sockets::*;
691/// use std::collections::HashMap;
692///
693/// struct MyStoreState {
694/// table: ResourceTable,
695/// states: HashMap<NamedId, WasiSocketsCtx>,
696/// }
697///
698/// fn main() -> Result<()> {
699/// let engine = Engine::default();
700/// let mut linker = Linker::new(&engine);
701/// let component = Component::new(&engine, "(component)")?;
702/// let mut name_map = HashMap::new();
703///
704/// wasmtime_wasi::p3::sockets::add_named_to_linker::<MyStoreState>(
705/// &mut linker,
706/// &component,
707/// |_, name| {
708/// let len = name_map.len();
709/// Ok(NamedId(*name_map.entry(name.to_string()).or_insert(len)))
710/// },
711/// )?;
712/// Ok(())
713/// }
714///
715/// impl WasiSocketsNamedView for MyStoreState {
716/// fn sockets(&mut self, id: NamedId) -> WasiSocketsCtxView<'_> {
717/// let ctx = self.states.get_mut(&id).expect("state for id");
718/// WasiSocketsCtxView {
719/// table: &mut self.table,
720/// ctx,
721/// }
722/// }
723/// }
724/// ```
725pub trait WasiSocketsNamedView: Send + 'static {
726 /// Looks up the [`WasiSocketsCtxView`] for the given [`NamedId`].
727 ///
728 /// This method will resolve the `id` specified to a specific sockets
729 /// context that is available to be used. Note that this method is
730 /// specifically infallible meaning that a sockets context must be returned
731 /// and this cannot generate a trap or panic or similar.
732 ///
733 /// Embedders are responsible for allocating [`NamedId`] and assigning
734 /// meaning to ids. When a `Linker` is populated embedders will have the
735 /// ability to generate a `NamedId` for all imports found, and then that
736 /// embedder-allocated id is then passed back here when the corresponding
737 /// imported function is invoked.
738 ///
739 /// Note that the [`ResourceTable`] referenced in the returned
740 /// [`WasiSocketsCtxView`] need not be unique. It's ok to use the same
741 /// [`ResourceTable`] for all imports. This is not a guest-visible
742 /// abstraction and just helps the host allocate and manage state.
743 fn sockets(&mut self, id: NamedId) -> WasiSocketsCtxView<'_>;
744}