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}