diff --git a/virtual-adapter/src/io.rs b/virtual-adapter/src/io.rs index 540b3cd..8ad47ab 100644 --- a/virtual-adapter/src/io.rs +++ b/virtual-adapter/src/io.rs @@ -28,13 +28,15 @@ use crate::exports::wasi::io::streams::{ StreamError, }; use crate::exports::wasi::sockets::ip_name_lookup::{ - Guest as IpNameLookup, GuestResolveAddressStream, IpAddress, IpAddressFamily, Network, - ResolveAddressStream, + Guest as IpNameLookup, GuestResolveAddressStream, IpAddress, Network, ResolveAddressStream, }; use crate::exports::wasi::sockets::tcp::{ ErrorCode as NetworkErrorCode, GuestTcpSocket, IpSocketAddress, ShutdownType, TcpSocket, }; -use crate::exports::wasi::sockets::udp::{Datagram, GuestUdpSocket, UdpSocket}; +use crate::exports::wasi::sockets::udp::{ + GuestIncomingDatagramStream, GuestOutgoingDatagramStream, GuestUdpSocket, IncomingDatagram, + OutgoingDatagram, UdpSocket, +}; use crate::wasi::cli::stderr; use crate::wasi::cli::stdin; @@ -42,6 +44,7 @@ use crate::wasi::cli::stdout; use crate::wasi::filesystem::preopens; use crate::wasi::filesystem::types as filesystem_types; use crate::wasi::io::streams; +use crate::wasi::sockets::network::IpAddressFamily; // these are all the subsystems which touch streams + poll use crate::wasi::clocks::monotonic_clock; @@ -422,6 +425,8 @@ pub struct HttpResponseOutparam(http_types::ResponseOutparam); pub struct SocketsResolveAddressStream(ip_name_lookup::ResolveAddressStream); pub struct SocketsTcpSocket(tcp::TcpSocket); pub struct SocketsUdpSocket(udp::UdpSocket); +pub struct SocketsIncomingDatagramStream(udp::IncomingDatagramStream); +pub struct SocketsOutgoingDatagramStream(udp::OutgoingDatagramStream); pub struct IoState { initialized: bool, @@ -684,12 +689,10 @@ impl IpNameLookup for VirtAdapter { fn resolve_addresses( network: &Network, name: String, - address_family: Option, - include_unavailable: bool, ) -> Result, NetworkErrorCode> { debug!("CALL wasi:sockets/ip-name-lookup#resolve-addresses"); Ok(Resource::new(SocketsResolveAddressStream( - ip_name_lookup::resolve_addresses(network, &name, address_family, include_unavailable)?, + ip_name_lookup::resolve_addresses(network, &name)?, ))) } } @@ -1457,41 +1460,17 @@ impl GuestTcpSocket for TcpSocket { debug!("CALL wasi:sockets/tcp#tcp-socket.address-family"); self.0.address_family() } - fn ipv6_only(&self) -> Result { - debug!("CALL wasi:sockets/tcp#tcp-socket.ipv6-only"); - self.0.ipv6_only() - } - fn set_ipv6_only(&self, value: bool) -> Result<(), NetworkErrorCode> { - debug!("CALL wasi:sockets/tcp#tcp-socket.set-ipv6-only"); - self.0.set_ipv6_only(value) - } fn set_listen_backlog_size(&self, value: u64) -> Result<(), NetworkErrorCode> { debug!("CALL wasi:sockets/tcp#tcp-socket.set-listen-backlog-size"); self.0.set_listen_backlog_size(value) } - fn keep_alive(&self) -> Result { - debug!("CALL wasi:sockets/tcp#tcp-socket.keep-alive"); - self.0.keep_alive() - } - fn set_keep_alive(&self, value: bool) -> Result<(), NetworkErrorCode> { - debug!("CALL wasi:sockets/tcp#tcp-socket.set-keep-alive"); - self.0.set_keep_alive(value) - } - fn no_delay(&self) -> Result { - debug!("CALL wasi:sockets/tcp#tcp-socket.no-delay"); - self.0.no_delay() - } - fn set_no_delay(&self, value: bool) -> Result<(), NetworkErrorCode> { - debug!("CALL wasi:sockets/tcp#tcp-socket.set-no-delay"); - self.0.set_no_delay(value) + fn keep_alive_enabled(&self) -> Result { + debug!("CALL wasi:sockets/tcp#tcp-socket.keep-alive-enabled"); + self.0.keep_alive_enabled() } - fn unicast_hop_limit(&self) -> Result { - debug!("CALL wasi:sockets/tcp#tcp-socket.unicast-hop-limit"); - self.0.unicast_hop_limit() - } - fn set_unicast_hop_limit(&self, value: u8) -> Result<(), NetworkErrorCode> { - debug!("CALL wasi:sockets/tcp#tcp-socket.set-unicast-hop-limit"); - self.0.set_unicast_hop_limit(value) + fn set_keep_alive_enabled(&self, value: bool) -> Result<(), NetworkErrorCode> { + debug!("CALL wasi:sockets/tcp#tcp-socket.set-keep-alive-enabled"); + self.0.set_keep_alive_enabled(value) } fn receive_buffer_size(&self) -> Result { debug!("CALL wasi:sockets/tcp#tcp-socket.receive-buffer-size"); @@ -1521,6 +1500,42 @@ impl GuestTcpSocket for TcpSocket { ShutdownType::Both => tcp::ShutdownType::Both, }) } + fn is_listening(&self) -> bool { + debug!("CALL wasi:sockets/tcp#tcp-socket.is-listening"); + self.0.is_listening() + } + fn keep_alive_idle_time(&self) -> Result { + debug!("CALL wasi:sockets/tcp#tcp-socket.keep-alive-idel-time"); + self.0.keep_alive_idle_time() + } + fn set_keep_alive_idle_time(&self, value: u64) -> Result<(), NetworkErrorCode> { + debug!("CALL wasi:sockets/tcp#tcp-socket.set-keep-alive-idel-time"); + self.0.set_keep_alive_idle_time(value) + } + fn keep_alive_interval(&self) -> Result { + debug!("CALL wasi:sockets/tcp#tcp-socket.keep-alive-interval"); + self.0.keep_alive_interval() + } + fn set_keep_alive_interval(&self, value: u64) -> Result<(), NetworkErrorCode> { + debug!("CALL wasi:sockets/tcp#tcp-socket.set-keep-alive-interval"); + self.0.set_keep_alive_interval(value) + } + fn keep_alive_count(&self) -> Result { + debug!("CALL wasi:sockets/tcp#tcp-socket.keep-alive-count"); + self.0.keep_alive_count() + } + fn set_keep_alive_count(&self, value: u32) -> Result<(), NetworkErrorCode> { + debug!("CALL wasi:sockets/tcp#tcp-socket.set-keep-alive-count"); + self.0.set_keep_alive_count(value) + } + fn hop_limit(&self) -> Result { + debug!("CALL wasi:sockets/tcp#tcp-socket.hop-limit"); + self.0.hop_limit() + } + fn set_hop_limit(&self, value: u8) -> Result<(), NetworkErrorCode> { + debug!("CALL wasi:sockets/tcp#tcp-socket.set-hop-limit"); + self.0.set_hop_limit(value) + } } impl GuestUdpSocket for UdpSocket { @@ -1536,44 +1551,6 @@ impl GuestUdpSocket for UdpSocket { debug!("CALL wasi:sockets/udp#udp-socket.finish-bind"); self.0.finish_bind() } - fn start_connect( - &self, - network: &Network, - remote_address: IpSocketAddress, - ) -> Result<(), NetworkErrorCode> { - debug!("CALL wasi:sockets/udp#udp-socket.start-connect"); - self.0.start_connect(network, remote_address) - } - fn finish_connect(&self) -> Result<(), NetworkErrorCode> { - debug!("CALL wasi:sockets/udp#udp-socket.finish-connect"); - self.0.finish_connect() - } - fn receive(&self, max_results: u64) -> Result, NetworkErrorCode> { - debug!("CALL wasi:sockets/udp#udp-socket.receive"); - match self.0.receive(max_results) { - Ok(mut datagrams) => Ok(datagrams - .drain(..) - .map(|d| Datagram { - data: d.data, - remote_address: d.remote_address, - }) - .collect::>()), - Err(err) => Err(err), - } - } - fn send(&self, mut datagrams: Vec) -> Result { - debug!("CALL wasi:sockets/udp#udp-socket.send"); - self.0.send( - datagrams - .drain(..) - .map(|d| udp::Datagram { - data: d.data, - remote_address: d.remote_address, - }) - .collect::>() - .as_slice(), - ) - } fn local_address(&self) -> Result { debug!("CALL wasi:sockets/udp#udp-socket.local-address"); self.0.local_address() @@ -1586,14 +1563,6 @@ impl GuestUdpSocket for UdpSocket { debug!("CALL wasi:sockets/udp#udp-socket.address-family"); self.0.address_family() } - fn ipv6_only(&self) -> Result { - debug!("CALL wasi:sockets/udp#udp-socket.ipv6-only"); - self.0.ipv6_only() - } - fn set_ipv6_only(&self, value: bool) -> Result<(), NetworkErrorCode> { - debug!("CALL wasi:sockets/udp#udp-socket.set-ipv6-only"); - self.0.set_ipv6_only(value) - } fn unicast_hop_limit(&self) -> Result { debug!("CALL wasi:sockets/udp#udp-socket.unicast-hop-limit"); self.0.unicast_hop_limit() @@ -1622,6 +1591,71 @@ impl GuestUdpSocket for UdpSocket { debug!("CALL wasi:sockets/udp#udp-socket.subscribe"); Resource::new(Pollable::Host(self.0.subscribe())) } + fn stream( + &self, + remote_address: Option, + ) -> Result< + ( + Resource, + Resource, + ), + NetworkErrorCode, + > { + debug!("CALL wasi:sockets/udp#udp-socket.stream"); + self.0.stream(remote_address).map(|(in_, out)| { + ( + Resource::new(SocketsIncomingDatagramStream(in_)), + Resource::new(SocketsOutgoingDatagramStream(out)), + ) + }) + } +} + +impl GuestIncomingDatagramStream for SocketsIncomingDatagramStream { + fn receive(&self, max_results: u64) -> Result, NetworkErrorCode> { + debug!("CALL wasi:sockets/udp#incoming-datagram-stream.receive"); + match self.0.receive(max_results) { + Ok(mut datagrams) => Ok(datagrams + .drain(..) + .map(|d| IncomingDatagram { + data: d.data, + remote_address: d.remote_address, + }) + .collect::>()), + Err(err) => Err(err), + } + } + + fn subscribe(&self) -> Resource { + debug!("CALL wasi:sockets/udp#incoming-datagram-stream.subscribe"); + Resource::new(Pollable::Host(self.0.subscribe())) + } +} + +impl GuestOutgoingDatagramStream for SocketsOutgoingDatagramStream { + fn check_send(&self) -> Result { + debug!("CALL wasi:sockets/udp#outgoing-datagram-stream.check-send"); + self.0.check_send() + } + + fn send(&self, mut datagrams: Vec) -> Result { + debug!("CALL wasi:sockets/udp#outgoing-datagram-stream.send"); + self.0.send( + datagrams + .drain(..) + .map(|d| udp::OutgoingDatagram { + data: d.data, + remote_address: d.remote_address, + }) + .collect::>() + .as_slice(), + ) + } + + fn subscribe(&self) -> Resource { + debug!("CALL wasi:sockets/udp#outgoing-datagram-stream.subscribe"); + Resource::new(Pollable::Host(self.0.subscribe())) + } } fn descriptor_ty_map(d: filesystem_types::DescriptorType) -> DescriptorType { diff --git a/virtual-adapter/src/lib.rs b/virtual-adapter/src/lib.rs index 7f57d3d..e3032e7 100644 --- a/virtual-adapter/src/lib.rs +++ b/virtual-adapter/src/lib.rs @@ -44,5 +44,7 @@ wit_bindgen::generate!({ "wasi:sockets/ip-name-lookup/resolve-address-stream": io::SocketsResolveAddressStream, "wasi:sockets/tcp/tcp-socket": io::SocketsTcpSocket, "wasi:sockets/udp/udp-socket": io::SocketsUdpSocket, + "wasi:sockets/udp/incoming-datagram-stream": io::SocketsIncomingDatagramStream, + "wasi:sockets/udp/outgoing-datagram-stream": io::SocketsOutgoingDatagramStream, } }); diff --git a/wit/deps/cli/command.wit b/wit/deps/cli/command.wit index cc82ae5..a988916 100644 --- a/wit/deps/cli/command.wit +++ b/wit/deps/cli/command.wit @@ -1,4 +1,4 @@ -package wasi:cli@0.2.0-rc-2023-12-05; +package wasi:cli@0.2.0-rc-2024-01-16; world command { include imports; diff --git a/wit/deps/cli/imports.wit b/wit/deps/cli/imports.wit index 36b1c59..8ce1abe 100644 --- a/wit/deps/cli/imports.wit +++ b/wit/deps/cli/imports.wit @@ -1,9 +1,9 @@ -package wasi:cli@0.2.0-rc-2023-12-05; +package wasi:cli@0.2.0-rc-2024-01-16; world imports { include wasi:clocks/imports@0.2.0-rc-2023-11-10; include wasi:filesystem/imports@0.2.0-rc-2023-11-10; - include wasi:sockets/imports@0.2.0-rc-2023-10-18; + include wasi:sockets/imports@0.2.0-rc-2024-01-16; include wasi:random/imports@0.2.0-rc-2023-11-10; include wasi:io/imports@0.2.0-rc-2023-11-10; diff --git a/wit/deps/cli/terminal.wit b/wit/deps/cli/terminal.wit index 4749576..38c724e 100644 --- a/wit/deps/cli/terminal.wit +++ b/wit/deps/cli/terminal.wit @@ -1,19 +1,21 @@ +/// Terminal input. +/// +/// In the future, this may include functions for disabling echoing, +/// disabling input buffering so that keyboard events are sent through +/// immediately, querying supported features, and so on. interface terminal-input { /// The input side of a terminal. resource terminal-input; - - // In the future, this may include functions for disabling echoing, - // disabling input buffering so that keyboard events are sent through - // immediately, querying supported features, and so on. } +/// Terminal output. +/// +/// In the future, this may include functions for querying the terminal +/// size, being notified of terminal size changes, querying supported +/// features, and so on. interface terminal-output { /// The output side of a terminal. resource terminal-output; - - // In the future, this may include functions for querying the terminal - // size, being notified of terminal size changes, querying supported - // features, and so on. } /// An interface providing an optional `terminal-input` for stdin as a diff --git a/wit/deps/http/proxy.wit b/wit/deps/http/proxy.wit index 20d4a43..8906da1 100644 --- a/wit/deps/http/proxy.wit +++ b/wit/deps/http/proxy.wit @@ -12,14 +12,14 @@ world proxy { // Proxies have standard output and error streams which are expected to // terminate in a developer-facing console provided by the host. - import wasi:cli/stdout@0.2.0-rc-2023-12-05; - import wasi:cli/stderr@0.2.0-rc-2023-12-05; + import wasi:cli/stdout@0.2.0-rc-2024-01-16; + import wasi:cli/stderr@0.2.0-rc-2024-01-16; // TODO: this is a temporary workaround until component tooling is able to // gracefully handle the absence of stdin. Hosts must return an eof stream // for this import, which is what wasi-libc + tooling will do automatically // when this import is properly removed. - import wasi:cli/stdin@0.2.0-rc-2023-12-05; + import wasi:cli/stdin@0.2.0-rc-2024-01-16; // This is the default handler to use when user code simply wants to make an // HTTP request (e.g., via `fetch()`). diff --git a/wit/deps/io/poll.wit b/wit/deps/io/poll.wit index bddde3c..81b1cab 100644 --- a/wit/deps/io/poll.wit +++ b/wit/deps/io/poll.wit @@ -3,7 +3,7 @@ package wasi:io@0.2.0-rc-2023-11-10; /// A poll API intended to let users wait for I/O events on multiple handles /// at once. interface poll { - /// `pollable` epresents a single I/O event which may be ready, or not. + /// `pollable` represents a single I/O event which may be ready, or not. resource pollable { /// Return the readiness of a pollable. This function never blocks. diff --git a/wit/deps/io/streams.wit b/wit/deps/io/streams.wit index e7e1b68..82e6e07 100644 --- a/wit/deps/io/streams.wit +++ b/wit/deps/io/streams.wit @@ -32,6 +32,11 @@ interface streams { resource input-stream { /// Perform a non-blocking read from the stream. /// + /// When the source of a `read` is binary data, the bytes from the source + /// are returned verbatim. When the source of a `read` is known to the + /// implementation to be text, bytes containing the UTF-8 encoding of the + /// text are returned. + /// /// This function returns a list of bytes containing the read data, /// when successful. The returned list will contain up to `len` bytes; /// it may return fewer than requested, but not more. The list is @@ -111,6 +116,12 @@ interface streams { /// Perform a write. This function never blocks. /// + /// When the destination of a `write` is binary data, the bytes from + /// `contents` are written verbatim. When the destination of a `write` is + /// known to the implementation to be text, the bytes of `contents` are + /// transcoded from UTF-8 into the encoding of the destination and then + /// written. + /// /// Precondition: check-write gave permit of Ok(n) and contents has a /// length of less than or equal to n. Otherwise, this function will trap. /// @@ -131,7 +142,7 @@ interface streams { /// let pollable = this.subscribe(); /// while !contents.is_empty() { /// // Wait for the stream to become writable - /// poll-one(pollable); + /// pollable.block(); /// let Ok(n) = this.check-write(); // eliding error handling /// let len = min(n, contents.len()); /// let (chunk, rest) = contents.split_at(len); @@ -140,7 +151,7 @@ interface streams { /// } /// this.flush(); /// // Wait for completion of `flush` - /// poll-one(pollable); + /// pollable.block(); /// // Check for any errors that arose during `flush` /// let _ = this.check-write(); // eliding error handling /// ``` @@ -178,7 +189,7 @@ interface streams { /// Write zeroes to a stream. /// - /// this should be used precisely like `write` with the exact same + /// This should be used precisely like `write` with the exact same /// preconditions (must use check-write first), but instead of /// passing a list of bytes, you simply pass the number of zero-bytes /// that should be written. @@ -199,7 +210,7 @@ interface streams { /// let pollable = this.subscribe(); /// while num_zeroes != 0 { /// // Wait for the stream to become writable - /// poll-one(pollable); + /// pollable.block(); /// let Ok(n) = this.check-write(); // eliding error handling /// let len = min(n, num_zeroes); /// this.write-zeroes(len); // eliding error handling @@ -207,7 +218,7 @@ interface streams { /// } /// this.flush(); /// // Wait for completion of `flush` - /// poll-one(pollable); + /// pollable.block(); /// // Check for any errors that arose during `flush` /// let _ = this.check-write(); // eliding error handling /// ``` diff --git a/wit/deps/sockets/instance-network.wit b/wit/deps/sockets/instance-network.wit index 14e4479..e455d0f 100644 --- a/wit/deps/sockets/instance-network.wit +++ b/wit/deps/sockets/instance-network.wit @@ -1,9 +1,9 @@ /// This interface provides a value-export of the default network handle.. interface instance-network { - use network.{network}; + use network.{network}; - /// Get a handle to the default network. - instance-network: func() -> network; + /// Get a handle to the default network. + instance-network: func() -> network; } diff --git a/wit/deps/sockets/ip-name-lookup.wit b/wit/deps/sockets/ip-name-lookup.wit index 5e8bd27..931ccf7 100644 --- a/wit/deps/sockets/ip-name-lookup.wit +++ b/wit/deps/sockets/ip-name-lookup.wit @@ -1,61 +1,51 @@ interface ip-name-lookup { - use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; - use network.{network, error-code, ip-address, ip-address-family}; + use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; + use network.{network, error-code, ip-address}; - /// Resolve an internet host name to a list of IP addresses. - /// - /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. - /// - /// # Parameters - /// - `name`: The name to look up. IP addresses are not allowed. Unicode domain names are automatically converted - /// to ASCII using IDNA encoding. - /// - `address-family`: If provided, limit the results to addresses of this specific address family. - /// - `include-unavailable`: When set to true, this function will also return addresses of which the runtime - /// thinks (or knows) can't be connected to at the moment. For example, this will return IPv6 addresses on - /// systems without an active IPv6 interface. Notes: - /// - Even when no public IPv6 interfaces are present or active, names like "localhost" can still resolve to an IPv6 address. - /// - Whatever is "available" or "unavailable" is volatile and can change everytime a network cable is unplugged. - /// - /// This function never blocks. It either immediately fails or immediately returns successfully with a `resolve-address-stream` - /// that can be used to (asynchronously) fetch the results. - /// - /// At the moment, the stream never completes successfully with 0 items. Ie. the first call - /// to `resolve-next-address` never returns `ok(none)`. This may change in the future. - /// - /// # Typical errors - /// - `invalid-argument`: `name` is a syntactically invalid domain name. - /// - `invalid-argument`: `name` is an IP address. - /// - `not-supported`: The specified `address-family` is not supported. (EAI_FAMILY) - /// - /// # References: - /// - - /// - - /// - - /// - - resolve-addresses: func(network: borrow, name: string, address-family: option, include-unavailable: bool) -> result; + /// Resolve an internet host name to a list of IP addresses. + /// + /// Unicode domain names are automatically converted to ASCII using IDNA encoding. + /// If the input is an IP address string, the address is parsed and returned + /// as-is without making any external requests. + /// + /// See the wasi-socket proposal README.md for a comparison with getaddrinfo. + /// + /// This function never blocks. It either immediately fails or immediately + /// returns successfully with a `resolve-address-stream` that can be used + /// to (asynchronously) fetch the results. + /// + /// # Typical errors + /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. + /// + /// # References: + /// - + /// - + /// - + /// - + resolve-addresses: func(network: borrow, name: string) -> result; - resource resolve-address-stream { - /// Returns the next address from the resolver. - /// - /// This function should be called multiple times. On each call, it will - /// return the next address in connection order preference. If all - /// addresses have been exhausted, this function returns `none`. - /// - /// This function never returns IPv4-mapped IPv6 addresses. - /// - /// # Typical errors - /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) - /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) - /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) - /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) - resolve-next-address: func() -> result, error-code>; + resource resolve-address-stream { + /// Returns the next address from the resolver. + /// + /// This function should be called multiple times. On each call, it will + /// return the next address in connection order preference. If all + /// addresses have been exhausted, this function returns `none`. + /// + /// This function never returns IPv4-mapped IPv6 addresses. + /// + /// # Typical errors + /// - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) + /// - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) + /// - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) + /// - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) + resolve-next-address: func() -> result, error-code>; - /// Create a `pollable` which will resolve once the stream is ready for I/O. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func() -> pollable; - } + /// Create a `pollable` which will resolve once the stream is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } } diff --git a/wit/deps/sockets/network.wit b/wit/deps/sockets/network.wit index fc51604..9cadf06 100644 --- a/wit/deps/sockets/network.wit +++ b/wit/deps/sockets/network.wit @@ -1,146 +1,145 @@ interface network { - /// An opaque resource that represents access to (a subset of) the network. - /// This enables context-based security for networking. - /// There is no need for this to map 1:1 to a physical network interface. - resource network; + /// An opaque resource that represents access to (a subset of) the network. + /// This enables context-based security for networking. + /// There is no need for this to map 1:1 to a physical network interface. + resource network; - /// Error codes. - /// - /// In theory, every API can return any error code. - /// In practice, API's typically only return the errors documented per API - /// combined with a couple of errors that are always possible: - /// - `unknown` - /// - `access-denied` - /// - `not-supported` - /// - `out-of-memory` - /// - `concurrency-conflict` - /// - /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. - enum error-code { - // ### GENERAL ERRORS ### + /// Error codes. + /// + /// In theory, every API can return any error code. + /// In practice, API's typically only return the errors documented per API + /// combined with a couple of errors that are always possible: + /// - `unknown` + /// - `access-denied` + /// - `not-supported` + /// - `out-of-memory` + /// - `concurrency-conflict` + /// + /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. + enum error-code { + /// Unknown error + unknown, - /// Unknown error - unknown, + /// Access denied. + /// + /// POSIX equivalent: EACCES, EPERM + access-denied, - /// Access denied. - /// - /// POSIX equivalent: EACCES, EPERM - access-denied, + /// The operation is not supported. + /// + /// POSIX equivalent: EOPNOTSUPP + not-supported, + + /// One of the arguments is invalid. + /// + /// POSIX equivalent: EINVAL + invalid-argument, + + /// Not enough memory to complete the operation. + /// + /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY + out-of-memory, + + /// The operation timed out before it could finish completely. + timeout, + + /// This operation is incompatible with another asynchronous operation that is already in progress. + /// + /// POSIX equivalent: EALREADY + concurrency-conflict, + + /// Trying to finish an asynchronous operation that: + /// - has not been started yet, or: + /// - was already finished by a previous `finish-*` call. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + not-in-progress, + + /// The operation has been aborted because it could not be completed immediately. + /// + /// Note: this is scheduled to be removed when `future`s are natively supported. + would-block, + + + /// The operation is not valid in the socket's current state. + invalid-state, + + /// A new socket resource could not be created because of a system limit. + new-socket-limit, + + /// A bind operation failed because the provided address is not an address that the `network` can bind to. + address-not-bindable, + + /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. + address-in-use, + + /// The remote address is not reachable + remote-unreachable, + + + /// The TCP connection was forcefully rejected + connection-refused, + + /// The TCP connection was reset. + connection-reset, + + /// A TCP connection was aborted. + connection-aborted, + + + /// The size of a datagram sent to a UDP socket exceeded the maximum + /// supported size. + datagram-too-large, + + + /// Name does not exist or has no suitable associated IP addresses. + name-unresolvable, + + /// A temporary failure in name resolution occurred. + temporary-resolver-failure, + + /// A permanent failure in name resolution occurred. + permanent-resolver-failure, + } + + enum ip-address-family { + /// Similar to `AF_INET` in POSIX. + ipv4, + + /// Similar to `AF_INET6` in POSIX. + ipv6, + } + + type ipv4-address = tuple; + type ipv6-address = tuple; + + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } - /// The operation is not supported. - /// - /// POSIX equivalent: EOPNOTSUPP - not-supported, + record ipv4-socket-address { + /// sin_port + port: u16, + /// sin_addr + address: ipv4-address, + } - /// One of the arguments is invalid. - /// - /// POSIX equivalent: EINVAL - invalid-argument, + record ipv6-socket-address { + /// sin6_port + port: u16, + /// sin6_flowinfo + flow-info: u32, + /// sin6_addr + address: ipv6-address, + /// sin6_scope_id + scope-id: u32, + } - /// Not enough memory to complete the operation. - /// - /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY - out-of-memory, - - /// The operation timed out before it could finish completely. - timeout, - - /// This operation is incompatible with another asynchronous operation that is already in progress. - /// - /// POSIX equivalent: EALREADY - concurrency-conflict, - - /// Trying to finish an asynchronous operation that: - /// - has not been started yet, or: - /// - was already finished by a previous `finish-*` call. - /// - /// Note: this is scheduled to be removed when `future`s are natively supported. - not-in-progress, - - /// The operation has been aborted because it could not be completed immediately. - /// - /// Note: this is scheduled to be removed when `future`s are natively supported. - would-block, - - - - // ### TCP & UDP SOCKET ERRORS ### - - /// The operation is not valid in the socket's current state. - invalid-state, - - /// A new socket resource could not be created because of a system limit. - new-socket-limit, - - /// A bind operation failed because the provided address is not an address that the `network` can bind to. - address-not-bindable, - - /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. - address-in-use, - - /// The remote address is not reachable - remote-unreachable, - - - // ### TCP SOCKET ERRORS ### - - /// The connection was forcefully rejected - connection-refused, - - /// The connection was reset. - connection-reset, - - /// A connection was aborted. - connection-aborted, - - // ### UDP SOCKET ERRORS ### - datagram-too-large, - - - // ### NAME LOOKUP ERRORS ### - - /// Name does not exist or has no suitable associated IP addresses. - name-unresolvable, - - /// A temporary failure in name resolution occurred. - temporary-resolver-failure, - - /// A permanent failure in name resolution occurred. - permanent-resolver-failure, - } - - enum ip-address-family { - /// Similar to `AF_INET` in POSIX. - ipv4, - - /// Similar to `AF_INET6` in POSIX. - ipv6, - } - - type ipv4-address = tuple; - type ipv6-address = tuple; - - variant ip-address { - ipv4(ipv4-address), - ipv6(ipv6-address), - } - - record ipv4-socket-address { - port: u16, // sin_port - address: ipv4-address, // sin_addr - } - - record ipv6-socket-address { - port: u16, // sin6_port - flow-info: u32, // sin6_flowinfo - address: ipv6-address, // sin6_addr - scope-id: u32, // sin6_scope_id - } - - variant ip-socket-address { - ipv4(ipv4-socket-address), - ipv6(ipv6-socket-address), - } + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } } diff --git a/wit/deps/sockets/tcp-create-socket.wit b/wit/deps/sockets/tcp-create-socket.wit index a9a3373..c7ddf1f 100644 --- a/wit/deps/sockets/tcp-create-socket.wit +++ b/wit/deps/sockets/tcp-create-socket.wit @@ -1,26 +1,27 @@ interface tcp-create-socket { - use network.{network, error-code, ip-address-family}; - use tcp.{tcp-socket}; + use network.{network, error-code, ip-address-family}; + use tcp.{tcp-socket}; - /// Create a new TCP socket. - /// - /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. - /// - /// This function does not require a network capability handle. This is considered to be safe because - /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`listen`/`connect` - /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. - /// - /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. - /// - /// # Typical errors - /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) - /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) - /// - /// # References - /// - - /// - - /// - - /// - - create-tcp-socket: func(address-family: ip-address-family) -> result; + /// Create a new TCP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_STREAM, IPPROTO_TCP)` in POSIX. + /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` + /// is called, the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + create-tcp-socket: func(address-family: ip-address-family) -> result; } diff --git a/wit/deps/sockets/tcp.wit b/wit/deps/sockets/tcp.wit index 76caf88..9f12b82 100644 --- a/wit/deps/sockets/tcp.wit +++ b/wit/deps/sockets/tcp.wit @@ -1,268 +1,353 @@ interface tcp { - use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream, output-stream}; - use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; - use network.{network, error-code, ip-socket-address, ip-address-family}; + use wasi:io/streams@0.2.0-rc-2023-11-10.{input-stream, output-stream}; + use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; + use wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10.{duration}; + use network.{network, error-code, ip-socket-address, ip-address-family}; - enum shutdown-type { - /// Similar to `SHUT_RD` in POSIX. - receive, + enum shutdown-type { + /// Similar to `SHUT_RD` in POSIX. + receive, - /// Similar to `SHUT_WR` in POSIX. - send, + /// Similar to `SHUT_WR` in POSIX. + send, - /// Similar to `SHUT_RDWR` in POSIX. - both, - } + /// Similar to `SHUT_RDWR` in POSIX. + both, + } + + /// A TCP socket resource. + /// + /// The socket can be in one of the following states: + /// - `unbound` + /// - `bind-in-progress` + /// - `bound` (See note below) + /// - `listen-in-progress` + /// - `listening` + /// - `connect-in-progress` + /// - `connected` + /// - `closed` + /// See + /// for a more information. + /// + /// Note: Except where explicitly mentioned, whenever this documentation uses + /// the term "bound" without backticks it actually means: in the `bound` state *or higher*. + /// (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`) + /// + /// In addition to the general error codes documented on the + /// `network::error-code` type, TCP socket methods may always return + /// `error(invalid-state)` when in the `closed` state. + resource tcp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the TCP/UDP port is zero, the socket will be bound to a random free port. + /// + /// Bind can be attempted multiple times on the same socket, even with + /// different arguments on each iteration. But never concurrently and + /// only as long as the previous bind failed. Once a bind succeeds, the + /// binding can't be changed anymore. + /// + /// # Typical errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) + /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. (EINVAL) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// When binding to a non-zero port, this bind operation shouldn't be affected by the TIME_WAIT + /// state of a recently closed socket on the same local address. In practice this means that the SO_REUSEADDR + /// socket option should be set implicitly on all platforms, except on Windows where this is the default behavior + /// and SO_REUSEADDR performs something different entirely. + /// + /// Unlike in POSIX, in WASI the bind operation is async. This enables + /// interactive WASI hosts to inject permission prompts. Runtimes that + /// don't want to make use of this ability can simply call the native + /// `bind` as part of either `start-bind` or `finish-bind`. + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + /// Connect to a remote endpoint. + /// + /// On success: + /// - the socket is transitioned into the `connection` state. + /// - a pair of streams is returned that can be used to read & write to the connection + /// + /// After a failed connection attempt, the socket will be in the `closed` + /// state and the only valid action left is to `drop` the socket. A single + /// socket can not be used to connect more than once. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) + /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) + /// - `invalid-argument`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. + /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN) + /// - `invalid-state`: The socket is already in the `listening` state. (EOPNOTSUPP, EINVAL on Windows) + /// - `timeout`: Connection timed out. (ETIMEDOUT) + /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `not-in-progress`: A connect operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// The POSIX equivalent of `start-connect` is the regular `connect` syscall. + /// Because all WASI sockets are non-blocking this is expected to return + /// EINPROGRESS, which should be translated to `ok()` in WASI. + /// + /// The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT` + /// with a timeout of 0 on the socket descriptor. Followed by a check for + /// the `SO_ERROR` socket option, in case the poll signaled readiness. + /// + /// # References + /// - + /// - + /// - + /// - + start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; + finish-connect: func() -> result, error-code>; - /// A TCP socket handle. - resource tcp-socket { - /// Bind the socket to a specific network on the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which - /// network interface(s) to bind to. - /// If the TCP/UDP port is zero, the socket will be bound to a random free port. - /// - /// When a socket is not explicitly bound, the first invocation to a listen or connect operation will - /// implicitly bind the socket. - /// - /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. - /// - /// # Typical `start` errors - /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) - /// - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) - /// - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address, but the socket has `ipv6-only` enabled. (EINVAL) - /// - `invalid-state`: The socket is already bound. (EINVAL) - /// - /// # Typical `finish` errors - /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) - /// - `not-in-progress`: A `bind` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; - finish-bind: func() -> result<_, error-code>; + /// Start listening for new connections. + /// + /// Transitions the socket into the `listening` state. + /// + /// Unlike POSIX, the socket must already be explicitly bound. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) + /// - `invalid-state`: The socket is already in the `connected` state. (EISCONN, EINVAL on BSD) + /// - `invalid-state`: The socket is already in the `listening` state. + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + /// - `not-in-progress`: A listen operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// Unlike in POSIX, in WASI the listen operation is async. This enables + /// interactive WASI hosts to inject permission prompts. Runtimes that + /// don't want to make use of this ability can simply call the native + /// `listen` as part of either `start-listen` or `finish-listen`. + /// + /// # References + /// - + /// - + /// - + /// - + start-listen: func() -> result<_, error-code>; + finish-listen: func() -> result<_, error-code>; - /// Connect to a remote endpoint. - /// - /// On success: - /// - the socket is transitioned into the Connection state - /// - a pair of streams is returned that can be used to read & write to the connection - /// - /// POSIX mentions: - /// > If connect() fails, the state of the socket is unspecified. Conforming applications should - /// > close the file descriptor and create a new socket before attempting to reconnect. - /// - /// WASI prescribes the following behavior: - /// - If `connect` fails because an input/state validation error, the socket should remain usable. - /// - If a connection was actually attempted but failed, the socket should become unusable for further network communication. - /// Besides `drop`, any method after such a failure may return an error. - /// - /// # Typical `start` errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) - /// - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address, but the socket has `ipv6-only` enabled. (EINVAL, EADDRNOTAVAIL on Illumos) - /// - `invalid-argument`: `remote-address` is a non-IPv4-mapped IPv6 address, but the socket was bound to a specific IPv4-mapped IPv6 address. (or vice versa) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) - /// - `invalid-argument`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. - /// - `invalid-state`: The socket is already in the Connection state. (EISCONN) - /// - `invalid-state`: The socket is already in the Listener state. (EOPNOTSUPP, EINVAL on Windows) - /// - /// # Typical `finish` errors - /// - `timeout`: Connection timed out. (ETIMEDOUT) - /// - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) - /// - `connection-reset`: The connection was reset. (ECONNRESET) - /// - `connection-aborted`: The connection was aborted. (ECONNABORTED) - /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - `not-in-progress`: A `connect` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; - finish-connect: func() -> result, error-code>; + /// Accept a new client socket. + /// + /// The returned socket is bound and in the `connected` state. The following properties are inherited from the listener socket: + /// - `address-family` + /// - `keep-alive-enabled` + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// - `hop-limit` + /// - `receive-buffer-size` + /// - `send-buffer-size` + /// + /// On success, this function returns the newly accepted client socket along with + /// a pair of streams that can be used to read & write to the connection. + /// + /// # Typical errors + /// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) + /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) + /// - `connection-aborted`: An incoming connection was pending, but was terminated by the client before this listener could accept it. (ECONNABORTED) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References + /// - + /// - + /// - + /// - + accept: func() -> result, error-code>; - /// Start listening for new connections. - /// - /// Transitions the socket into the Listener state. - /// - /// Unlike POSIX: - /// - this function is async. This enables interactive WASI hosts to inject permission prompts. - /// - the socket must already be explicitly bound. - /// - /// # Typical `start` errors - /// - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) - /// - `invalid-state`: The socket is already in the Connection state. (EISCONN, EINVAL on BSD) - /// - `invalid-state`: The socket is already in the Listener state. - /// - /// # Typical `finish` errors - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) - /// - `not-in-progress`: A `listen` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-listen: func() -> result<_, error-code>; - finish-listen: func() -> result<_, error-code>; + /// Get the bound local address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result; - /// Accept a new client socket. - /// - /// The returned socket is bound and in the Connection state. The following properties are inherited from the listener socket: - /// - `address-family` - /// - `ipv6-only` - /// - `keep-alive` - /// - `no-delay` - /// - `unicast-hop-limit` - /// - `receive-buffer-size` - /// - `send-buffer-size` - /// - /// On success, this function returns the newly accepted client socket along with - /// a pair of streams that can be used to read & write to the connection. - /// - /// # Typical errors - /// - `invalid-state`: Socket is not in the Listener state. (EINVAL) - /// - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) - /// - `connection-aborted`: An incoming connection was pending, but was terminated by the client before this listener could accept it. (ECONNABORTED) - /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) - /// - /// # References - /// - - /// - - /// - - /// - - accept: func() -> result, error-code>; + /// Get the remote address. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result; - /// Get the bound local address. - /// - /// POSIX mentions: - /// > If the socket has not been bound to a local name, the value - /// > stored in the object pointed to by `address` is unspecified. - /// - /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - local-address: func() -> result; + /// Whether the socket is in the `listening` state. + /// + /// Equivalent to the SO_ACCEPTCONN socket option. + is-listening: func() -> bool; - /// Get the remote address. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - remote-address: func() -> result; + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family; - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - address-family: func() -> ip-address-family; + /// Hints the desired listen queue size. Implementations are free to ignore this. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// + /// # Typical errors + /// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. + /// - `invalid-argument`: (set) The provided value was 0. + /// - `invalid-state`: (set) The socket is in the `connect-in-progress` or `connected` state. + set-listen-backlog-size: func(value: u64) -> result<_, error-code>; - /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. - /// - /// Equivalent to the IPV6_V6ONLY socket option. - /// - /// # Typical errors - /// - `invalid-state`: (set) The socket is already bound. - /// - `not-supported`: (get/set) `this` socket is an IPv4 socket. - /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) - ipv6-only: func() -> result; - set-ipv6-only: func(value: bool) -> result<_, error-code>; + /// Enables or disables keepalive. + /// + /// The keepalive behavior can be adjusted using: + /// - `keep-alive-idle-time` + /// - `keep-alive-interval` + /// - `keep-alive-count` + /// These properties can be configured while `keep-alive-enabled` is false, but only come into effect when `keep-alive-enabled` is true. + /// + /// Equivalent to the SO_KEEPALIVE socket option. + keep-alive-enabled: func() -> result; + set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; - /// Hints the desired listen queue size. Implementations are free to ignore this. - /// - /// # Typical errors - /// - `not-supported`: (set) The platform does not support changing the backlog size after the initial listen. - /// - `invalid-state`: (set) The socket is already in the Connection state. - set-listen-backlog-size: func(value: u64) -> result<_, error-code>; + /// Amount of time the connection has to be idle before TCP starts sending keepalive packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-idle-time: func() -> result; + set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; - /// Equivalent to the SO_KEEPALIVE socket option. - keep-alive: func() -> result; - set-keep-alive: func(value: bool) -> result<_, error-code>; + /// The time between keepalive packets. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPINTVL socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-interval: func() -> result; + set-keep-alive-interval: func(value: duration) -> result<_, error-code>; - /// Equivalent to the TCP_NODELAY socket option. - /// - /// The default value is `false`. - no-delay: func() -> result; - set-no-delay: func(value: bool) -> result<_, error-code>; + /// The maximum amount of keepalive packets TCP should send before aborting the connection. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the TCP_KEEPCNT socket option. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + keep-alive-count: func() -> result; + set-keep-alive-count: func(value: u32) -> result<_, error-code>; - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - /// - /// # Typical errors - /// - `invalid-argument`: (set) The TTL value must be 1 or higher. - /// - `invalid-state`: (set) The socket is already in the Connection state. - /// - `invalid-state`: (set) The socket is already in the Listener state. - unicast-hop-limit: func() -> result; - set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + hop-limit: func() -> result; + set-hop-limit: func(value: u8) -> result<_, error-code>; - /// The kernel buffer space reserved for sends/receives on this socket. - /// - /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. - /// In other words, after setting a value, reading the same setting back may return a different value. - /// - /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of - /// actual data to be sent/received by the application, because the kernel might also use the buffer space - /// for internal metadata structures. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - /// - /// # Typical errors - /// - `invalid-state`: (set) The socket is already in the Connection state. - /// - `invalid-state`: (set) The socket is already in the Listener state. - receive-buffer-size: func() -> result; - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - send-buffer-size: func() -> result; - set-send-buffer-size: func(value: u64) -> result<_, error-code>; + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; - /// Create a `pollable` which will resolve once the socket is ready for I/O. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func() -> pollable; + /// Create a `pollable` which can be used to poll for, or block on, + /// completion of any of the asynchronous operations of this socket. + /// + /// When `finish-bind`, `finish-listen`, `finish-connect` or `accept` + /// return `error(would-block)`, this pollable can be used to wait for + /// their success or failure, after which the method can be retried. + /// + /// The pollable is not limited to the async operation that happens to be + /// in progress at the time of calling `subscribe` (if any). Theoretically, + /// `subscribe` only has to be called once per socket and can then be + /// (re)used for the remainder of the socket's lifetime. + /// + /// See + /// for a more information. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; - /// Initiate a graceful shutdown. - /// - /// - receive: the socket is not expecting to receive any more data from the peer. All subsequent read - /// operations on the `input-stream` associated with this socket will return an End Of Stream indication. - /// Any data still in the receive queue at time of calling `shutdown` will be discarded. - /// - send: the socket is not expecting to send any more data to the peer. All subsequent write - /// operations on the `output-stream` associated with this socket will return an error. - /// - both: same effect as receive & send combined. - /// - /// The shutdown function does not close (drop) the socket. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not in the Connection state. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; - } + /// Initiate a graceful shutdown. + /// + /// - `receive`: The socket is not expecting to receive any data from + /// the peer. The `input-stream` associated with this socket will be + /// closed. Any data still in the receive queue at time of calling + /// this method will be discarded. + /// - `send`: The socket has no more data to send to the peer. The `output-stream` + /// associated with this socket will be closed and a FIN packet will be sent. + /// - `both`: Same effect as `receive` & `send` combined. + /// + /// This function is idempotent. Shutting a down a direction more than once + /// has no effect and returns `ok`. + /// + /// The shutdown function does not close (drop) the socket. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; + } } diff --git a/wit/deps/sockets/udp-create-socket.wit b/wit/deps/sockets/udp-create-socket.wit index e026359..0482d1f 100644 --- a/wit/deps/sockets/udp-create-socket.wit +++ b/wit/deps/sockets/udp-create-socket.wit @@ -1,26 +1,27 @@ interface udp-create-socket { - use network.{network, error-code, ip-address-family}; - use udp.{udp-socket}; + use network.{network, error-code, ip-address-family}; + use udp.{udp-socket}; - /// Create a new UDP socket. - /// - /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. - /// - /// This function does not require a network capability handle. This is considered to be safe because - /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind`/`connect` is called, - /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. - /// - /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. - /// - /// # Typical errors - /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) - /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) - /// - /// # References: - /// - - /// - - /// - - /// - - create-udp-socket: func(address-family: ip-address-family) -> result; + /// Create a new UDP socket. + /// + /// Similar to `socket(AF_INET or AF_INET6, SOCK_DGRAM, IPPROTO_UDP)` in POSIX. + /// On IPv6 sockets, IPV6_V6ONLY is enabled by default and can't be configured otherwise. + /// + /// This function does not require a network capability handle. This is considered to be safe because + /// at time of creation, the socket is not bound to any `network` yet. Up to the moment `bind` is called, + /// the socket is effectively an in-memory configuration object, unable to communicate with the outside world. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # Typical errors + /// - `not-supported`: The specified `address-family` is not supported. (EAFNOSUPPORT) + /// - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) + /// + /// # References: + /// - + /// - + /// - + /// - + create-udp-socket: func(address-family: ip-address-family) -> result; } diff --git a/wit/deps/sockets/udp.wit b/wit/deps/sockets/udp.wit index 4c1a9f2..6ba380f 100644 --- a/wit/deps/sockets/udp.wit +++ b/wit/deps/sockets/udp.wit @@ -1,213 +1,266 @@ interface udp { - use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; - use network.{network, error-code, ip-socket-address, ip-address-family}; - - - record datagram { - data: list, // Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. - remote-address: ip-socket-address, - - /// Possible future additions: - /// local-address: ip-socket-address, // IP_PKTINFO / IP_RECVDSTADDR / IPV6_PKTINFO - /// local-interface: u32, // IP_PKTINFO / IP_RECVIF - /// ttl: u8, // IP_RECVTTL - /// dscp: u6, // IP_RECVTOS - /// ecn: u2, // IP_RECVTOS - } - - - - /// A UDP socket handle. - resource udp-socket { - /// Bind the socket to a specific network on the provided IP address and port. - /// - /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which - /// network interface(s) to bind to. - /// If the TCP/UDP port is zero, the socket will be bound to a random free port. - /// - /// When a socket is not explicitly bound, the first invocation to connect will implicitly bind the socket. - /// - /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. - /// - /// # Typical `start` errors - /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) - /// - `invalid-state`: The socket is already bound. (EINVAL) - /// - /// # Typical `finish` errors - /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) - /// - `address-in-use`: Address is already in use. (EADDRINUSE) - /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) - /// - `not-in-progress`: A `bind` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; - finish-bind: func() -> result<_, error-code>; - - /// Set the destination address. - /// - /// The local-address is updated based on the best network path to `remote-address`. - /// - /// When a destination address is set: - /// - all receive operations will only return datagrams sent from the provided `remote-address`. - /// - the `send` function can only be used to send to this destination. - /// - /// Note that this function does not generate any network traffic and the peer is not aware of this "connection". - /// - /// Unlike in POSIX, this function is async. This enables interactive WASI hosts to inject permission prompts. - /// - /// # Typical `start` errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-argument`: `remote-address` is a non-IPv4-mapped IPv6 address, but the socket was bound to a specific IPv4-mapped IPv6 address. (or vice versa) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The socket is already bound to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. - /// - /// # Typical `finish` errors - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - `not-in-progress`: A `connect` operation is not in progress. - /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - start-connect: func(network: borrow, remote-address: ip-socket-address) -> result<_, error-code>; - finish-connect: func() -> result<_, error-code>; - - /// Receive messages on the socket. - /// - /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. - /// The returned list may contain fewer elements than requested, but never more. - /// If `max-results` is 0, this function returns successfully with an empty list. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. (EINVAL) - /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) - /// - `would-block`: There is no pending data available to be read at the moment. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - receive: func(max-results: u64) -> result, error-code>; - - /// Send messages on the socket. - /// - /// This function attempts to send all provided `datagrams` on the socket without blocking and - /// returns how many messages were actually sent (or queued for sending). - /// - /// This function semantically behaves the same as iterating the `datagrams` list and sequentially - /// sending each individual datagram until either the end of the list has been reached or the first error occurred. - /// If at least one datagram has been sent successfully, this function never returns an error. - /// - /// If the input list is empty, the function returns `ok(0)`. - /// - /// The remote address option is required. To send a message to the "connected" peer, - /// call `remote-address` to get their address. - /// - /// # Typical errors - /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) - /// - `invalid-argument`: `remote-address` is a non-IPv4-mapped IPv6 address, but the socket was bound to a specific IPv4-mapped IPv6 address. (or vice versa) - /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) - /// - `invalid-argument`: The socket is in "connected" mode and the `datagram.remote-address` does not match the address passed to `connect`. (EISCONN) - /// - `invalid-state`: The socket is not bound to any local address. Unlike POSIX, this function does not perform an implicit bind. - /// - `remote-unreachable`: The remote address is not reachable. (ECONNREFUSED, ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN) - /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) - /// - `would-block`: The send buffer is currently full. (EWOULDBLOCK, EAGAIN) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - send: func(datagrams: list) -> result; - - /// Get the current bound address. - /// - /// POSIX mentions: - /// > If the socket has not been bound to a local name, the value - /// > stored in the object pointed to by `address` is unspecified. - /// - /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not bound to any local address. - /// - /// # References - /// - - /// - - /// - - /// - - local-address: func() -> result; - - /// Get the address set with `connect`. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - remote-address: func() -> result; - - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - address-family: func() -> ip-address-family; - - /// Whether IPv4 compatibility (dual-stack) mode is disabled or not. - /// - /// Equivalent to the IPV6_V6ONLY socket option. - /// - /// # Typical errors - /// - `not-supported`: (get/set) `this` socket is an IPv4 socket. - /// - `invalid-state`: (set) The socket is already bound. - /// - `not-supported`: (set) Host does not support dual-stack sockets. (Implementations are not required to.) - ipv6-only: func() -> result; - set-ipv6-only: func(value: bool) -> result<_, error-code>; - - /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. - unicast-hop-limit: func() -> result; - set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; - - /// The kernel buffer space reserved for sends/receives on this socket. - /// - /// Note #1: an implementation may choose to cap or round the buffer size when setting the value. - /// In other words, after setting a value, reading the same setting back may return a different value. - /// - /// Note #2: there is not necessarily a direct relationship between the kernel buffer size and the bytes of - /// actual data to be sent/received by the application, because the kernel might also use the buffer space - /// for internal metadata structures. - /// - /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. - receive-buffer-size: func() -> result; - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - send-buffer-size: func() -> result; - set-send-buffer-size: func(value: u64) -> result<_, error-code>; - - /// Create a `pollable` which will resolve once the socket is ready for I/O. - /// - /// Note: this function is here for WASI Preview2 only. - /// It's planned to be removed when `future` is natively supported in Preview3. - subscribe: func() -> pollable; - } + use wasi:io/poll@0.2.0-rc-2023-11-10.{pollable}; + use network.{network, error-code, ip-socket-address, ip-address-family}; + + /// A received datagram. + record incoming-datagram { + /// The payload. + /// + /// Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. + data: list, + + /// The source address. + /// + /// This field is guaranteed to match the remote address the stream was initialized with, if any. + /// + /// Equivalent to the `src_addr` out parameter of `recvfrom`. + remote-address: ip-socket-address, + } + + /// A datagram to be sent out. + record outgoing-datagram { + /// The payload. + data: list, + + /// The destination address. + /// + /// The requirements on this field depend on how the stream was initialized: + /// - with a remote address: this field must be None or match the stream's remote address exactly. + /// - without a remote address: this field is required. + /// + /// If this value is None, the send operation is equivalent to `send` in POSIX. Otherwise it is equivalent to `sendto`. + remote-address: option, + } + + + + /// A UDP socket handle. + resource udp-socket { + /// Bind the socket to a specific network on the provided IP address and port. + /// + /// If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which + /// network interface(s) to bind to. + /// If the port is zero, the socket will be bound to a random free port. + /// + /// # Typical errors + /// - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) + /// - `invalid-state`: The socket is already bound. (EINVAL) + /// - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) + /// - `address-in-use`: Address is already in use. (EADDRINUSE) + /// - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) + /// - `not-in-progress`: A `bind` operation is not in progress. + /// - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) + /// + /// # Implementors note + /// Unlike in POSIX, in WASI the bind operation is async. This enables + /// interactive WASI hosts to inject permission prompts. Runtimes that + /// don't want to make use of this ability can simply call the native + /// `bind` as part of either `start-bind` or `finish-bind`. + /// + /// # References + /// - + /// - + /// - + /// - + start-bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + finish-bind: func() -> result<_, error-code>; + + /// Set up inbound & outbound communication channels, optionally to a specific peer. + /// + /// This function only changes the local socket configuration and does not generate any network traffic. + /// On success, the `remote-address` of the socket is updated. The `local-address` may be updated as well, + /// based on the best network path to `remote-address`. + /// + /// When a `remote-address` is provided, the returned streams are limited to communicating with that specific peer: + /// - `send` can only be used to send to this destination. + /// - `receive` will only return datagrams sent from the provided `remote-address`. + /// + /// This method may be called multiple times on the same socket to change its association, but + /// only the most recently returned pair of streams will be operational. Implementations may trap if + /// the streams returned by a previous invocation haven't been dropped yet before calling `stream` again. + /// + /// The POSIX equivalent in pseudo-code is: + /// ```text + /// if (was previously connected) { + /// connect(s, AF_UNSPEC) + /// } + /// if (remote_address is Some) { + /// connect(s, remote_address) + /// } + /// ``` + /// + /// Unlike in POSIX, the socket must already be explicitly bound. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-state`: The socket is not bound. + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// + /// # References + /// - + /// - + /// - + /// - + %stream: func(remote-address: option) -> result, error-code>; + + /// Get the current bound address. + /// + /// POSIX mentions: + /// > If the socket has not been bound to a local name, the value + /// > stored in the object pointed to by `address` is unspecified. + /// + /// WASI is stricter and requires `local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not bound to any local address. + /// + /// # References + /// - + /// - + /// - + /// - + local-address: func() -> result; + + /// Get the address the socket is currently streaming to. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + remote-address: func() -> result; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + address-family: func() -> ip-address-family; + + /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The TTL value must be 1 or higher. + unicast-hop-limit: func() -> result; + set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; + + /// The kernel buffer space reserved for sends/receives on this socket. + /// + /// If the provided value is 0, an `invalid-argument` error is returned. + /// Any other value will never cause an error, but it might be silently clamped and/or rounded. + /// I.e. after setting a value, reading the same setting back may return a different value. + /// + /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. + /// + /// # Typical errors + /// - `invalid-argument`: (set) The provided value was 0. + receive-buffer-size: func() -> result; + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + send-buffer-size: func() -> result; + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + + /// Create a `pollable` which will resolve once the socket is ready for I/O. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } + + resource incoming-datagram-stream { + /// Receive messages on the socket. + /// + /// This function attempts to receive up to `max-results` datagrams on the socket without blocking. + /// The returned list may contain fewer elements than requested, but never more. + /// + /// This function returns successfully with an empty list when either: + /// - `max-results` is 0, or: + /// - `max-results` is greater than 0, but no results are immediately available. + /// This function never returns `error(would-block)`. + /// + /// # Typical errors + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + receive: func(max-results: u64) -> result, error-code>; + + /// Create a `pollable` which will resolve once the stream is ready to receive again. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } + + resource outgoing-datagram-stream { + /// Check readiness for sending. This function never blocks. + /// + /// Returns the number of datagrams permitted for the next call to `send`, + /// or an error. Calling `send` with more datagrams than this function has + /// permitted will trap. + /// + /// When this function returns ok(0), the `subscribe` pollable will + /// become ready when this function will report at least ok(1), or an + /// error. + /// + /// Never returns `would-block`. + check-send: func() -> result; + + /// Send messages on the socket. + /// + /// This function attempts to send all provided `datagrams` on the socket without blocking and + /// returns how many messages were actually sent (or queued for sending). This function never + /// returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` is returned. + /// + /// This function semantically behaves the same as iterating the `datagrams` list and sequentially + /// sending each individual datagram until either the end of the list has been reached or the first error occurred. + /// If at least one datagram has been sent successfully, this function never returns an error. + /// + /// If the input list is empty, the function returns `ok(0)`. + /// + /// Each call to `send` must be permitted by a preceding `check-send`. Implementations must trap if + /// either `check-send` was not called or `datagrams` contains more items than `check-send` permitted. + /// + /// # Typical errors + /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) + /// - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) + /// - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `stream`. (EISCONN) + /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) + /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// - `connection-refused`: The connection was refused. (ECONNREFUSED) + /// - `datagram-too-large`: The datagram is too large. (EMSGSIZE) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + send: func(datagrams: list) -> result; + + /// Create a `pollable` which will resolve once the stream is ready to send again. + /// + /// Note: this function is here for WASI Preview2 only. + /// It's planned to be removed when `future` is natively supported in Preview3. + subscribe: func() -> pollable; + } } diff --git a/wit/deps/sockets/world.wit b/wit/deps/sockets/world.wit index d16530c..8588cc6 100644 --- a/wit/deps/sockets/world.wit +++ b/wit/deps/sockets/world.wit @@ -1,4 +1,4 @@ -package wasi:sockets@0.2.0-rc-2023-10-18; +package wasi:sockets@0.2.0-rc-2024-01-16; world imports { import instance-network; diff --git a/wit/virt.wit b/wit/virt.wit index 64cbca0..81de6bc 100644 --- a/wit/virt.wit +++ b/wit/virt.wit @@ -3,7 +3,7 @@ package local:virt; // in future this should be defined as a union world of the various // virtual subsystems, when union syntax lands world virtual-adapter { - import wasi:cli/environment@0.2.0-rc-2023-12-05; + import wasi:cli/environment@0.2.0-rc-2024-01-16; import wasi:filesystem/preopens@0.2.0-rc-2023-11-10; import wasi:filesystem/types@0.2.0-rc-2023-11-10; import wasi:io/error@0.2.0-rc-2023-11-10; @@ -12,37 +12,37 @@ world virtual-adapter { export wasi:io/streams@0.2.0-rc-2023-11-10; import wasi:io/poll@0.2.0-rc-2023-11-10; export wasi:io/poll@0.2.0-rc-2023-11-10; - export wasi:cli/environment@0.2.0-rc-2023-12-05; + export wasi:cli/environment@0.2.0-rc-2024-01-16; export wasi:filesystem/types@0.2.0-rc-2023-11-10; export wasi:filesystem/preopens@0.2.0-rc-2023-11-10; - import wasi:cli/stdin@0.2.0-rc-2023-12-05; - import wasi:cli/stdout@0.2.0-rc-2023-12-05; - import wasi:cli/stderr@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-input@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-output@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-stdin@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-stdout@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-stderr@0.2.0-rc-2023-12-05; - export wasi:cli/stdin@0.2.0-rc-2023-12-05; - export wasi:cli/stdout@0.2.0-rc-2023-12-05; - export wasi:cli/stderr@0.2.0-rc-2023-12-05; - export wasi:cli/terminal-input@0.2.0-rc-2023-12-05; - export wasi:cli/terminal-output@0.2.0-rc-2023-12-05; - export wasi:cli/terminal-stdin@0.2.0-rc-2023-12-05; - export wasi:cli/terminal-stdout@0.2.0-rc-2023-12-05; - export wasi:cli/terminal-stderr@0.2.0-rc-2023-12-05; + import wasi:cli/stdin@0.2.0-rc-2024-01-16; + import wasi:cli/stdout@0.2.0-rc-2024-01-16; + import wasi:cli/stderr@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-input@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-output@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-stdin@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-stdout@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-stderr@0.2.0-rc-2024-01-16; + export wasi:cli/stdin@0.2.0-rc-2024-01-16; + export wasi:cli/stdout@0.2.0-rc-2024-01-16; + export wasi:cli/stderr@0.2.0-rc-2024-01-16; + export wasi:cli/terminal-input@0.2.0-rc-2024-01-16; + export wasi:cli/terminal-output@0.2.0-rc-2024-01-16; + export wasi:cli/terminal-stdin@0.2.0-rc-2024-01-16; + export wasi:cli/terminal-stdout@0.2.0-rc-2024-01-16; + export wasi:cli/terminal-stderr@0.2.0-rc-2024-01-16; import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10; export wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10; import wasi:http/types@0.2.0-rc-2023-10-18; export wasi:http/types@0.2.0-rc-2023-10-18; import wasi:http/outgoing-handler@0.2.0-rc-2023-10-18; export wasi:http/outgoing-handler@0.2.0-rc-2023-10-18; - import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-10-18; - export wasi:sockets/ip-name-lookup@0.2.0-rc-2023-10-18; - import wasi:sockets/tcp@0.2.0-rc-2023-10-18; - export wasi:sockets/tcp@0.2.0-rc-2023-10-18; - import wasi:sockets/udp@0.2.0-rc-2023-10-18; - export wasi:sockets/udp@0.2.0-rc-2023-10-18; + import wasi:sockets/ip-name-lookup@0.2.0-rc-2024-01-16; + export wasi:sockets/ip-name-lookup@0.2.0-rc-2024-01-16; + import wasi:sockets/tcp@0.2.0-rc-2024-01-16; + export wasi:sockets/tcp@0.2.0-rc-2024-01-16; + import wasi:sockets/udp@0.2.0-rc-2024-01-16; + export wasi:sockets/udp@0.2.0-rc-2024-01-16; } world virtual-base { @@ -61,12 +61,12 @@ world virtual-io { // where there is an intersection of // streams + poll world virtual-io-sockets { - import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-10-18; - export wasi:sockets/ip-name-lookup@0.2.0-rc-2023-10-18; - import wasi:sockets/tcp@0.2.0-rc-2023-10-18; - export wasi:sockets/tcp@0.2.0-rc-2023-10-18; - import wasi:sockets/udp@0.2.0-rc-2023-10-18; - export wasi:sockets/udp@0.2.0-rc-2023-10-18; + import wasi:sockets/ip-name-lookup@0.2.0-rc-2024-01-16; + export wasi:sockets/ip-name-lookup@0.2.0-rc-2024-01-16; + import wasi:sockets/tcp@0.2.0-rc-2024-01-16; + export wasi:sockets/tcp@0.2.0-rc-2024-01-16; + import wasi:sockets/udp@0.2.0-rc-2024-01-16; + export wasi:sockets/udp@0.2.0-rc-2024-01-16; } world virtual-io-clocks { @@ -89,28 +89,28 @@ world virtual-fs { } world virtual-stdio { - import wasi:cli/stdin@0.2.0-rc-2023-12-05; - import wasi:cli/stdout@0.2.0-rc-2023-12-05; - import wasi:cli/stderr@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-input@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-output@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-stdin@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-stdout@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-stderr@0.2.0-rc-2023-12-05; - export wasi:cli/stdin@0.2.0-rc-2023-12-05; - export wasi:cli/stdout@0.2.0-rc-2023-12-05; - export wasi:cli/stderr@0.2.0-rc-2023-12-05; - export wasi:cli/terminal-input@0.2.0-rc-2023-12-05; - export wasi:cli/terminal-output@0.2.0-rc-2023-12-05; - export wasi:cli/terminal-stdin@0.2.0-rc-2023-12-05; - export wasi:cli/terminal-stdout@0.2.0-rc-2023-12-05; - export wasi:cli/terminal-stderr@0.2.0-rc-2023-12-05; + import wasi:cli/stdin@0.2.0-rc-2024-01-16; + import wasi:cli/stdout@0.2.0-rc-2024-01-16; + import wasi:cli/stderr@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-input@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-output@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-stdin@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-stdout@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-stderr@0.2.0-rc-2024-01-16; + export wasi:cli/stdin@0.2.0-rc-2024-01-16; + export wasi:cli/stdout@0.2.0-rc-2024-01-16; + export wasi:cli/stderr@0.2.0-rc-2024-01-16; + export wasi:cli/terminal-input@0.2.0-rc-2024-01-16; + export wasi:cli/terminal-output@0.2.0-rc-2024-01-16; + export wasi:cli/terminal-stdin@0.2.0-rc-2024-01-16; + export wasi:cli/terminal-stdout@0.2.0-rc-2024-01-16; + export wasi:cli/terminal-stderr@0.2.0-rc-2024-01-16; } // remaining subsystems world virtual-env { - import wasi:cli/environment@0.2.0-rc-2023-12-05; - export wasi:cli/environment@0.2.0-rc-2023-12-05; + import wasi:cli/environment@0.2.0-rc-2024-01-16; + export wasi:cli/environment@0.2.0-rc-2024-01-16; } world virtual-clocks { @@ -130,20 +130,20 @@ world virtual-random { } world virtual-sockets { - import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-10-18; - export wasi:sockets/ip-name-lookup@0.2.0-rc-2023-10-18; - import wasi:sockets/tcp@0.2.0-rc-2023-10-18; - export wasi:sockets/tcp@0.2.0-rc-2023-10-18; - import wasi:sockets/udp@0.2.0-rc-2023-10-18; - export wasi:sockets/udp@0.2.0-rc-2023-10-18; - import wasi:sockets/instance-network@0.2.0-rc-2023-10-18; - import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-10-18; - import wasi:sockets/udp-create-socket@0.2.0-rc-2023-10-18; - export wasi:sockets/instance-network@0.2.0-rc-2023-10-18; - export wasi:sockets/tcp-create-socket@0.2.0-rc-2023-10-18; - export wasi:sockets/udp-create-socket@0.2.0-rc-2023-10-18; - import wasi:sockets/network@0.2.0-rc-2023-10-18; - export wasi:sockets/network@0.2.0-rc-2023-10-18; + import wasi:sockets/ip-name-lookup@0.2.0-rc-2024-01-16; + export wasi:sockets/ip-name-lookup@0.2.0-rc-2024-01-16; + import wasi:sockets/tcp@0.2.0-rc-2024-01-16; + export wasi:sockets/tcp@0.2.0-rc-2024-01-16; + import wasi:sockets/udp@0.2.0-rc-2024-01-16; + export wasi:sockets/udp@0.2.0-rc-2024-01-16; + import wasi:sockets/instance-network@0.2.0-rc-2024-01-16; + import wasi:sockets/tcp-create-socket@0.2.0-rc-2024-01-16; + import wasi:sockets/udp-create-socket@0.2.0-rc-2024-01-16; + export wasi:sockets/instance-network@0.2.0-rc-2024-01-16; + export wasi:sockets/tcp-create-socket@0.2.0-rc-2024-01-16; + export wasi:sockets/udp-create-socket@0.2.0-rc-2024-01-16; + import wasi:sockets/network@0.2.0-rc-2024-01-16; + export wasi:sockets/network@0.2.0-rc-2024-01-16; } world virtual-http { @@ -156,37 +156,37 @@ world virtual-http { } world virtual-exit { - import wasi:cli/exit@0.2.0-rc-2023-12-05; - export wasi:cli/exit@0.2.0-rc-2023-12-05; + import wasi:cli/exit@0.2.0-rc-2024-01-16; + export wasi:cli/exit@0.2.0-rc-2024-01-16; } world virt-test { import wasi:clocks/wall-clock@0.2.0-rc-2023-11-10; import wasi:clocks/monotonic-clock@0.2.0-rc-2023-11-10; import wasi:filesystem/types@0.2.0-rc-2023-11-10; - import wasi:sockets/instance-network@0.2.0-rc-2023-10-18; - import wasi:sockets/ip-name-lookup@0.2.0-rc-2023-10-18; - import wasi:sockets/network@0.2.0-rc-2023-10-18; - import wasi:sockets/tcp-create-socket@0.2.0-rc-2023-10-18; - import wasi:sockets/tcp@0.2.0-rc-2023-10-18; - import wasi:sockets/udp-create-socket@0.2.0-rc-2023-10-18; - import wasi:sockets/udp@0.2.0-rc-2023-10-18; + import wasi:sockets/instance-network@0.2.0-rc-2024-01-16; + import wasi:sockets/ip-name-lookup@0.2.0-rc-2024-01-16; + import wasi:sockets/network@0.2.0-rc-2024-01-16; + import wasi:sockets/tcp-create-socket@0.2.0-rc-2024-01-16; + import wasi:sockets/tcp@0.2.0-rc-2024-01-16; + import wasi:sockets/udp-create-socket@0.2.0-rc-2024-01-16; + import wasi:sockets/udp@0.2.0-rc-2024-01-16; import wasi:random/random@0.2.0-rc-2023-11-10; import wasi:random/insecure@0.2.0-rc-2023-11-10; import wasi:random/insecure-seed@0.2.0-rc-2023-11-10; import wasi:io/poll@0.2.0-rc-2023-11-10; import wasi:io/streams@0.2.0-rc-2023-11-10; - import wasi:cli/environment@0.2.0-rc-2023-12-05; + import wasi:cli/environment@0.2.0-rc-2024-01-16; import wasi:filesystem/preopens@0.2.0-rc-2023-11-10; - import wasi:cli/exit@0.2.0-rc-2023-12-05; - import wasi:cli/stdin@0.2.0-rc-2023-12-05; - import wasi:cli/stdout@0.2.0-rc-2023-12-05; - import wasi:cli/stderr@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-input@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-output@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-stdin@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-stdout@0.2.0-rc-2023-12-05; - import wasi:cli/terminal-stderr@0.2.0-rc-2023-12-05; + import wasi:cli/exit@0.2.0-rc-2024-01-16; + import wasi:cli/stdin@0.2.0-rc-2024-01-16; + import wasi:cli/stdout@0.2.0-rc-2024-01-16; + import wasi:cli/stderr@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-input@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-output@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-stdin@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-stdout@0.2.0-rc-2024-01-16; + import wasi:cli/terminal-stderr@0.2.0-rc-2024-01-16; export test-get-env: func() -> list>; export test-file-read: func(path: string) -> string;