Skip to main content

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}