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