From d0f8ab57f3383fb5184757ee8aadc9d8974e0165 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 14 Jan 2025 12:29:41 +0100 Subject: [PATCH 01/36] feat: add `wasi-0.3.0` draft Followed the example of `wasi-http` and `wasi-clocks` duplicating the package in a subdirectory - In wasip3 return values of functions are pollable by guests, therefore remove abstractions for which there is no need anymore: - Replace `start-X`/`finish-X` function pairs by `X` - Remove `would-block` error code - Remove `not-in-progress` error code - Replace `wasi:io/error.error` usage by `error-context` - Replace `wasi:io/streams.input-stream` return values by `stream` in return position - Replace `wasi:io/streams.output-stream` return values by `stream` in parameter position - Guests should be able to rely on `stream.new` to construct streams Signed-off-by: Roman Volosatovs --- wit-0.3.0-draft/deps.lock | 4 + wit-0.3.0-draft/deps.toml | 1 + .../deps/clocks/monotonic-clock.wit | 45 +++ wit-0.3.0-draft/deps/clocks/timezone.wit | 55 +++ wit-0.3.0-draft/deps/clocks/wall-clock.wit | 46 +++ wit-0.3.0-draft/deps/clocks/world.wit | 11 + wit-0.3.0-draft/instance-network.wit | 11 + wit-0.3.0-draft/ip-name-lookup.wit | 46 +++ wit-0.3.0-draft/network.wit | 153 ++++++++ wit-0.3.0-draft/tcp-create-socket.wit | 30 ++ wit-0.3.0-draft/tcp.wit | 331 ++++++++++++++++++ wit-0.3.0-draft/udp-create-socket.wit | 30 ++ wit-0.3.0-draft/udp.wit | 208 +++++++++++ wit-0.3.0-draft/world.wit | 19 + 14 files changed, 990 insertions(+) create mode 100644 wit-0.3.0-draft/deps.lock create mode 100644 wit-0.3.0-draft/deps.toml create mode 100644 wit-0.3.0-draft/deps/clocks/monotonic-clock.wit create mode 100644 wit-0.3.0-draft/deps/clocks/timezone.wit create mode 100644 wit-0.3.0-draft/deps/clocks/wall-clock.wit create mode 100644 wit-0.3.0-draft/deps/clocks/world.wit create mode 100644 wit-0.3.0-draft/instance-network.wit create mode 100644 wit-0.3.0-draft/ip-name-lookup.wit create mode 100644 wit-0.3.0-draft/network.wit create mode 100644 wit-0.3.0-draft/tcp-create-socket.wit create mode 100644 wit-0.3.0-draft/tcp.wit create mode 100644 wit-0.3.0-draft/udp-create-socket.wit create mode 100644 wit-0.3.0-draft/udp.wit create mode 100644 wit-0.3.0-draft/world.wit diff --git a/wit-0.3.0-draft/deps.lock b/wit-0.3.0-draft/deps.lock new file mode 100644 index 0000000..20306c6 --- /dev/null +++ b/wit-0.3.0-draft/deps.lock @@ -0,0 +1,4 @@ +[clocks] +url = "https://github.com/WebAssembly/wasi-clocks/archive/main.tar.gz" +sha256 = "26e315db0d371495f8834edfc0e479042f94152ce677d96d54d3623d0e4ffb1e" +sha512 = "e1c76f499435841316f9287b88d8173558e64f277c321ff390556de8707a0b18dd6c1749bbb17bbbba8d523da246ef6eb05c990ceddb762e03efb2ae30cacc76" diff --git a/wit-0.3.0-draft/deps.toml b/wit-0.3.0-draft/deps.toml new file mode 100644 index 0000000..3f6ad6d --- /dev/null +++ b/wit-0.3.0-draft/deps.toml @@ -0,0 +1 @@ +clocks = "https://github.com/WebAssembly/wasi-clocks/archive/main.tar.gz" diff --git a/wit-0.3.0-draft/deps/clocks/monotonic-clock.wit b/wit-0.3.0-draft/deps/clocks/monotonic-clock.wit new file mode 100644 index 0000000..87ebdaa --- /dev/null +++ b/wit-0.3.0-draft/deps/clocks/monotonic-clock.wit @@ -0,0 +1,45 @@ +package wasi:clocks@0.3.0; +/// WASI Monotonic Clock is a clock API intended to let users measure elapsed +/// time. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A monotonic clock is a clock which has an unspecified initial value, and +/// successive reads of the clock will produce non-decreasing values. +@since(version = 0.3.0) +interface monotonic-clock { + /// An instant in time, in nanoseconds. An instant is relative to an + /// unspecified initial value, and can only be compared to instances from + /// the same monotonic-clock. + @since(version = 0.3.0) + type instant = u64; + + /// A duration of time, in nanoseconds. + @since(version = 0.3.0) + type duration = u64; + + /// Read the current value of the clock. + /// + /// The clock is monotonic, therefore calling this function repeatedly will + /// produce a sequence of non-decreasing values. + @since(version = 0.3.0) + now: func() -> instant; + + /// Query the resolution of the clock. Returns the duration of time + /// corresponding to a clock tick. + @since(version = 0.3.0) + resolution: func() -> duration; + + /// Wait until the specified instant has occurred. + @since(version = 0.3.0) + wait-until: func( + when: instant, + ); + + /// Wait for the specified duration has elapsed. + @since(version = 0.3.0) + wait-for: func( + how-long: duration, + ); +} diff --git a/wit-0.3.0-draft/deps/clocks/timezone.wit b/wit-0.3.0-draft/deps/clocks/timezone.wit new file mode 100644 index 0000000..ac91468 --- /dev/null +++ b/wit-0.3.0-draft/deps/clocks/timezone.wit @@ -0,0 +1,55 @@ +package wasi:clocks@0.3.0; + +@unstable(feature = clocks-timezone) +interface timezone { + @unstable(feature = clocks-timezone) + use wall-clock.{datetime}; + + /// Return information needed to display the given `datetime`. This includes + /// the UTC offset, the time zone name, and a flag indicating whether + /// daylight saving time is active. + /// + /// If the timezone cannot be determined for the given `datetime`, return a + /// `timezone-display` for `UTC` with a `utc-offset` of 0 and no daylight + /// saving time. + @unstable(feature = clocks-timezone) + display: func(when: datetime) -> timezone-display; + + /// The same as `display`, but only return the UTC offset. + @unstable(feature = clocks-timezone) + utc-offset: func(when: datetime) -> s32; + + /// Information useful for displaying the timezone of a specific `datetime`. + /// + /// This information may vary within a single `timezone` to reflect daylight + /// saving time adjustments. + @unstable(feature = clocks-timezone) + record timezone-display { + /// The number of seconds difference between UTC time and the local + /// time of the timezone. + /// + /// The returned value will always be less than 86400 which is the + /// number of seconds in a day (24*60*60). + /// + /// In implementations that do not expose an actual time zone, this + /// should return 0. + utc-offset: s32, + + /// The abbreviated name of the timezone to display to a user. The name + /// `UTC` indicates Coordinated Universal Time. Otherwise, this should + /// reference local standards for the name of the time zone. + /// + /// In implementations that do not expose an actual time zone, this + /// should be the string `UTC`. + /// + /// In time zones that do not have an applicable name, a formatted + /// representation of the UTC offset may be returned, such as `-04:00`. + name: string, + + /// Whether daylight saving time is active. + /// + /// In implementations that do not expose an actual time zone, this + /// should return false. + in-daylight-saving-time: bool, + } +} diff --git a/wit-0.3.0-draft/deps/clocks/wall-clock.wit b/wit-0.3.0-draft/deps/clocks/wall-clock.wit new file mode 100644 index 0000000..b7a85ab --- /dev/null +++ b/wit-0.3.0-draft/deps/clocks/wall-clock.wit @@ -0,0 +1,46 @@ +package wasi:clocks@0.3.0; +/// WASI Wall Clock is a clock API intended to let users query the current +/// time. The name "wall" makes an analogy to a "clock on the wall", which +/// is not necessarily monotonic as it may be reset. +/// +/// It is intended to be portable at least between Unix-family platforms and +/// Windows. +/// +/// A wall clock is a clock which measures the date and time according to +/// some external reference. +/// +/// External references may be reset, so this clock is not necessarily +/// monotonic, making it unsuitable for measuring elapsed time. +/// +/// It is intended for reporting the current date and time for humans. +@since(version = 0.3.0) +interface wall-clock { + /// A time and date in seconds plus nanoseconds. + @since(version = 0.3.0) + record datetime { + seconds: u64, + nanoseconds: u32, + } + + /// Read the current value of the clock. + /// + /// This clock is not monotonic, therefore calling this function repeatedly + /// will not necessarily produce a sequence of non-decreasing values. + /// + /// The returned timestamps represent the number of seconds since + /// 1970-01-01T00:00:00Z, also known as [POSIX's Seconds Since the Epoch], + /// also known as [Unix Time]. + /// + /// The nanoseconds field of the output is always less than 1000000000. + /// + /// [POSIX's Seconds Since the Epoch]: https://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xbd_chap04.html#tag_21_04_16 + /// [Unix Time]: https://en.wikipedia.org/wiki/Unix_time + @since(version = 0.3.0) + now: func() -> datetime; + + /// Query the resolution of the clock. + /// + /// The nanoseconds field of the output is always less than 1000000000. + @since(version = 0.3.0) + resolution: func() -> datetime; +} diff --git a/wit-0.3.0-draft/deps/clocks/world.wit b/wit-0.3.0-draft/deps/clocks/world.wit new file mode 100644 index 0000000..f97bcfe --- /dev/null +++ b/wit-0.3.0-draft/deps/clocks/world.wit @@ -0,0 +1,11 @@ +package wasi:clocks@0.3.0; + +@since(version = 0.3.0) +world imports { + @since(version = 0.3.0) + import monotonic-clock; + @since(version = 0.3.0) + import wall-clock; + @unstable(feature = clocks-timezone) + import timezone; +} diff --git a/wit-0.3.0-draft/instance-network.wit b/wit-0.3.0-draft/instance-network.wit new file mode 100644 index 0000000..5f6e6c1 --- /dev/null +++ b/wit-0.3.0-draft/instance-network.wit @@ -0,0 +1,11 @@ + +/// This interface provides a value-export of the default network handle.. +@since(version = 0.2.0) +interface instance-network { + @since(version = 0.2.0) + use network.{network}; + + /// Get a handle to the default network. + @since(version = 0.2.0) + instance-network: func() -> network; +} diff --git a/wit-0.3.0-draft/ip-name-lookup.wit b/wit-0.3.0-draft/ip-name-lookup.wit new file mode 100644 index 0000000..dd26e6a --- /dev/null +++ b/wit-0.3.0-draft/ip-name-lookup.wit @@ -0,0 +1,46 @@ +@since(version = 0.2.0) +interface ip-name-lookup { + @since(version = 0.2.0) + use network.{network, error-code, ip-address}; + + /// 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: + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + resolve-addresses: func(network: borrow, name: string) -> result; + + @since(version = 0.2.0) + 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) + @since(version = 0.2.0) + resolve-next-address: func() -> result, error-code>; + } +} diff --git a/wit-0.3.0-draft/network.wit b/wit-0.3.0-draft/network.wit new file mode 100644 index 0000000..c66efec --- /dev/null +++ b/wit-0.3.0-draft/network.wit @@ -0,0 +1,153 @@ +@since(version = 0.2.0) +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. + @since(version = 0.2.0) + 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. + @since(version = 0.2.0) + enum error-code { + /// Unknown error + unknown, + + /// 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, + + /// 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, + } + + /// Attempts to extract a network-related `error-code` from the stream + /// `error` provided. + /// + /// Stream operations which return `stream-error::last-operation-failed` + /// have a payload with more information about the operation that failed. + /// This payload can be passed through to this function to see if there's + /// network-related information about the error to return. + /// + /// Note that this function is fallible because not all stream-related + /// errors are network-related errors. + @unstable(feature = network-error-code) + network-error-code: func(err: borrow) -> option; + + @since(version = 0.2.0) + enum ip-address-family { + /// Similar to `AF_INET` in POSIX. + ipv4, + + /// Similar to `AF_INET6` in POSIX. + ipv6, + } + + @since(version = 0.2.0) + type ipv4-address = tuple; + @since(version = 0.2.0) + type ipv6-address = tuple; + + @since(version = 0.2.0) + variant ip-address { + ipv4(ipv4-address), + ipv6(ipv6-address), + } + + @since(version = 0.2.0) + record ipv4-socket-address { + /// sin_port + port: u16, + /// sin_addr + address: ipv4-address, + } + + @since(version = 0.2.0) + record ipv6-socket-address { + /// sin6_port + port: u16, + /// sin6_flowinfo + flow-info: u32, + /// sin6_addr + address: ipv6-address, + /// sin6_scope_id + scope-id: u32, + } + + @since(version = 0.2.0) + variant ip-socket-address { + ipv4(ipv4-socket-address), + ipv6(ipv6-socket-address), + } +} diff --git a/wit-0.3.0-draft/tcp-create-socket.wit b/wit-0.3.0-draft/tcp-create-socket.wit new file mode 100644 index 0000000..eedbd30 --- /dev/null +++ b/wit-0.3.0-draft/tcp-create-socket.wit @@ -0,0 +1,30 @@ +@since(version = 0.2.0) +interface tcp-create-socket { + @since(version = 0.2.0) + use network.{network, error-code, ip-address-family}; + @since(version = 0.2.0) + use tcp.{tcp-socket}; + + /// 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 + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + create-tcp-socket: func(address-family: ip-address-family) -> result; +} diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit new file mode 100644 index 0000000..23ab05d --- /dev/null +++ b/wit-0.3.0-draft/tcp.wit @@ -0,0 +1,331 @@ +@since(version = 0.2.0) +interface tcp { + @since(version = 0.2.0) + use wasi:clocks/monotonic-clock@0.3.0.{duration}; + @since(version = 0.2.0) + use network.{network, error-code, ip-socket-address, ip-address-family}; + + @since(version = 0.2.0) + enum shutdown-type { + /// Similar to `SHUT_RD` in POSIX. + receive, + + /// Similar to `SHUT_WR` in POSIX. + send, + + /// 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 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. + @since(version = 0.2.0) + 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) + /// + /// # 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. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + + /// Connect to a remote endpoint. + /// This function takes a stream as a parameter, which will be used for data transmission to the server. + /// + /// On success: + /// - the socket is transitioned into the `connected` state. + /// - a stream is returned that can be used to read from 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) + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + connect: func(network: borrow, remote-address: ip-socket-address, tx: stream) -> 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) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + listen: func() -> result<_, error-code>; + + /// Accept a new client socket. + /// This function takes a stream as a parameter, which will be used for data transmission to the client. + /// + /// 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 streams that can be used to read from the connection. + /// + /// # Typical errors + /// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) + /// - `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 + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + accept: func(tx: stream) -> 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 + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + local-address: func() -> result; + + /// Get the remote address. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + remote-address: func() -> result; + + /// Whether the socket is in the `listening` state. + /// + /// Equivalent to the SO_ACCEPTCONN socket option. + @since(version = 0.2.0) + is-listening: func() -> bool; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + @since(version = 0.2.0) + 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. + @since(version = 0.2.0) + set-listen-backlog-size: func(value: u64) -> 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. + @since(version = 0.2.0) + keep-alive-enabled: func() -> result; + @since(version = 0.2.0) + set-keep-alive-enabled: func(value: bool) -> 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. + @since(version = 0.2.0) + keep-alive-idle-time: func() -> result; + @since(version = 0.2.0) + set-keep-alive-idle-time: func(value: duration) -> 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. + @since(version = 0.2.0) + keep-alive-interval: func() -> result; + @since(version = 0.2.0) + set-keep-alive-interval: func(value: duration) -> 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. + @since(version = 0.2.0) + keep-alive-count: func() -> result; + @since(version = 0.2.0) + set-keep-alive-count: func(value: u32) -> 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. + @since(version = 0.2.0) + hop-limit: func() -> result; + @since(version = 0.2.0) + set-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. + @since(version = 0.2.0) + receive-buffer-size: func() -> result; + @since(version = 0.2.0) + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + @since(version = 0.2.0) + send-buffer-size: func() -> result; + @since(version = 0.2.0) + set-send-buffer-size: func(value: u64) -> 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 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 + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; + } +} diff --git a/wit-0.3.0-draft/udp-create-socket.wit b/wit-0.3.0-draft/udp-create-socket.wit new file mode 100644 index 0000000..e8eeacb --- /dev/null +++ b/wit-0.3.0-draft/udp-create-socket.wit @@ -0,0 +1,30 @@ +@since(version = 0.2.0) +interface udp-create-socket { + @since(version = 0.2.0) + use network.{network, error-code, ip-address-family}; + @since(version = 0.2.0) + use udp.{udp-socket}; + + /// 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: + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + create-udp-socket: func(address-family: ip-address-family) -> result; +} diff --git a/wit-0.3.0-draft/udp.wit b/wit-0.3.0-draft/udp.wit new file mode 100644 index 0000000..0f9e1c2 --- /dev/null +++ b/wit-0.3.0-draft/udp.wit @@ -0,0 +1,208 @@ +@since(version = 0.2.0) +interface udp { + @since(version = 0.2.0) + use network.{network, error-code, ip-socket-address, ip-address-family}; + + /// A received datagram. + @since(version = 0.2.0) + 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. + @since(version = 0.2.0) + 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. + @since(version = 0.2.0) + 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) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + bind: func(network: borrow, local-address: ip-socket-address) -> 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 + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + %stream: func(remote-address: option) -> 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). + /// 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)`. + /// + /// # 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 + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + 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 + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + 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 + /// - + /// - + /// - + /// - + @since(version = 0.2.0) + remote-address: func() -> result; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + @since(version = 0.2.0) + 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. + @since(version = 0.2.0) + unicast-hop-limit: func() -> result; + @since(version = 0.2.0) + 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. + @since(version = 0.2.0) + receive-buffer-size: func() -> result; + @since(version = 0.2.0) + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + @since(version = 0.2.0) + send-buffer-size: func() -> result; + @since(version = 0.2.0) + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + } +} diff --git a/wit-0.3.0-draft/world.wit b/wit-0.3.0-draft/world.wit new file mode 100644 index 0000000..e7ba6a1 --- /dev/null +++ b/wit-0.3.0-draft/world.wit @@ -0,0 +1,19 @@ +package wasi:sockets@0.3.0-draft; + +@since(version = 0.2.0) +world imports { + @since(version = 0.2.0) + import instance-network; + @since(version = 0.2.0) + import network; + @since(version = 0.2.0) + import udp; + @since(version = 0.2.0) + import udp-create-socket; + @since(version = 0.2.0) + import tcp; + @since(version = 0.2.0) + import tcp-create-socket; + @since(version = 0.2.0) + import ip-name-lookup; +} From 8cc04c8cd1e4634c16549e21981a76485319aa1d Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 14 Jan 2025 15:06:32 +0100 Subject: [PATCH 02/36] refactor: replace `error-context` introspection by futures This seems to be better aligned with latest specification on error context https://github.com/WebAssembly/component-model/blob/cbdd15d9033446558571824af52a78022aaa3f58/design/mvp/Explainer.md#error-context-type > A consequence of this, however, is that components *must not* depend on the > contents of `error-context` values for behavioral correctness. In particular, > case analysis of the contents of an `error-context` should not determine > *error recovery*; explicit `result` or `variant` types must be used in the > function return type instead (e.g., > `(func (result (tuple (stream u8) (future $my-error)))`). Signed-off-by: Roman Volosatovs --- wit-0.3.0-draft/network.wit | 13 ------------- wit-0.3.0-draft/tcp.wit | 24 +++++++++++++++++------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/wit-0.3.0-draft/network.wit b/wit-0.3.0-draft/network.wit index c66efec..bbe138f 100644 --- a/wit-0.3.0-draft/network.wit +++ b/wit-0.3.0-draft/network.wit @@ -92,19 +92,6 @@ interface network { permanent-resolver-failure, } - /// Attempts to extract a network-related `error-code` from the stream - /// `error` provided. - /// - /// Stream operations which return `stream-error::last-operation-failed` - /// have a payload with more information about the operation that failed. - /// This payload can be passed through to this function to see if there's - /// network-related information about the error to return. - /// - /// Note that this function is fallible because not all stream-related - /// errors are network-related errors. - @unstable(feature = network-error-code) - network-error-code: func(err: borrow) -> option; - @since(version = 0.2.0) enum ip-address-family { /// Similar to `AF_INET` in POSIX. diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index 23ab05d..58fdd6b 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -77,9 +77,13 @@ interface tcp { /// Connect to a remote endpoint. /// This function takes a stream as a parameter, which will be used for data transmission to the server. /// - /// On success: - /// - the socket is transitioned into the `connected` state. - /// - a stream is returned that can be used to read from the connection + /// On success, the socket is transitioned into the `connected` state and this function returns: + /// - Future, which will resolve to an optional error code if writing full contents of the stream to the connection fails. + /// The future resolves to `none` once full contents of the stream are transmitted successfully and the writing side of the + /// connection is closed. + /// - A stream that can be used to read from the connection + /// - Future, which will resolve to an optional error code if reading contents from the connection fails. + /// The future resolves to `none` once receiving side of the stream is closed. /// /// 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 @@ -106,7 +110,7 @@ interface tcp { /// - /// - @since(version = 0.3.0) - connect: func(network: borrow, remote-address: ip-socket-address, tx: stream) -> result, error-code>; + connect: func(network: borrow, remote-address: ip-socket-address, tx: stream) -> result>, stream, future>>, error-code>; /// Start listening for new connections. /// @@ -141,8 +145,14 @@ interface tcp { /// - `receive-buffer-size` /// - `send-buffer-size` /// - /// On success, this function returns the newly accepted client socket along with - /// a streams that can be used to read from the connection. + /// On success, this function returns: + /// - Future, which will resolve to an optional error code if writing full contents of the stream to the connection fails. + /// The future resolves to `none` once full contents of the stream are transmitted successfully and the writing side of the + /// connection is closed. + /// - Newly accepted client socket + /// - A stream that can be used to read from the connection + /// - Future, which will resolve to an optional error code if reading contents from the connection fails. + /// The future resolves to `none` once receiving side of the stream is closed. /// /// # Typical errors /// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) @@ -155,7 +165,7 @@ interface tcp { /// - /// - @since(version = 0.3.0) - accept: func(tx: stream) -> result>, error-code>; + accept: func(tx: stream) -> result>, tcp-socket, stream, future>>, error-code>; /// Get the bound local address. /// From 8a3b48272fd55895105cdb537dad894d0440cdb9 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 14 Jan 2025 16:24:58 +0100 Subject: [PATCH 03/36] chore: bump `@since` to `0.3.0` https://github.com/WebAssembly/wasi-filesystem/pull/164#discussion_r1914946117 Signed-off-by: Roman Volosatovs --- wit-0.3.0-draft/instance-network.wit | 6 ++-- wit-0.3.0-draft/ip-name-lookup.wit | 10 +++--- wit-0.3.0-draft/network.wit | 20 +++++------ wit-0.3.0-draft/tcp-create-socket.wit | 8 ++--- wit-0.3.0-draft/tcp.wit | 50 +++++++++++++-------------- wit-0.3.0-draft/udp-create-socket.wit | 8 ++--- wit-0.3.0-draft/udp.wit | 28 +++++++-------- wit-0.3.0-draft/world.wit | 16 ++++----- 8 files changed, 73 insertions(+), 73 deletions(-) diff --git a/wit-0.3.0-draft/instance-network.wit b/wit-0.3.0-draft/instance-network.wit index 5f6e6c1..a9e54ef 100644 --- a/wit-0.3.0-draft/instance-network.wit +++ b/wit-0.3.0-draft/instance-network.wit @@ -1,11 +1,11 @@ /// This interface provides a value-export of the default network handle.. -@since(version = 0.2.0) +@since(version = 0.3.0) interface instance-network { - @since(version = 0.2.0) + @since(version = 0.3.0) use network.{network}; /// Get a handle to the default network. - @since(version = 0.2.0) + @since(version = 0.3.0) instance-network: func() -> network; } diff --git a/wit-0.3.0-draft/ip-name-lookup.wit b/wit-0.3.0-draft/ip-name-lookup.wit index dd26e6a..2be9bc0 100644 --- a/wit-0.3.0-draft/ip-name-lookup.wit +++ b/wit-0.3.0-draft/ip-name-lookup.wit @@ -1,6 +1,6 @@ -@since(version = 0.2.0) +@since(version = 0.3.0) interface ip-name-lookup { - @since(version = 0.2.0) + @since(version = 0.3.0) use network.{network, error-code, ip-address}; /// Resolve an internet host name to a list of IP addresses. @@ -23,10 +23,10 @@ interface ip-name-lookup { /// - /// - /// - - @since(version = 0.2.0) + @since(version = 0.3.0) resolve-addresses: func(network: borrow, name: string) -> result; - @since(version = 0.2.0) + @since(version = 0.3.0) resource resolve-address-stream { /// Returns the next address from the resolver. /// @@ -40,7 +40,7 @@ interface ip-name-lookup { /// - `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) - @since(version = 0.2.0) + @since(version = 0.3.0) resolve-next-address: func() -> result, error-code>; } } diff --git a/wit-0.3.0-draft/network.wit b/wit-0.3.0-draft/network.wit index bbe138f..21644ba 100644 --- a/wit-0.3.0-draft/network.wit +++ b/wit-0.3.0-draft/network.wit @@ -1,9 +1,9 @@ -@since(version = 0.2.0) +@since(version = 0.3.0) 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. - @since(version = 0.2.0) + @since(version = 0.3.0) resource network; /// Error codes. @@ -18,7 +18,7 @@ interface network { /// - `concurrency-conflict` /// /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. - @since(version = 0.2.0) + @since(version = 0.3.0) enum error-code { /// Unknown error unknown, @@ -92,7 +92,7 @@ interface network { permanent-resolver-failure, } - @since(version = 0.2.0) + @since(version = 0.3.0) enum ip-address-family { /// Similar to `AF_INET` in POSIX. ipv4, @@ -101,18 +101,18 @@ interface network { ipv6, } - @since(version = 0.2.0) + @since(version = 0.3.0) type ipv4-address = tuple; - @since(version = 0.2.0) + @since(version = 0.3.0) type ipv6-address = tuple; - @since(version = 0.2.0) + @since(version = 0.3.0) variant ip-address { ipv4(ipv4-address), ipv6(ipv6-address), } - @since(version = 0.2.0) + @since(version = 0.3.0) record ipv4-socket-address { /// sin_port port: u16, @@ -120,7 +120,7 @@ interface network { address: ipv4-address, } - @since(version = 0.2.0) + @since(version = 0.3.0) record ipv6-socket-address { /// sin6_port port: u16, @@ -132,7 +132,7 @@ interface network { scope-id: u32, } - @since(version = 0.2.0) + @since(version = 0.3.0) variant ip-socket-address { ipv4(ipv4-socket-address), ipv6(ipv6-socket-address), diff --git a/wit-0.3.0-draft/tcp-create-socket.wit b/wit-0.3.0-draft/tcp-create-socket.wit index eedbd30..3b62062 100644 --- a/wit-0.3.0-draft/tcp-create-socket.wit +++ b/wit-0.3.0-draft/tcp-create-socket.wit @@ -1,8 +1,8 @@ -@since(version = 0.2.0) +@since(version = 0.3.0) interface tcp-create-socket { - @since(version = 0.2.0) + @since(version = 0.3.0) use network.{network, error-code, ip-address-family}; - @since(version = 0.2.0) + @since(version = 0.3.0) use tcp.{tcp-socket}; /// Create a new TCP socket. @@ -25,6 +25,6 @@ interface tcp-create-socket { /// - /// - /// - - @since(version = 0.2.0) + @since(version = 0.3.0) create-tcp-socket: func(address-family: ip-address-family) -> result; } diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index 58fdd6b..a6fd58e 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -1,11 +1,11 @@ -@since(version = 0.2.0) +@since(version = 0.3.0) interface tcp { - @since(version = 0.2.0) + @since(version = 0.3.0) use wasi:clocks/monotonic-clock@0.3.0.{duration}; - @since(version = 0.2.0) + @since(version = 0.3.0) use network.{network, error-code, ip-socket-address, ip-address-family}; - @since(version = 0.2.0) + @since(version = 0.3.0) enum shutdown-type { /// Similar to `SHUT_RD` in POSIX. receive, @@ -38,7 +38,7 @@ interface tcp { /// 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. - @since(version = 0.2.0) + @since(version = 0.3.0) resource tcp-socket { /// Bind the socket to a specific network on the provided IP address and port. /// @@ -183,7 +183,7 @@ interface tcp { /// - /// - /// - - @since(version = 0.2.0) + @since(version = 0.3.0) local-address: func() -> result; /// Get the remote address. @@ -196,19 +196,19 @@ interface tcp { /// - /// - /// - - @since(version = 0.2.0) + @since(version = 0.3.0) remote-address: func() -> result; /// Whether the socket is in the `listening` state. /// /// Equivalent to the SO_ACCEPTCONN socket option. - @since(version = 0.2.0) + @since(version = 0.3.0) is-listening: func() -> bool; /// Whether this is a IPv4 or IPv6 socket. /// /// Equivalent to the SO_DOMAIN socket option. - @since(version = 0.2.0) + @since(version = 0.3.0) address-family: func() -> ip-address-family; /// Hints the desired listen queue size. Implementations are free to ignore this. @@ -220,7 +220,7 @@ interface tcp { /// - `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. - @since(version = 0.2.0) + @since(version = 0.3.0) set-listen-backlog-size: func(value: u64) -> result<_, error-code>; /// Enables or disables keepalive. @@ -232,9 +232,9 @@ interface tcp { /// 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. - @since(version = 0.2.0) + @since(version = 0.3.0) keep-alive-enabled: func() -> result; - @since(version = 0.2.0) + @since(version = 0.3.0) set-keep-alive-enabled: func(value: bool) -> result<_, error-code>; /// Amount of time the connection has to be idle before TCP starts sending keepalive packets. @@ -247,9 +247,9 @@ interface tcp { /// /// # Typical errors /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.2.0) + @since(version = 0.3.0) keep-alive-idle-time: func() -> result; - @since(version = 0.2.0) + @since(version = 0.3.0) set-keep-alive-idle-time: func(value: duration) -> result<_, error-code>; /// The time between keepalive packets. @@ -262,9 +262,9 @@ interface tcp { /// /// # Typical errors /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.2.0) + @since(version = 0.3.0) keep-alive-interval: func() -> result; - @since(version = 0.2.0) + @since(version = 0.3.0) set-keep-alive-interval: func(value: duration) -> result<_, error-code>; /// The maximum amount of keepalive packets TCP should send before aborting the connection. @@ -277,9 +277,9 @@ interface tcp { /// /// # Typical errors /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.2.0) + @since(version = 0.3.0) keep-alive-count: func() -> result; - @since(version = 0.2.0) + @since(version = 0.3.0) set-keep-alive-count: func(value: u32) -> result<_, error-code>; /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. @@ -288,9 +288,9 @@ interface tcp { /// /// # Typical errors /// - `invalid-argument`: (set) The TTL value must be 1 or higher. - @since(version = 0.2.0) + @since(version = 0.3.0) hop-limit: func() -> result; - @since(version = 0.2.0) + @since(version = 0.3.0) set-hop-limit: func(value: u8) -> result<_, error-code>; /// The kernel buffer space reserved for sends/receives on this socket. @@ -303,13 +303,13 @@ interface tcp { /// /// # Typical errors /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.2.0) + @since(version = 0.3.0) receive-buffer-size: func() -> result; - @since(version = 0.2.0) + @since(version = 0.3.0) set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - @since(version = 0.2.0) + @since(version = 0.3.0) send-buffer-size: func() -> result; - @since(version = 0.2.0) + @since(version = 0.3.0) set-send-buffer-size: func(value: u64) -> result<_, error-code>; /// Initiate a graceful shutdown. @@ -335,7 +335,7 @@ interface tcp { /// - /// - /// - - @since(version = 0.2.0) + @since(version = 0.3.0) shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; } } diff --git a/wit-0.3.0-draft/udp-create-socket.wit b/wit-0.3.0-draft/udp-create-socket.wit index e8eeacb..339afee 100644 --- a/wit-0.3.0-draft/udp-create-socket.wit +++ b/wit-0.3.0-draft/udp-create-socket.wit @@ -1,8 +1,8 @@ -@since(version = 0.2.0) +@since(version = 0.3.0) interface udp-create-socket { - @since(version = 0.2.0) + @since(version = 0.3.0) use network.{network, error-code, ip-address-family}; - @since(version = 0.2.0) + @since(version = 0.3.0) use udp.{udp-socket}; /// Create a new UDP socket. @@ -25,6 +25,6 @@ interface udp-create-socket { /// - /// - /// - - @since(version = 0.2.0) + @since(version = 0.3.0) create-udp-socket: func(address-family: ip-address-family) -> result; } diff --git a/wit-0.3.0-draft/udp.wit b/wit-0.3.0-draft/udp.wit index 0f9e1c2..f3d5f1a 100644 --- a/wit-0.3.0-draft/udp.wit +++ b/wit-0.3.0-draft/udp.wit @@ -1,10 +1,10 @@ -@since(version = 0.2.0) +@since(version = 0.3.0) interface udp { - @since(version = 0.2.0) + @since(version = 0.3.0) use network.{network, error-code, ip-socket-address, ip-address-family}; /// A received datagram. - @since(version = 0.2.0) + @since(version = 0.3.0) record incoming-datagram { /// The payload. /// @@ -20,7 +20,7 @@ interface udp { } /// A datagram to be sent out. - @since(version = 0.2.0) + @since(version = 0.3.0) record outgoing-datagram { /// The payload. data: list, @@ -36,7 +36,7 @@ interface udp { } /// A UDP socket handle. - @since(version = 0.2.0) + @since(version = 0.3.0) resource udp-socket { /// Bind the socket to a specific network on the provided IP address and port. /// @@ -153,7 +153,7 @@ interface udp { /// - /// - /// - - @since(version = 0.2.0) + @since(version = 0.3.0) local-address: func() -> result; /// Get the address the socket is currently streaming to. @@ -166,13 +166,13 @@ interface udp { /// - /// - /// - - @since(version = 0.2.0) + @since(version = 0.3.0) remote-address: func() -> result; /// Whether this is a IPv4 or IPv6 socket. /// /// Equivalent to the SO_DOMAIN socket option. - @since(version = 0.2.0) + @since(version = 0.3.0) address-family: func() -> ip-address-family; /// Equivalent to the IP_TTL & IPV6_UNICAST_HOPS socket options. @@ -181,9 +181,9 @@ interface udp { /// /// # Typical errors /// - `invalid-argument`: (set) The TTL value must be 1 or higher. - @since(version = 0.2.0) + @since(version = 0.3.0) unicast-hop-limit: func() -> result; - @since(version = 0.2.0) + @since(version = 0.3.0) set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; /// The kernel buffer space reserved for sends/receives on this socket. @@ -196,13 +196,13 @@ interface udp { /// /// # Typical errors /// - `invalid-argument`: (set) The provided value was 0. - @since(version = 0.2.0) + @since(version = 0.3.0) receive-buffer-size: func() -> result; - @since(version = 0.2.0) + @since(version = 0.3.0) set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - @since(version = 0.2.0) + @since(version = 0.3.0) send-buffer-size: func() -> result; - @since(version = 0.2.0) + @since(version = 0.3.0) set-send-buffer-size: func(value: u64) -> result<_, error-code>; } } diff --git a/wit-0.3.0-draft/world.wit b/wit-0.3.0-draft/world.wit index e7ba6a1..61007bc 100644 --- a/wit-0.3.0-draft/world.wit +++ b/wit-0.3.0-draft/world.wit @@ -1,19 +1,19 @@ package wasi:sockets@0.3.0-draft; -@since(version = 0.2.0) +@since(version = 0.3.0) world imports { - @since(version = 0.2.0) + @since(version = 0.3.0) import instance-network; - @since(version = 0.2.0) + @since(version = 0.3.0) import network; - @since(version = 0.2.0) + @since(version = 0.3.0) import udp; - @since(version = 0.2.0) + @since(version = 0.3.0) import udp-create-socket; - @since(version = 0.2.0) + @since(version = 0.3.0) import tcp; - @since(version = 0.2.0) + @since(version = 0.3.0) import tcp-create-socket; - @since(version = 0.2.0) + @since(version = 0.3.0) import ip-name-lookup; } From a4ca7f6d2353534e12b87451fdb053ecf21aa113 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Wed, 15 Jan 2025 12:12:55 +0100 Subject: [PATCH 04/36] feat(0.3): introduce `tcp-connection` Signed-off-by: Roman Volosatovs --- wit-0.3.0-draft/tcp.wit | 42 +++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index a6fd58e..1733f18 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -16,6 +16,26 @@ interface tcp { /// Similar to `SHUT_RDWR` in POSIX. both, } + + /// A TCP connection resource + /// + /// The connection is used to transmit and receive data. + resource tcp-connection { + /// Transmit data to peer. + /// + /// This function returns once full contents of the stream are transmitted + /// or an error is encoutered. + write: func(data: stream) -> result<_, error-code>; + + /// Read data from peer. + /// + /// This function fails if `read` was already called before on this connection. + /// + /// On sucess, this function returns a stream and a future, which will resolve + /// to an error code if receiving data from stream fails. + /// The returned future resolves to success if receiving side of the connection is closed. + read: func() -> result, future>>>; + } /// A TCP socket resource. /// @@ -77,13 +97,7 @@ interface tcp { /// Connect to a remote endpoint. /// This function takes a stream as a parameter, which will be used for data transmission to the server. /// - /// On success, the socket is transitioned into the `connected` state and this function returns: - /// - Future, which will resolve to an optional error code if writing full contents of the stream to the connection fails. - /// The future resolves to `none` once full contents of the stream are transmitted successfully and the writing side of the - /// connection is closed. - /// - A stream that can be used to read from the connection - /// - Future, which will resolve to an optional error code if reading contents from the connection fails. - /// The future resolves to `none` once receiving side of the stream is closed. + /// On success, the socket is transitioned into the `connected` state and this function returns a connection resource. /// /// 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 @@ -110,7 +124,7 @@ interface tcp { /// - /// - @since(version = 0.3.0) - connect: func(network: borrow, remote-address: ip-socket-address, tx: stream) -> result>, stream, future>>, error-code>; + connect: func(network: borrow, remote-address: ip-socket-address) -> result; /// Start listening for new connections. /// @@ -133,7 +147,6 @@ interface tcp { listen: func() -> result<_, error-code>; /// Accept a new client socket. - /// This function takes a stream as a parameter, which will be used for data transmission to the client. /// /// The returned socket is bound and in the `connected` state. The following properties are inherited from the listener socket: /// - `address-family` @@ -145,14 +158,7 @@ interface tcp { /// - `receive-buffer-size` /// - `send-buffer-size` /// - /// On success, this function returns: - /// - Future, which will resolve to an optional error code if writing full contents of the stream to the connection fails. - /// The future resolves to `none` once full contents of the stream are transmitted successfully and the writing side of the - /// connection is closed. - /// - Newly accepted client socket - /// - A stream that can be used to read from the connection - /// - Future, which will resolve to an optional error code if reading contents from the connection fails. - /// The future resolves to `none` once receiving side of the stream is closed. + /// On success, this function returns the newly accepted client socket and associated TCP connection. /// /// # Typical errors /// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) @@ -165,7 +171,7 @@ interface tcp { /// - /// - @since(version = 0.3.0) - accept: func(tx: stream) -> result>, tcp-socket, stream, future>>, error-code>; + accept: func() -> result, error-code>; /// Get the bound local address. /// From 955b5a487953069214dae03c3dea8a570239284e Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 11:40:47 +0100 Subject: [PATCH 05/36] Add TcpSocketOperationalSemantics-0.3.0-draft.md --- TcpSocketOperationalSemantics-0.3.0-draft.md | 53 ++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 TcpSocketOperationalSemantics-0.3.0-draft.md diff --git a/TcpSocketOperationalSemantics-0.3.0-draft.md b/TcpSocketOperationalSemantics-0.3.0-draft.md new file mode 100644 index 0000000..6ff5689 --- /dev/null +++ b/TcpSocketOperationalSemantics-0.3.0-draft.md @@ -0,0 +1,53 @@ +# Operational semantics of WASI TCP sockets + +WASI TCP sockets must behave [as-if](https://en.wikipedia.org/wiki/As-if_rule) they are implemented using the state machine described in this document. + +## States +> Note: These refer to the states of the TCP socket, not the [TCP connection](https://datatracker.ietf.org/doc/html/rfc9293#name-state-machine-overview) + +In pseudo code: + +```wit +interface tcp { + variant state { + unbound, + bound, + listening(accept-stream), + connecting(connect-future), + connected, + closed, + } +} +``` + +## Transitions +The following diagram describes the exhaustive set of all possible state transitions: + +```mermaid +stateDiagram-v2 + state "unbound" as Unbound + state "bound" as Bound + state "connecting" as Connecting + state "connected" as Connected + state "listening" as Listening + state "closed" as Closed + + + [*] --> Unbound: create-tcp-socket() -> ok + Unbound --> Bound: bind() -> ok + Unbound --> Connecting: connect() + + Connecting --> Connected: «task resolves successfully» + Connecting --> Closed: «task resolves with error» + + Connected --> Closed: «connection terminated» + + Bound --> Connecting: connect() + Bound --> Listening: listen() -> ok +``` + +- Transitions annotated with `-> ok` only apply when the method returns successfully. +- Calling a method from the wrong state returns `error(invalid-state)` and does not affect the state of the socket. +- This diagram only includes the methods that impact the socket's state. For an overview of all methods and their required states, see [tcp.wit](./wit/tcp.wit) +- Client sockets returned by `accept()` are in immediately in the `connected` state. +- A socket resource can be dropped in any state. From c5b9684a56ffeeac73bb212ec25ed3469e194759 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 12:04:30 +0100 Subject: [PATCH 06/36] Remove resolve-address-stream --- wit-0.3.0-draft/ip-name-lookup.wit | 31 ++++++++---------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/wit-0.3.0-draft/ip-name-lookup.wit b/wit-0.3.0-draft/ip-name-lookup.wit index 2be9bc0..f1dc5bf 100644 --- a/wit-0.3.0-draft/ip-name-lookup.wit +++ b/wit-0.3.0-draft/ip-name-lookup.wit @@ -11,12 +11,15 @@ interface ip-name-lookup { /// /// 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. + /// This function never succeeds with 0 results. It either fails succeeds + /// with at least one address. Additionally, this function never returns + /// IPv4-mapped IPv6 addresses. /// /// # Typical errors - /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. + /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. + /// - `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) /// /// # References: /// - @@ -24,23 +27,5 @@ interface ip-name-lookup { /// - /// - @since(version = 0.3.0) - resolve-addresses: func(network: borrow, name: string) -> result; - - @since(version = 0.3.0) - 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) - @since(version = 0.3.0) - resolve-next-address: func() -> result, error-code>; - } + resolve-addresses: func(network: borrow, name: string) -> result, error-code>; } From 26390dbb8d56892b3da17ccf5d6d848135617be3 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 12:24:30 +0100 Subject: [PATCH 07/36] Split lookup errors off from the general socket errors type --- wit-0.3.0-draft/ip-name-lookup.wit | 10 ++-------- wit-0.3.0-draft/network.wit | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/wit-0.3.0-draft/ip-name-lookup.wit b/wit-0.3.0-draft/ip-name-lookup.wit index f1dc5bf..7063ff9 100644 --- a/wit-0.3.0-draft/ip-name-lookup.wit +++ b/wit-0.3.0-draft/ip-name-lookup.wit @@ -1,7 +1,7 @@ @since(version = 0.3.0) interface ip-name-lookup { @since(version = 0.3.0) - use network.{network, error-code, ip-address}; + use network.{network, lookup-error, ip-address}; /// Resolve an internet host name to a list of IP addresses. /// @@ -15,17 +15,11 @@ interface ip-name-lookup { /// with at least one address. Additionally, this function never returns /// IPv4-mapped IPv6 addresses. /// - /// # Typical errors - /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. - /// - `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) - /// /// # References: /// - /// - /// - /// - @since(version = 0.3.0) - resolve-addresses: func(network: borrow, name: string) -> result, error-code>; + resolve-addresses: func(network: borrow, name: string) -> result, lookup-error>; } diff --git a/wit-0.3.0-draft/network.wit b/wit-0.3.0-draft/network.wit index 21644ba..902ba3c 100644 --- a/wit-0.3.0-draft/network.wit +++ b/wit-0.3.0-draft/network.wit @@ -80,15 +80,37 @@ interface network { /// The size of a datagram sent to a UDP socket exceeded the maximum /// supported size. datagram-too-large, + } + + /// Lookup error codes. + @since(version = 0.3.0) + enum lookup-error { + /// Unknown error + unknown, + + /// Access denied. + /// + /// POSIX equivalent: EACCES, EPERM + access-denied, + /// `name` is a syntactically invalid domain name or IP address. + /// + /// POSIX equivalent: EINVAL + invalid-argument, /// Name does not exist or has no suitable associated IP addresses. + /// + /// POSIX equivalent: EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY name-unresolvable, /// A temporary failure in name resolution occurred. + /// + /// POSIX equivalent: EAI_AGAIN temporary-resolver-failure, /// A permanent failure in name resolution occurred. + /// + /// POSIX equivalent: EAI_FAIL permanent-resolver-failure, } From bee1ffd5cbd0853672a6d35d2d3165e8d98e21dc Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 12:40:45 +0100 Subject: [PATCH 08/36] Remove the runtime `network` capability and rely solely on link-time capability. --- wit-0.3.0-draft/instance-network.wit | 11 ----------- wit-0.3.0-draft/ip-name-lookup.wit | 4 ++-- wit-0.3.0-draft/network.wit | 6 ------ wit-0.3.0-draft/tcp-create-socket.wit | 6 +----- wit-0.3.0-draft/tcp.wit | 11 +++++------ wit-0.3.0-draft/udp-create-socket.wit | 6 +----- wit-0.3.0-draft/udp.wit | 8 ++++---- wit-0.3.0-draft/world.wit | 2 -- 8 files changed, 13 insertions(+), 41 deletions(-) delete mode 100644 wit-0.3.0-draft/instance-network.wit diff --git a/wit-0.3.0-draft/instance-network.wit b/wit-0.3.0-draft/instance-network.wit deleted file mode 100644 index a9e54ef..0000000 --- a/wit-0.3.0-draft/instance-network.wit +++ /dev/null @@ -1,11 +0,0 @@ - -/// This interface provides a value-export of the default network handle.. -@since(version = 0.3.0) -interface instance-network { - @since(version = 0.3.0) - use network.{network}; - - /// Get a handle to the default network. - @since(version = 0.3.0) - instance-network: func() -> network; -} diff --git a/wit-0.3.0-draft/ip-name-lookup.wit b/wit-0.3.0-draft/ip-name-lookup.wit index 7063ff9..58a102d 100644 --- a/wit-0.3.0-draft/ip-name-lookup.wit +++ b/wit-0.3.0-draft/ip-name-lookup.wit @@ -1,7 +1,7 @@ @since(version = 0.3.0) interface ip-name-lookup { @since(version = 0.3.0) - use network.{network, lookup-error, ip-address}; + use network.{lookup-error, ip-address}; /// Resolve an internet host name to a list of IP addresses. /// @@ -21,5 +21,5 @@ interface ip-name-lookup { /// - /// - @since(version = 0.3.0) - resolve-addresses: func(network: borrow, name: string) -> result, lookup-error>; + resolve-addresses: func(name: string) -> result, lookup-error>; } diff --git a/wit-0.3.0-draft/network.wit b/wit-0.3.0-draft/network.wit index 902ba3c..8b93ac4 100644 --- a/wit-0.3.0-draft/network.wit +++ b/wit-0.3.0-draft/network.wit @@ -1,11 +1,5 @@ @since(version = 0.3.0) 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. - @since(version = 0.3.0) - resource network; - /// Error codes. /// /// In theory, every API can return any error code. diff --git a/wit-0.3.0-draft/tcp-create-socket.wit b/wit-0.3.0-draft/tcp-create-socket.wit index 3b62062..d9c94fc 100644 --- a/wit-0.3.0-draft/tcp-create-socket.wit +++ b/wit-0.3.0-draft/tcp-create-socket.wit @@ -1,7 +1,7 @@ @since(version = 0.3.0) interface tcp-create-socket { @since(version = 0.3.0) - use network.{network, error-code, ip-address-family}; + use network.{error-code, ip-address-family}; @since(version = 0.3.0) use tcp.{tcp-socket}; @@ -10,10 +10,6 @@ interface tcp-create-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 diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index 1733f18..573e95d 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -3,7 +3,7 @@ interface tcp { @since(version = 0.3.0) use wasi:clocks/monotonic-clock@0.3.0.{duration}; @since(version = 0.3.0) - use network.{network, error-code, ip-socket-address, ip-address-family}; + use network.{error-code, ip-socket-address, ip-address-family}; @since(version = 0.3.0) enum shutdown-type { @@ -60,7 +60,7 @@ interface tcp { /// `error(invalid-state)` when in the `closed` state. @since(version = 0.3.0) resource tcp-socket { - /// Bind the socket to a specific network on the provided IP address and port. + /// Bind the socket to 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. @@ -78,7 +78,7 @@ interface tcp { /// - `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) + /// - `address-not-bindable`: `local-address` is not an address that can be bound to. (EADDRNOTAVAIL) /// /// # Implementors note /// When binding to a non-zero port, this bind operation shouldn't be affected by the TIME_WAIT @@ -92,7 +92,7 @@ interface tcp { /// - /// - @since(version = 0.3.0) - bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + bind: func(local-address: ip-socket-address) -> result<_, error-code>; /// Connect to a remote endpoint. /// This function takes a stream as a parameter, which will be used for data transmission to the server. @@ -109,7 +109,6 @@ interface tcp { /// - `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) @@ -124,7 +123,7 @@ interface tcp { /// - /// - @since(version = 0.3.0) - connect: func(network: borrow, remote-address: ip-socket-address) -> result; + connect: func(remote-address: ip-socket-address) -> result; /// Start listening for new connections. /// diff --git a/wit-0.3.0-draft/udp-create-socket.wit b/wit-0.3.0-draft/udp-create-socket.wit index 339afee..0fdd4d3 100644 --- a/wit-0.3.0-draft/udp-create-socket.wit +++ b/wit-0.3.0-draft/udp-create-socket.wit @@ -1,7 +1,7 @@ @since(version = 0.3.0) interface udp-create-socket { @since(version = 0.3.0) - use network.{network, error-code, ip-address-family}; + use network.{error-code, ip-address-family}; @since(version = 0.3.0) use udp.{udp-socket}; @@ -10,10 +10,6 @@ interface udp-create-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 diff --git a/wit-0.3.0-draft/udp.wit b/wit-0.3.0-draft/udp.wit index f3d5f1a..d557c6d 100644 --- a/wit-0.3.0-draft/udp.wit +++ b/wit-0.3.0-draft/udp.wit @@ -1,7 +1,7 @@ @since(version = 0.3.0) interface udp { @since(version = 0.3.0) - use network.{network, error-code, ip-socket-address, ip-address-family}; + use network.{error-code, ip-socket-address, ip-address-family}; /// A received datagram. @since(version = 0.3.0) @@ -38,7 +38,7 @@ interface udp { /// A UDP socket handle. @since(version = 0.3.0) resource udp-socket { - /// Bind the socket to a specific network on the provided IP address and port. + /// Bind the socket to 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. @@ -49,7 +49,7 @@ interface udp { /// - `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) + /// - `address-not-bindable`: `local-address` is not an address that can be bound to. (EADDRNOTAVAIL) /// /// # References /// - @@ -57,7 +57,7 @@ interface udp { /// - /// - @since(version = 0.3.0) - bind: func(network: borrow, local-address: ip-socket-address) -> result<_, error-code>; + bind: func(local-address: ip-socket-address) -> result<_, error-code>; /// Set up inbound & outbound communication channels, optionally to a specific peer. /// diff --git a/wit-0.3.0-draft/world.wit b/wit-0.3.0-draft/world.wit index 61007bc..352af8e 100644 --- a/wit-0.3.0-draft/world.wit +++ b/wit-0.3.0-draft/world.wit @@ -2,8 +2,6 @@ package wasi:sockets@0.3.0-draft; @since(version = 0.3.0) world imports { - @since(version = 0.3.0) - import instance-network; @since(version = 0.3.0) import network; @since(version = 0.3.0) From 6de677f6befe878d6e79738e7deaabe88c7c677a Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 12:49:17 +0100 Subject: [PATCH 09/36] Remove `create-tcp/udp-socket` methods & interfaces in favor of plain `constructor`s --- wit-0.3.0-draft/network.wit | 3 --- wit-0.3.0-draft/tcp-create-socket.wit | 26 -------------------------- wit-0.3.0-draft/tcp.wit | 16 ++++++++++++++++ wit-0.3.0-draft/udp-create-socket.wit | 26 -------------------------- wit-0.3.0-draft/udp.wit | 16 ++++++++++++++++ wit-0.3.0-draft/world.wit | 4 ---- 6 files changed, 32 insertions(+), 59 deletions(-) delete mode 100644 wit-0.3.0-draft/tcp-create-socket.wit delete mode 100644 wit-0.3.0-draft/udp-create-socket.wit diff --git a/wit-0.3.0-draft/network.wit b/wit-0.3.0-draft/network.wit index 8b93ac4..aa4aa8e 100644 --- a/wit-0.3.0-draft/network.wit +++ b/wit-0.3.0-draft/network.wit @@ -48,9 +48,6 @@ interface network { /// 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, diff --git a/wit-0.3.0-draft/tcp-create-socket.wit b/wit-0.3.0-draft/tcp-create-socket.wit deleted file mode 100644 index d9c94fc..0000000 --- a/wit-0.3.0-draft/tcp-create-socket.wit +++ /dev/null @@ -1,26 +0,0 @@ -@since(version = 0.3.0) -interface tcp-create-socket { - @since(version = 0.3.0) - use network.{error-code, ip-address-family}; - @since(version = 0.3.0) - use tcp.{tcp-socket}; - - /// 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. - /// - /// 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 - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - create-tcp-socket: func(address-family: ip-address-family) -> result; -} diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index 573e95d..9d0d932 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -60,6 +60,22 @@ interface tcp { /// `error(invalid-state)` when in the `closed` state. @since(version = 0.3.0) resource tcp-socket { + + /// 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. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + constructor(address-family: ip-address-family); + /// Bind the socket to 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 diff --git a/wit-0.3.0-draft/udp-create-socket.wit b/wit-0.3.0-draft/udp-create-socket.wit deleted file mode 100644 index 0fdd4d3..0000000 --- a/wit-0.3.0-draft/udp-create-socket.wit +++ /dev/null @@ -1,26 +0,0 @@ -@since(version = 0.3.0) -interface udp-create-socket { - @since(version = 0.3.0) - use network.{error-code, ip-address-family}; - @since(version = 0.3.0) - use udp.{udp-socket}; - - /// 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. - /// - /// 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: - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - create-udp-socket: func(address-family: ip-address-family) -> result; -} diff --git a/wit-0.3.0-draft/udp.wit b/wit-0.3.0-draft/udp.wit index d557c6d..0a86ad9 100644 --- a/wit-0.3.0-draft/udp.wit +++ b/wit-0.3.0-draft/udp.wit @@ -38,6 +38,22 @@ interface udp { /// A UDP socket handle. @since(version = 0.3.0) resource udp-socket { + + /// 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. + /// + /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// + /// # References: + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + constructor(address-family: ip-address-family); + /// Bind the socket to 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 diff --git a/wit-0.3.0-draft/world.wit b/wit-0.3.0-draft/world.wit index 352af8e..b9b7461 100644 --- a/wit-0.3.0-draft/world.wit +++ b/wit-0.3.0-draft/world.wit @@ -7,11 +7,7 @@ world imports { @since(version = 0.3.0) import udp; @since(version = 0.3.0) - import udp-create-socket; - @since(version = 0.3.0) import tcp; @since(version = 0.3.0) - import tcp-create-socket; - @since(version = 0.3.0) import ip-name-lookup; } From 4417d188065658810a0235a06fd3d5291dbaa972 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 12:52:47 +0100 Subject: [PATCH 10/36] Rename `write` -> `send`, `read` -> `receive` --- wit-0.3.0-draft/tcp.wit | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index 9d0d932..6feab00 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -24,8 +24,8 @@ interface tcp { /// Transmit data to peer. /// /// This function returns once full contents of the stream are transmitted - /// or an error is encoutered. - write: func(data: stream) -> result<_, error-code>; + /// or an error is encountered. + send: func(data: stream) -> result<_, error-code>; /// Read data from peer. /// @@ -34,7 +34,7 @@ interface tcp { /// On sucess, this function returns a stream and a future, which will resolve /// to an error code if receiving data from stream fails. /// The returned future resolves to success if receiving side of the connection is closed. - read: func() -> result, future>>>; + receive: func() -> result, future>>>; } /// A TCP socket resource. From 544ec8c8d3aaed4497a46cdc4e3901956efca78d Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 13:06:15 +0100 Subject: [PATCH 11/36] Remove the TCP `shutdown` method. It is redundant now that we have proper streams with cancellation. --- wit-0.3.0-draft/tcp.wit | 52 ++++++++++------------------------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index 6feab00..24bd96a 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -5,26 +5,19 @@ interface tcp { @since(version = 0.3.0) use network.{error-code, ip-socket-address, ip-address-family}; - @since(version = 0.3.0) - enum shutdown-type { - /// Similar to `SHUT_RD` in POSIX. - receive, - - /// Similar to `SHUT_WR` in POSIX. - send, - - /// Similar to `SHUT_RDWR` in POSIX. - both, - } - /// A TCP connection resource /// /// The connection is used to transmit and receive data. resource tcp-connection { /// Transmit data to peer. /// - /// This function returns once full contents of the stream are transmitted - /// or an error is encountered. + /// The caller should close the stream when it has no more data to send + /// to the peer. Under normal circumstances this will cause a FIN packet + /// to be sent. Closing the stream is equivalent to calling + /// `shutdown(SHUT_WR)` in POSIX. + /// + /// This function may be called at most once and returns once the full + /// contents of the stream are transmitted or an error is encountered. send: func(data: stream) -> result<_, error-code>; /// Read data from peer. @@ -34,6 +27,11 @@ interface tcp { /// On sucess, this function returns a stream and a future, which will resolve /// to an error code if receiving data from stream fails. /// The returned future resolves to success if receiving side of the connection is closed. + /// + /// If the caller is not expecting to receive any data from the peer, + /// they may cancel the receive task. Any data still in the receive queue + /// will be discarded. This is equivalent to calling `shutdown(SHUT_RD)` + /// in POSIX. receive: func() -> result, future>>>; } @@ -332,31 +330,5 @@ interface tcp { send-buffer-size: func() -> result; @since(version = 0.3.0) set-send-buffer-size: func(value: u64) -> 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 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 - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>; } } From 5134af083dbec6c49ebc33456114288a57d90eb9 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 14:04:11 +0100 Subject: [PATCH 12/36] Now that binding a socket doesn't require a runtime network handle anymore, `listen` is free to do an implicit bind again. Just like POSIX. --- TcpSocketOperationalSemantics-0.3.0-draft.md | 1 + wit-0.3.0-draft/tcp.wit | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/TcpSocketOperationalSemantics-0.3.0-draft.md b/TcpSocketOperationalSemantics-0.3.0-draft.md index 6ff5689..d5db7d9 100644 --- a/TcpSocketOperationalSemantics-0.3.0-draft.md +++ b/TcpSocketOperationalSemantics-0.3.0-draft.md @@ -44,6 +44,7 @@ stateDiagram-v2 Bound --> Connecting: connect() Bound --> Listening: listen() -> ok + Unbound --> Listening: listen() -> ok ``` - Transitions annotated with `-> ok` only apply when the method returns successfully. diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index 24bd96a..1c4d902 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -143,10 +143,10 @@ interface tcp { /// /// Transitions the socket into the `listening` state. /// - /// Unlike POSIX, the socket must already be explicitly bound. + /// If the socket is not already explicitly bound, this function will + /// implicitly bind the socket to a random free port. /// /// # 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) From 37782192f18469c05165da1742360477b8f799bd Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 14:15:42 +0100 Subject: [PATCH 13/36] Merge `listen` & `accept` into a single method that returns a stream of client sockets. --- TcpSocketOperationalSemantics-0.3.0-draft.md | 2 +- wit-0.3.0-draft/tcp.wit | 34 +++++++------------- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/TcpSocketOperationalSemantics-0.3.0-draft.md b/TcpSocketOperationalSemantics-0.3.0-draft.md index d5db7d9..3640163 100644 --- a/TcpSocketOperationalSemantics-0.3.0-draft.md +++ b/TcpSocketOperationalSemantics-0.3.0-draft.md @@ -50,5 +50,5 @@ stateDiagram-v2 - Transitions annotated with `-> ok` only apply when the method returns successfully. - Calling a method from the wrong state returns `error(invalid-state)` and does not affect the state of the socket. - This diagram only includes the methods that impact the socket's state. For an overview of all methods and their required states, see [tcp.wit](./wit/tcp.wit) -- Client sockets returned by `accept()` are in immediately in the `connected` state. +- Client sockets returned by `listen()` are in immediately in the `connected` state. - A socket resource can be dropped in any state. diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index 1c4d902..2d4788b 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -139,29 +139,15 @@ interface tcp { @since(version = 0.3.0) connect: func(remote-address: ip-socket-address) -> result; - /// Start listening for new connections. + /// Start listening return a stream of new inbound connections. /// /// Transitions the socket into the `listening` state. /// /// If the socket is not already explicitly bound, this function will /// implicitly bind the socket to a random free port. /// - /// # Typical errors - /// - `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) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - listen: 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: + /// The returned sockets are bound and in the `connected` state. + /// The following properties are inherited from the listener socket: /// - `address-family` /// - `keep-alive-enabled` /// - `keep-alive-idle-time` @@ -171,20 +157,22 @@ interface tcp { /// - `receive-buffer-size` /// - `send-buffer-size` /// - /// On success, this function returns the newly accepted client socket and associated TCP connection. - /// /// # Typical errors - /// - `invalid-state`: Socket is not in the `listening` state. (EINVAL) - /// - `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) + /// - `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) /// /// # References + /// - /// - + /// - /// - + /// - /// - + /// - /// - @since(version = 0.3.0) - accept: func() -> result, error-code>; + listen: func() -> result>, error-code>; /// Get the bound local address. /// From 2752e1bcbae9976d3bbfbe10e8b7fc6385c6e84c Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 14:21:30 +0100 Subject: [PATCH 14/36] Update state names --- wit-0.3.0-draft/tcp.wit | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index 2d4788b..d9b1348 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -39,11 +39,9 @@ interface tcp { /// /// 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` + /// - `connecting` /// - `connected` /// - `closed` /// See @@ -51,7 +49,7 @@ interface tcp { /// /// 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`) + /// (i.e. `bound`, `listening`, `connecting` or `connected`) /// /// In addition to the general error codes documented on the /// `network::error-code` type, TCP socket methods may always return @@ -226,7 +224,7 @@ interface tcp { /// # 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. + /// - `invalid-state`: (set) The socket is in the `connecting` or `connected` state. @since(version = 0.3.0) set-listen-backlog-size: func(value: u64) -> result<_, error-code>; From c48f6b912e226ffc007608400470c4cdf65eadc1 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 14:29:23 +0100 Subject: [PATCH 15/36] Clarify EALREADY equivalence --- wit-0.3.0-draft/tcp.wit | 1 + 1 file changed, 1 insertion(+) diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index d9b1348..b1e3ae6 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -121,6 +121,7 @@ interface tcp { /// - `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-state`: The socket is already in the `connecting` state. (EALREADY) /// - `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) From ee629732dd06097c311e25bf22d66a1e1ce473c3 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 14:41:28 +0100 Subject: [PATCH 16/36] Remove superfluous `concurrency-conflict` error code. `invalid-state` already covers all its use-cases. Was an oversight in v0.2 --- wit-0.3.0-draft/network.wit | 6 ------ 1 file changed, 6 deletions(-) diff --git a/wit-0.3.0-draft/network.wit b/wit-0.3.0-draft/network.wit index aa4aa8e..81b923f 100644 --- a/wit-0.3.0-draft/network.wit +++ b/wit-0.3.0-draft/network.wit @@ -9,7 +9,6 @@ interface network { /// - `access-denied` /// - `not-supported` /// - `out-of-memory` - /// - `concurrency-conflict` /// /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. @since(version = 0.3.0) @@ -40,11 +39,6 @@ interface network { /// 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, - /// The operation is not valid in the socket's current state. invalid-state, From c483c3a54f745ec1acc45a5cc1ceed3a690ff2f4 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 14:48:40 +0100 Subject: [PATCH 17/36] Add Since annotations to tcp-connection --- wit-0.3.0-draft/tcp.wit | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index b1e3ae6..28f771b 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -8,6 +8,7 @@ interface tcp { /// A TCP connection resource /// /// The connection is used to transmit and receive data. + @since(version = 0.3.0) resource tcp-connection { /// Transmit data to peer. /// @@ -18,6 +19,7 @@ interface tcp { /// /// This function may be called at most once and returns once the full /// contents of the stream are transmitted or an error is encountered. + @since(version = 0.3.0) send: func(data: stream) -> result<_, error-code>; /// Read data from peer. @@ -32,6 +34,7 @@ interface tcp { /// they may cancel the receive task. Any data still in the receive queue /// will be discarded. This is equivalent to calling `shutdown(SHUT_RD)` /// in POSIX. + @since(version = 0.3.0) receive: func() -> result, future>>>; } From 401fc2b4f8925251c02d95c2e0969bb458e946c7 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 15:06:21 +0100 Subject: [PATCH 18/36] Remove last references to wasi:io --- wit-0.3.0-draft/tcp.wit | 4 +++- wit-0.3.0-draft/udp.wit | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index 28f771b..e9ffdd0 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -65,7 +65,9 @@ interface tcp { /// 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. /// - /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// Unlike POSIX, WASI sockets have no notion of a socket-level + /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's + /// async support. /// /// # References /// - diff --git a/wit-0.3.0-draft/udp.wit b/wit-0.3.0-draft/udp.wit index 0a86ad9..8e46b7e 100644 --- a/wit-0.3.0-draft/udp.wit +++ b/wit-0.3.0-draft/udp.wit @@ -44,7 +44,9 @@ interface udp { /// 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. /// - /// All sockets are non-blocking. Use the wasi-poll interface to block on asynchronous operations. + /// Unlike POSIX, WASI sockets have no notion of a socket-level + /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's + /// async support. /// /// # References: /// - From 762a3ab39015b4731ee53f9f79810106d3c56df1 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 15:07:07 +0100 Subject: [PATCH 19/36] Remove outdated doc now that we have tcp-connection. --- wit-0.3.0-draft/tcp.wit | 1 - 1 file changed, 1 deletion(-) diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index e9ffdd0..2109498 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -112,7 +112,6 @@ interface tcp { bind: func(local-address: ip-socket-address) -> result<_, error-code>; /// Connect to a remote endpoint. - /// This function takes a stream as a parameter, which will be used for data transmission to the server. /// /// On success, the socket is transitioned into the `connected` state and this function returns a connection resource. /// From 7f3ec8d56bfe7a8f52d9bcb400327d8dc48c427b Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 15:28:53 +0100 Subject: [PATCH 20/36] Now that binding a socket doesn't require a runtime network handle anymore, `stream` is free to do an implicit bind again. Just like POSIX. --- wit-0.3.0-draft/udp.wit | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/wit-0.3.0-draft/udp.wit b/wit-0.3.0-draft/udp.wit index 8e46b7e..97c94e2 100644 --- a/wit-0.3.0-draft/udp.wit +++ b/wit-0.3.0-draft/udp.wit @@ -79,9 +79,12 @@ interface udp { /// 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`. + /// 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`. If the socket was + /// not already explicitly bound, this function will implicitly bind the + /// socket to a random free port. /// /// 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. @@ -100,14 +103,11 @@ interface udp { /// 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) From 0e8554fb6f4c9800676a8e8e85d2e1029a9aad0c Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 16 Jan 2025 15:48:12 +0100 Subject: [PATCH 21/36] feat(0.3): asyncify `resolve-address-stream` Signed-off-by: Roman Volosatovs --- wit-0.3.0-draft/ip-name-lookup.wit | 35 ++++++++++-------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/wit-0.3.0-draft/ip-name-lookup.wit b/wit-0.3.0-draft/ip-name-lookup.wit index 2be9bc0..73f5ec9 100644 --- a/wit-0.3.0-draft/ip-name-lookup.wit +++ b/wit-0.3.0-draft/ip-name-lookup.wit @@ -11,12 +11,19 @@ interface ip-name-lookup { /// /// 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. + /// This function returns a stream that can be used + /// to (asynchronously) fetch the resolved IP addresses. + /// Addresses are fetched in connection order preference. + /// This stream will never contain IPv4-mapped IPv6 addresses. + /// + /// The returned future will resolve to an error code in case of failure. + /// It will resolve to success once the returned stream is exhausted. /// /// # Typical errors - /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. + /// - `invalid-argument`: `name` is a syntactically invalid domain name or IP address. + /// - `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) /// /// # References: /// - @@ -24,23 +31,5 @@ interface ip-name-lookup { /// - /// - @since(version = 0.3.0) - resolve-addresses: func(network: borrow, name: string) -> result; - - @since(version = 0.3.0) - 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) - @since(version = 0.3.0) - resolve-next-address: func() -> result, error-code>; - } + resolve-addresses: func(network: borrow, name: string) -> tuple, future>>; } From dbe26c7c2c9618ade4d56fedc16a0d9a301bfe39 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 16:19:09 +0100 Subject: [PATCH 22/36] Merge UDP `send` into `%stream` --- wit-0.3.0-draft/udp.wit | 64 ++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/wit-0.3.0-draft/udp.wit b/wit-0.3.0-draft/udp.wit index 97c94e2..ade7d3f 100644 --- a/wit-0.3.0-draft/udp.wit +++ b/wit-0.3.0-draft/udp.wit @@ -79,6 +79,9 @@ interface udp { /// Set up inbound & outbound communication channels, optionally to a specific peer. /// + /// The `datagrams` stream parameter represents the data to be sent out. + /// The returned stream represents the data that was received. + /// /// 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, @@ -86,13 +89,15 @@ interface udp { /// not already explicitly bound, this function will implicitly bind the /// socket to a random free port. /// - /// 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. + /// When a `remote-address` is provided, the streams are limited to + /// communicating with that specific peer: + /// - The `outgoing-datagram` stream can only be used to send to this destination. + /// - The `incoming-datagram` stream 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 recent pair of streams will be + /// operational. Calling this function more than once effectively cancels + /// any previously started `%stream` tasks. /// /// The POSIX equivalent in pseudo-code is: /// ```text @@ -109,51 +114,30 @@ interface udp { /// - `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) /// - `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 /// - - /// - - /// - - /// - - @since(version = 0.3.0) - %stream: func(remote-address: option) -> 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). - /// 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)`. - /// - /// # 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 /// - /// - + /// - + /// - + /// - /// - /// - + /// - + /// - + /// - /// - /// - /// - + /// - + /// - + /// - + /// - /// - + /// - @since(version = 0.3.0) - send: func(datagrams: list) -> result; - + %stream: func(datagrams: list, remote-address: option) -> result, error-code>; /// Get the current bound address. /// From 5ab0f731bbc69a4ba436b7b4b9f619ec8a137ccc Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 16:24:06 +0100 Subject: [PATCH 23/36] Rename `%stream` to `transfer`. The `%stream` name was already suboptimal, but now that streams are actually a thing, it was even more akward. --- wit-0.3.0-draft/udp.wit | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wit-0.3.0-draft/udp.wit b/wit-0.3.0-draft/udp.wit index ade7d3f..819eecc 100644 --- a/wit-0.3.0-draft/udp.wit +++ b/wit-0.3.0-draft/udp.wit @@ -97,7 +97,7 @@ interface udp { /// This method may be called multiple times on the same socket to change /// its association, but only the most recent pair of streams will be /// operational. Calling this function more than once effectively cancels - /// any previously started `%stream` tasks. + /// any previously started `transfer` tasks. /// /// The POSIX equivalent in pseudo-code is: /// ```text @@ -137,7 +137,7 @@ interface udp { /// - /// - @since(version = 0.3.0) - %stream: func(datagrams: list, remote-address: option) -> result, error-code>; + transfer: func(datagrams: list, remote-address: option) -> result, error-code>; /// Get the current bound address. /// From 60dd2e82e8784182edaeb7f1ef429b6581ed1da8 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 16:46:06 +0100 Subject: [PATCH 24/36] Rename `network.wit` to `types.wit` to match other proposals --- wit-0.3.0-draft/ip-name-lookup.wit | 2 +- wit-0.3.0-draft/tcp.wit | 4 ++-- wit-0.3.0-draft/{network.wit => types.wit} | 2 +- wit-0.3.0-draft/udp.wit | 2 +- wit-0.3.0-draft/world.wit | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) rename wit-0.3.0-draft/{network.wit => types.wit} (99%) diff --git a/wit-0.3.0-draft/ip-name-lookup.wit b/wit-0.3.0-draft/ip-name-lookup.wit index 6391a8c..c8c6b3f 100644 --- a/wit-0.3.0-draft/ip-name-lookup.wit +++ b/wit-0.3.0-draft/ip-name-lookup.wit @@ -1,7 +1,7 @@ @since(version = 0.3.0) interface ip-name-lookup { @since(version = 0.3.0) - use network.{lookup-error, ip-address}; + use types.{lookup-error, ip-address}; /// Resolve an internet host name to a list of IP addresses. /// diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit index 2109498..7deecb5 100644 --- a/wit-0.3.0-draft/tcp.wit +++ b/wit-0.3.0-draft/tcp.wit @@ -3,7 +3,7 @@ interface tcp { @since(version = 0.3.0) use wasi:clocks/monotonic-clock@0.3.0.{duration}; @since(version = 0.3.0) - use network.{error-code, ip-socket-address, ip-address-family}; + use types.{error-code, ip-socket-address, ip-address-family}; /// A TCP connection resource /// @@ -55,7 +55,7 @@ interface tcp { /// (i.e. `bound`, `listening`, `connecting` or `connected`) /// /// In addition to the general error codes documented on the - /// `network::error-code` type, TCP socket methods may always return + /// `types::error-code` type, TCP socket methods may always return /// `error(invalid-state)` when in the `closed` state. @since(version = 0.3.0) resource tcp-socket { diff --git a/wit-0.3.0-draft/network.wit b/wit-0.3.0-draft/types.wit similarity index 99% rename from wit-0.3.0-draft/network.wit rename to wit-0.3.0-draft/types.wit index 81b923f..dae7096 100644 --- a/wit-0.3.0-draft/network.wit +++ b/wit-0.3.0-draft/types.wit @@ -1,5 +1,5 @@ @since(version = 0.3.0) -interface network { +interface types { /// Error codes. /// /// In theory, every API can return any error code. diff --git a/wit-0.3.0-draft/udp.wit b/wit-0.3.0-draft/udp.wit index 819eecc..4dd76fe 100644 --- a/wit-0.3.0-draft/udp.wit +++ b/wit-0.3.0-draft/udp.wit @@ -1,7 +1,7 @@ @since(version = 0.3.0) interface udp { @since(version = 0.3.0) - use network.{error-code, ip-socket-address, ip-address-family}; + use types.{error-code, ip-socket-address, ip-address-family}; /// A received datagram. @since(version = 0.3.0) diff --git a/wit-0.3.0-draft/world.wit b/wit-0.3.0-draft/world.wit index b9b7461..6704b67 100644 --- a/wit-0.3.0-draft/world.wit +++ b/wit-0.3.0-draft/world.wit @@ -3,7 +3,7 @@ package wasi:sockets@0.3.0-draft; @since(version = 0.3.0) world imports { @since(version = 0.3.0) - import network; + import types; @since(version = 0.3.0) import udp; @since(version = 0.3.0) From d1107f5eeaf0abc1bb67398c2d1b3c97e3072952 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 16:51:50 +0100 Subject: [PATCH 25/36] Move tcp-socket & udp-socket into types.wit to match other proposals. --- wit-0.3.0-draft/ip-name-lookup.wit | 36 +- wit-0.3.0-draft/tcp.wit | 325 ----------------- wit-0.3.0-draft/types.wit | 558 +++++++++++++++++++++++++++-- wit-0.3.0-draft/udp.wit | 210 ----------- wit-0.3.0-draft/world.wit | 4 - 5 files changed, 560 insertions(+), 573 deletions(-) delete mode 100644 wit-0.3.0-draft/tcp.wit delete mode 100644 wit-0.3.0-draft/udp.wit diff --git a/wit-0.3.0-draft/ip-name-lookup.wit b/wit-0.3.0-draft/ip-name-lookup.wit index c8c6b3f..7cc8b03 100644 --- a/wit-0.3.0-draft/ip-name-lookup.wit +++ b/wit-0.3.0-draft/ip-name-lookup.wit @@ -1,7 +1,39 @@ @since(version = 0.3.0) interface ip-name-lookup { @since(version = 0.3.0) - use types.{lookup-error, ip-address}; + use types.{ip-address}; + + /// Lookup error codes. + @since(version = 0.3.0) + enum error-code { + /// Unknown error + unknown, + + /// Access denied. + /// + /// POSIX equivalent: EACCES, EPERM + access-denied, + + /// `name` is a syntactically invalid domain name or IP address. + /// + /// POSIX equivalent: EINVAL + invalid-argument, + + /// Name does not exist or has no suitable associated IP addresses. + /// + /// POSIX equivalent: EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY + name-unresolvable, + + /// A temporary failure in name resolution occurred. + /// + /// POSIX equivalent: EAI_AGAIN + temporary-resolver-failure, + + /// A permanent failure in name resolution occurred. + /// + /// POSIX equivalent: EAI_FAIL + permanent-resolver-failure, + } /// Resolve an internet host name to a list of IP addresses. /// @@ -26,5 +58,5 @@ interface ip-name-lookup { /// - /// - @since(version = 0.3.0) - resolve-addresses: func(name: string) -> result, lookup-error>; + resolve-addresses: func(name: string) -> result, error-code>; } diff --git a/wit-0.3.0-draft/tcp.wit b/wit-0.3.0-draft/tcp.wit deleted file mode 100644 index 7deecb5..0000000 --- a/wit-0.3.0-draft/tcp.wit +++ /dev/null @@ -1,325 +0,0 @@ -@since(version = 0.3.0) -interface tcp { - @since(version = 0.3.0) - use wasi:clocks/monotonic-clock@0.3.0.{duration}; - @since(version = 0.3.0) - use types.{error-code, ip-socket-address, ip-address-family}; - - /// A TCP connection resource - /// - /// The connection is used to transmit and receive data. - @since(version = 0.3.0) - resource tcp-connection { - /// Transmit data to peer. - /// - /// The caller should close the stream when it has no more data to send - /// to the peer. Under normal circumstances this will cause a FIN packet - /// to be sent. Closing the stream is equivalent to calling - /// `shutdown(SHUT_WR)` in POSIX. - /// - /// This function may be called at most once and returns once the full - /// contents of the stream are transmitted or an error is encountered. - @since(version = 0.3.0) - send: func(data: stream) -> result<_, error-code>; - - /// Read data from peer. - /// - /// This function fails if `read` was already called before on this connection. - /// - /// On sucess, this function returns a stream and a future, which will resolve - /// to an error code if receiving data from stream fails. - /// The returned future resolves to success if receiving side of the connection is closed. - /// - /// If the caller is not expecting to receive any data from the peer, - /// they may cancel the receive task. Any data still in the receive queue - /// will be discarded. This is equivalent to calling `shutdown(SHUT_RD)` - /// in POSIX. - @since(version = 0.3.0) - receive: func() -> result, future>>>; - } - - /// A TCP socket resource. - /// - /// The socket can be in one of the following states: - /// - `unbound` - /// - `bound` (See note below) - /// - `listening` - /// - `connecting` - /// - `connected` - /// - `closed` - /// See - /// for 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`, `listening`, `connecting` or `connected`) - /// - /// In addition to the general error codes documented on the - /// `types::error-code` type, TCP socket methods may always return - /// `error(invalid-state)` when in the `closed` state. - @since(version = 0.3.0) - resource tcp-socket { - - /// 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. - /// - /// Unlike POSIX, WASI sockets have no notion of a socket-level - /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's - /// async support. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - constructor(address-family: ip-address-family); - - /// Bind the socket to 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 can be bound to. (EADDRNOTAVAIL) - /// - /// # 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. - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - bind: func(local-address: ip-socket-address) -> result<_, error-code>; - - /// Connect to a remote endpoint. - /// - /// On success, the socket is transitioned into the `connected` state and this function returns a connection resource. - /// - /// 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-state`: The socket is already in the `connecting` state. (EALREADY) - /// - `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) - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - connect: func(remote-address: ip-socket-address) -> result; - - /// Start listening return a stream of new inbound connections. - /// - /// Transitions the socket into the `listening` state. - /// - /// If the socket is not already explicitly bound, this function will - /// implicitly bind the socket to a random free port. - /// - /// The returned sockets are 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` - /// - /// # Typical errors - /// - `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) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - 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 - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - local-address: func() -> result; - - /// Get the remote address. - /// - /// # Typical errors - /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - remote-address: func() -> result; - - /// Whether the socket is in the `listening` state. - /// - /// Equivalent to the SO_ACCEPTCONN socket option. - @since(version = 0.3.0) - is-listening: func() -> bool; - - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - @since(version = 0.3.0) - 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 `connecting` or `connected` state. - @since(version = 0.3.0) - set-listen-backlog-size: func(value: u64) -> 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. - @since(version = 0.3.0) - keep-alive-enabled: func() -> result; - @since(version = 0.3.0) - set-keep-alive-enabled: func(value: bool) -> 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. - @since(version = 0.3.0) - keep-alive-idle-time: func() -> result; - @since(version = 0.3.0) - set-keep-alive-idle-time: func(value: duration) -> 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. - @since(version = 0.3.0) - keep-alive-interval: func() -> result; - @since(version = 0.3.0) - set-keep-alive-interval: func(value: duration) -> 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. - @since(version = 0.3.0) - keep-alive-count: func() -> result; - @since(version = 0.3.0) - set-keep-alive-count: func(value: u32) -> 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. - @since(version = 0.3.0) - hop-limit: func() -> result; - @since(version = 0.3.0) - set-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. - @since(version = 0.3.0) - receive-buffer-size: func() -> result; - @since(version = 0.3.0) - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - @since(version = 0.3.0) - send-buffer-size: func() -> result; - @since(version = 0.3.0) - set-send-buffer-size: func(value: u64) -> result<_, error-code>; - } -} diff --git a/wit-0.3.0-draft/types.wit b/wit-0.3.0-draft/types.wit index dae7096..ef6c8da 100644 --- a/wit-0.3.0-draft/types.wit +++ b/wit-0.3.0-draft/types.wit @@ -1,5 +1,8 @@ @since(version = 0.3.0) interface types { + @since(version = 0.3.0) + use wasi:clocks/monotonic-clock@0.3.0.{duration}; + /// Error codes. /// /// In theory, every API can return any error code. @@ -67,38 +70,6 @@ interface types { datagram-too-large, } - /// Lookup error codes. - @since(version = 0.3.0) - enum lookup-error { - /// Unknown error - unknown, - - /// Access denied. - /// - /// POSIX equivalent: EACCES, EPERM - access-denied, - - /// `name` is a syntactically invalid domain name or IP address. - /// - /// POSIX equivalent: EINVAL - invalid-argument, - - /// Name does not exist or has no suitable associated IP addresses. - /// - /// POSIX equivalent: EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY - name-unresolvable, - - /// A temporary failure in name resolution occurred. - /// - /// POSIX equivalent: EAI_AGAIN - temporary-resolver-failure, - - /// A permanent failure in name resolution occurred. - /// - /// POSIX equivalent: EAI_FAIL - permanent-resolver-failure, - } - @since(version = 0.3.0) enum ip-address-family { /// Similar to `AF_INET` in POSIX. @@ -144,4 +115,527 @@ interface types { ipv4(ipv4-socket-address), ipv6(ipv6-socket-address), } + + /// A TCP connection resource + /// + /// The connection is used to transmit and receive data. + @since(version = 0.3.0) + resource tcp-connection { + /// Transmit data to peer. + /// + /// The caller should close the stream when it has no more data to send + /// to the peer. Under normal circumstances this will cause a FIN packet + /// to be sent. Closing the stream is equivalent to calling + /// `shutdown(SHUT_WR)` in POSIX. + /// + /// This function may be called at most once and returns once the full + /// contents of the stream are transmitted or an error is encountered. + @since(version = 0.3.0) + send: func(data: stream) -> result<_, error-code>; + + /// Read data from peer. + /// + /// This function fails if `read` was already called before on this connection. + /// + /// On sucess, this function returns a stream and a future, which will resolve + /// to an error code if receiving data from stream fails. + /// The returned future resolves to success if receiving side of the connection is closed. + /// + /// If the caller is not expecting to receive any data from the peer, + /// they may cancel the receive task. Any data still in the receive queue + /// will be discarded. This is equivalent to calling `shutdown(SHUT_RD)` + /// in POSIX. + @since(version = 0.3.0) + receive: func() -> result, future>>>; + } + + /// A TCP socket resource. + /// + /// The socket can be in one of the following states: + /// - `unbound` + /// - `bound` (See note below) + /// - `listening` + /// - `connecting` + /// - `connected` + /// - `closed` + /// See + /// for 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`, `listening`, `connecting` or `connected`) + /// + /// In addition to the general error codes documented on the + /// `types::error-code` type, TCP socket methods may always return + /// `error(invalid-state)` when in the `closed` state. + @since(version = 0.3.0) + resource tcp-socket { + + /// 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. + /// + /// Unlike POSIX, WASI sockets have no notion of a socket-level + /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's + /// async support. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + constructor(address-family: ip-address-family); + + /// Bind the socket to 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 can be bound to. (EADDRNOTAVAIL) + /// + /// # 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. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + bind: func(local-address: ip-socket-address) -> result<_, error-code>; + + /// Connect to a remote endpoint. + /// + /// On success, the socket is transitioned into the `connected` state and this function returns a connection resource. + /// + /// 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-state`: The socket is already in the `connecting` state. (EALREADY) + /// - `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) + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + connect: func(remote-address: ip-socket-address) -> result; + + /// Start listening return a stream of new inbound connections. + /// + /// Transitions the socket into the `listening` state. + /// + /// If the socket is not already explicitly bound, this function will + /// implicitly bind the socket to a random free port. + /// + /// The returned sockets are 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` + /// + /// # Typical errors + /// - `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) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + 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 + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + local-address: func() -> result; + + /// Get the remote address. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + remote-address: func() -> result; + + /// Whether the socket is in the `listening` state. + /// + /// Equivalent to the SO_ACCEPTCONN socket option. + @since(version = 0.3.0) + is-listening: func() -> bool; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + @since(version = 0.3.0) + 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 `connecting` or `connected` state. + @since(version = 0.3.0) + set-listen-backlog-size: func(value: u64) -> 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. + @since(version = 0.3.0) + keep-alive-enabled: func() -> result; + @since(version = 0.3.0) + set-keep-alive-enabled: func(value: bool) -> 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. + @since(version = 0.3.0) + keep-alive-idle-time: func() -> result; + @since(version = 0.3.0) + set-keep-alive-idle-time: func(value: duration) -> 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. + @since(version = 0.3.0) + keep-alive-interval: func() -> result; + @since(version = 0.3.0) + set-keep-alive-interval: func(value: duration) -> 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. + @since(version = 0.3.0) + keep-alive-count: func() -> result; + @since(version = 0.3.0) + set-keep-alive-count: func(value: u32) -> 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. + @since(version = 0.3.0) + hop-limit: func() -> result; + @since(version = 0.3.0) + set-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. + @since(version = 0.3.0) + receive-buffer-size: func() -> result; + @since(version = 0.3.0) + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + @since(version = 0.3.0) + send-buffer-size: func() -> result; + @since(version = 0.3.0) + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + } + + /// A received datagram. + @since(version = 0.3.0) + 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. + @since(version = 0.3.0) + 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. + @since(version = 0.3.0) + resource udp-socket { + + /// 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. + /// + /// Unlike POSIX, WASI sockets have no notion of a socket-level + /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's + /// async support. + /// + /// # References: + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + constructor(address-family: ip-address-family); + + /// Bind the socket to 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 can be bound to. (EADDRNOTAVAIL) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + bind: func(local-address: ip-socket-address) -> result<_, error-code>; + + /// Set up inbound & outbound communication channels, optionally to a specific peer. + /// + /// The `datagrams` stream parameter represents the data to be sent out. + /// The returned stream represents the data that was received. + /// + /// 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`. If the socket was + /// not already explicitly bound, this function will implicitly bind the + /// socket to a random free port. + /// + /// When a `remote-address` is provided, the streams are limited to + /// communicating with that specific peer: + /// - The `outgoing-datagram` stream can only be used to send to this destination. + /// - The `incoming-datagram` stream 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 recent pair of streams will be + /// operational. Calling this function more than once effectively cancels + /// any previously started `transfer` tasks. + /// + /// 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) + /// } + /// ``` + /// + /// # 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) + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) + /// + /// # References + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + transfer: func(datagrams: list, 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 + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + 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 + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + remote-address: func() -> result; + + /// Whether this is a IPv4 or IPv6 socket. + /// + /// Equivalent to the SO_DOMAIN socket option. + @since(version = 0.3.0) + 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. + @since(version = 0.3.0) + unicast-hop-limit: func() -> result; + @since(version = 0.3.0) + 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. + @since(version = 0.3.0) + receive-buffer-size: func() -> result; + @since(version = 0.3.0) + set-receive-buffer-size: func(value: u64) -> result<_, error-code>; + @since(version = 0.3.0) + send-buffer-size: func() -> result; + @since(version = 0.3.0) + set-send-buffer-size: func(value: u64) -> result<_, error-code>; + } } diff --git a/wit-0.3.0-draft/udp.wit b/wit-0.3.0-draft/udp.wit deleted file mode 100644 index 4dd76fe..0000000 --- a/wit-0.3.0-draft/udp.wit +++ /dev/null @@ -1,210 +0,0 @@ -@since(version = 0.3.0) -interface udp { - @since(version = 0.3.0) - use types.{error-code, ip-socket-address, ip-address-family}; - - /// A received datagram. - @since(version = 0.3.0) - 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. - @since(version = 0.3.0) - 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. - @since(version = 0.3.0) - resource udp-socket { - - /// 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. - /// - /// Unlike POSIX, WASI sockets have no notion of a socket-level - /// `O_NONBLOCK` flag. Instead they fully rely on the Component Model's - /// async support. - /// - /// # References: - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - constructor(address-family: ip-address-family); - - /// Bind the socket to 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 can be bound to. (EADDRNOTAVAIL) - /// - /// # References - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - bind: func(local-address: ip-socket-address) -> result<_, error-code>; - - /// Set up inbound & outbound communication channels, optionally to a specific peer. - /// - /// The `datagrams` stream parameter represents the data to be sent out. - /// The returned stream represents the data that was received. - /// - /// 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`. If the socket was - /// not already explicitly bound, this function will implicitly bind the - /// socket to a random free port. - /// - /// When a `remote-address` is provided, the streams are limited to - /// communicating with that specific peer: - /// - The `outgoing-datagram` stream can only be used to send to this destination. - /// - The `incoming-datagram` stream 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 recent pair of streams will be - /// operational. Calling this function more than once effectively cancels - /// any previously started `transfer` tasks. - /// - /// 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) - /// } - /// ``` - /// - /// # 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) - /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) - /// - /// # References - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - transfer: func(datagrams: list, 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 - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - 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 - /// - - /// - - /// - - /// - - @since(version = 0.3.0) - remote-address: func() -> result; - - /// Whether this is a IPv4 or IPv6 socket. - /// - /// Equivalent to the SO_DOMAIN socket option. - @since(version = 0.3.0) - 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. - @since(version = 0.3.0) - unicast-hop-limit: func() -> result; - @since(version = 0.3.0) - 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. - @since(version = 0.3.0) - receive-buffer-size: func() -> result; - @since(version = 0.3.0) - set-receive-buffer-size: func(value: u64) -> result<_, error-code>; - @since(version = 0.3.0) - send-buffer-size: func() -> result; - @since(version = 0.3.0) - set-send-buffer-size: func(value: u64) -> result<_, error-code>; - } -} diff --git a/wit-0.3.0-draft/world.wit b/wit-0.3.0-draft/world.wit index 6704b67..a293a30 100644 --- a/wit-0.3.0-draft/world.wit +++ b/wit-0.3.0-draft/world.wit @@ -5,9 +5,5 @@ world imports { @since(version = 0.3.0) import types; @since(version = 0.3.0) - import udp; - @since(version = 0.3.0) - import tcp; - @since(version = 0.3.0) import ip-name-lookup; } From 3abda6e0bccd1743fa0736c94e5fbb3ec2fb0eaf Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 16:53:42 +0100 Subject: [PATCH 26/36] Make wit-bindgen work. It failed with: ``` Error: failed to resolve directory while parsing WIT for path [wit-0.3.0-draft] Caused by: 0: failed to process feature gate for type [duration] in package [wasi:sockets@0.3.0-draft] 1: feature gate cannot reference unreleased version 0.3.0 of package [wasi:sockets@0.3.0-draft] (current version 0.3.0-draft) ``` --- wit-0.3.0-draft/world.wit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wit-0.3.0-draft/world.wit b/wit-0.3.0-draft/world.wit index a293a30..6c9951d 100644 --- a/wit-0.3.0-draft/world.wit +++ b/wit-0.3.0-draft/world.wit @@ -1,4 +1,4 @@ -package wasi:sockets@0.3.0-draft; +package wasi:sockets@0.3.0; @since(version = 0.3.0) world imports { From e92d1440ad375ef4c6b31ca85a684032f7c87b69 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Thu, 16 Jan 2025 19:36:47 +0100 Subject: [PATCH 27/36] Update TcpSocketOperationalSemantics-0.3.0-draft.md Co-authored-by: Roman Volosatovs --- TcpSocketOperationalSemantics-0.3.0-draft.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TcpSocketOperationalSemantics-0.3.0-draft.md b/TcpSocketOperationalSemantics-0.3.0-draft.md index 3640163..b64e185 100644 --- a/TcpSocketOperationalSemantics-0.3.0-draft.md +++ b/TcpSocketOperationalSemantics-0.3.0-draft.md @@ -50,5 +50,5 @@ stateDiagram-v2 - Transitions annotated with `-> ok` only apply when the method returns successfully. - Calling a method from the wrong state returns `error(invalid-state)` and does not affect the state of the socket. - This diagram only includes the methods that impact the socket's state. For an overview of all methods and their required states, see [tcp.wit](./wit/tcp.wit) -- Client sockets returned by `listen()` are in immediately in the `connected` state. +- Client sockets returned by `listen()` are immediately in the `connected` state. - A socket resource can be dropped in any state. From f646c2557bd7e82d9a888b25ae76aa97a802b8f7 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 16 Jan 2025 18:44:31 +0100 Subject: [PATCH 28/36] feat: update wit-deps to 0.5.0 Signed-off-by: Roman Volosatovs --- .github/workflows/main.yml | 4 +++- wit-0.3.0-draft/deps.lock | 1 + wit-0.3.0-draft/deps.toml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 248ca1d..3e61870 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,10 +13,12 @@ jobs: - uses: actions/checkout@v4 - name: ensure `./wit/deps` are in sync run: | - curl -Lo 'wit-deps' https://github.com/bytecodealliance/wit-deps/releases/download/v0.3.3/wit-deps-x86_64-unknown-linux-musl + curl -Lo 'wit-deps' https://github.com/bytecodealliance/wit-deps/releases/download/v0.5.0/wit-deps-x86_64-unknown-linux-musl chmod +x wit-deps ./wit-deps lock + ./wit-deps -m wit-0.3.0-draft/deps.toml -l wit-0.3.0-draft/deps.lock -d wit-0.3.0-draft/deps lock git add -N wit/deps + git add -N wit-0.3.0-draft/deps git diff --exit-code - uses: WebAssembly/wit-abi-up-to-date@v22 with: diff --git a/wit-0.3.0-draft/deps.lock b/wit-0.3.0-draft/deps.lock index 20306c6..0386808 100644 --- a/wit-0.3.0-draft/deps.lock +++ b/wit-0.3.0-draft/deps.lock @@ -1,4 +1,5 @@ [clocks] url = "https://github.com/WebAssembly/wasi-clocks/archive/main.tar.gz" +subdir = "wit-0.3.0-draft" sha256 = "26e315db0d371495f8834edfc0e479042f94152ce677d96d54d3623d0e4ffb1e" sha512 = "e1c76f499435841316f9287b88d8173558e64f277c321ff390556de8707a0b18dd6c1749bbb17bbbba8d523da246ef6eb05c990ceddb762e03efb2ae30cacc76" diff --git a/wit-0.3.0-draft/deps.toml b/wit-0.3.0-draft/deps.toml index 3f6ad6d..e004547 100644 --- a/wit-0.3.0-draft/deps.toml +++ b/wit-0.3.0-draft/deps.toml @@ -1 +1 @@ -clocks = "https://github.com/WebAssembly/wasi-clocks/archive/main.tar.gz" +clocks = { url = "https://github.com/WebAssembly/wasi-clocks/archive/main.tar.gz", subdir = "wit-0.3.0-draft" } From bbebffa93d4af009b637b4b97e214180dc37ae0c Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Fri, 17 Jan 2025 21:19:01 +0100 Subject: [PATCH 29/36] Update wit-0.3.0-draft/types.wit Co-authored-by: Roman Volosatovs --- wit-0.3.0-draft/types.wit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wit-0.3.0-draft/types.wit b/wit-0.3.0-draft/types.wit index ef6c8da..4c4a851 100644 --- a/wit-0.3.0-draft/types.wit +++ b/wit-0.3.0-draft/types.wit @@ -568,7 +568,7 @@ interface types { /// - /// - @since(version = 0.3.0) - transfer: func(datagrams: list, remote-address: option) -> result, error-code>; + transfer: func(datagrams: stream, remote-address: option) -> result, error-code>; /// Get the current bound address. /// From 074d94884ac70680f8df63eb4e0be006afdb12ed Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Sun, 19 Jan 2025 18:01:17 +0100 Subject: [PATCH 30/36] Fix WSARecvMsg link --- wit-0.3.0-draft/types.wit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wit-0.3.0-draft/types.wit b/wit-0.3.0-draft/types.wit index 4c4a851..8014ffb 100644 --- a/wit-0.3.0-draft/types.wit +++ b/wit-0.3.0-draft/types.wit @@ -563,7 +563,7 @@ interface types { /// - /// - /// - - /// - + /// - /// - /// - /// - From 5efd1e95f5bb81ffc01e75a42b5aadb31462ea50 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Sun, 19 Jan 2025 19:22:17 +0100 Subject: [PATCH 31/36] Document the distinction between permanent and transient errors on `listen`, and provide guidance on how implementations may handle them. --- TcpSocketOperationalSemantics-0.3.0-draft.md | 2 +- wit-0.3.0-draft/types.wit | 36 ++++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/TcpSocketOperationalSemantics-0.3.0-draft.md b/TcpSocketOperationalSemantics-0.3.0-draft.md index b64e185..9aeeddf 100644 --- a/TcpSocketOperationalSemantics-0.3.0-draft.md +++ b/TcpSocketOperationalSemantics-0.3.0-draft.md @@ -15,7 +15,7 @@ interface tcp { listening(accept-stream), connecting(connect-future), connected, - closed, + closed(option), } } ``` diff --git a/wit-0.3.0-draft/types.wit b/wit-0.3.0-draft/types.wit index 8014ffb..2b7b462 100644 --- a/wit-0.3.0-draft/types.wit +++ b/wit-0.3.0-draft/types.wit @@ -255,12 +255,19 @@ interface types { /// Start listening return a stream of new inbound connections. /// - /// Transitions the socket into the `listening` state. + /// Transitions the socket into the `listening` state. This can be called + /// at most once per socket. /// /// If the socket is not already explicitly bound, this function will /// implicitly bind the socket to a random free port. /// - /// The returned sockets are bound and in the `connected` state. + /// Normally, the returned sockets are bound, in the `connected` state + /// and immediately ready for I/O. Though, depending on exact timing and + /// circumstances, a newly accepted connection may already be `closed` + /// by the time the server attempts to perform its first I/O on it. This + /// is true regardless of whether the WASI implementation uses + /// "synthesized" sockets or not (see Implementors Notes below). + /// /// The following properties are inherited from the listener socket: /// - `address-family` /// - `keep-alive-enabled` @@ -276,6 +283,31 @@ interface types { /// - `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) /// + /// # Implementors note + /// This method returns a single perpetual stream that should only close + /// on fatal errors (if any). Yet, the POSIX' `accept` function may also + /// return transient errors (e.g. ECONNABORTED). The exact details differ + /// per operation system. For example, the Linux manual mentions: + /// + /// > Linux accept() passes already-pending network errors on the new + /// > socket as an error code from accept(). This behavior differs from + /// > other BSD socket implementations. For reliable operation the + /// > application should detect the network errors defined for the + /// > protocol after accept() and treat them like EAGAIN by retrying. + /// > In the case of TCP/IP, these are ENETDOWN, EPROTO, ENOPROTOOPT, + /// > EHOSTDOWN, ENONET, EHOSTUNREACH, EOPNOTSUPP, and ENETUNREACH. + /// Source: https://man7.org/linux/man-pages/man2/accept.2.html + /// + /// WASI implementations have two options to handle this: + /// - Optionally log it and then skip over non-fatal errors returned by + /// `accept`. Guest code never gets to see these failures. Or: + /// - Synthesize a `tcp-socket` resource that exposes the error when + /// attempting to send or receive on it. Guest code then sees these + /// failures as regular I/O errors. + /// + /// In either case, the stream returned by this `listen` method remains + /// operational. + /// /// # References /// - /// - From 7545a4b229e572371e4c00d993ff61986dc3980d Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Sun, 19 Jan 2025 20:00:46 +0100 Subject: [PATCH 32/36] Move `send` & `receive` into `tcp-socket`, simplifying the signatures of `connect` & `listen` even further. --- wit-0.3.0-draft/types.wit | 86 +++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 35 deletions(-) diff --git a/wit-0.3.0-draft/types.wit b/wit-0.3.0-draft/types.wit index 2b7b462..5d9cdcb 100644 --- a/wit-0.3.0-draft/types.wit +++ b/wit-0.3.0-draft/types.wit @@ -116,39 +116,6 @@ interface types { ipv6(ipv6-socket-address), } - /// A TCP connection resource - /// - /// The connection is used to transmit and receive data. - @since(version = 0.3.0) - resource tcp-connection { - /// Transmit data to peer. - /// - /// The caller should close the stream when it has no more data to send - /// to the peer. Under normal circumstances this will cause a FIN packet - /// to be sent. Closing the stream is equivalent to calling - /// `shutdown(SHUT_WR)` in POSIX. - /// - /// This function may be called at most once and returns once the full - /// contents of the stream are transmitted or an error is encountered. - @since(version = 0.3.0) - send: func(data: stream) -> result<_, error-code>; - - /// Read data from peer. - /// - /// This function fails if `read` was already called before on this connection. - /// - /// On sucess, this function returns a stream and a future, which will resolve - /// to an error code if receiving data from stream fails. - /// The returned future resolves to success if receiving side of the connection is closed. - /// - /// If the caller is not expecting to receive any data from the peer, - /// they may cancel the receive task. Any data still in the receive queue - /// will be discarded. This is equivalent to calling `shutdown(SHUT_RD)` - /// in POSIX. - @since(version = 0.3.0) - receive: func() -> result, future>>>; - } - /// A TCP socket resource. /// /// The socket can be in one of the following states: @@ -251,7 +218,7 @@ interface types { /// - /// - @since(version = 0.3.0) - connect: func(remote-address: ip-socket-address) -> result; + connect: func(remote-address: ip-socket-address) -> result<_, error-code>; /// Start listening return a stream of new inbound connections. /// @@ -318,7 +285,56 @@ interface types { /// - /// - @since(version = 0.3.0) - listen: func() -> result>, error-code>; + listen: func() -> result, error-code>; + + /// Transmit data to peer. + /// + /// The caller should close the stream when it has no more data to send + /// to the peer. Under normal circumstances this will cause a FIN packet + /// to be sent out. Closing the stream is equivalent to calling + /// `shutdown(SHUT_WR)` in POSIX. + /// + /// This function may be called at most once and returns once the full + /// contents of the stream are transmitted or an error is encountered. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + send: func(data: stream) -> result<_, error-code>; + + /// Read data from peer. + /// + /// This function fails if `receive` was already called before on this connection. + /// + /// On success, this function returns a stream and a future, which will resolve + /// to an error code if receiving data from stream fails. + /// The returned future resolves to success if receiving side of the connection is closed. + /// + /// If the caller is not expecting to receive any data from the peer, + /// they may cancel the receive task. Any data still in the receive queue + /// will be discarded. This is equivalent to calling `shutdown(SHUT_RD)` + /// in POSIX. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) + /// - `connection-reset`: The connection was reset. (ECONNRESET) + /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + receive: func() -> result, future>>>; /// Get the bound local address. /// From 92248c5a0e858a690f8fea6d1d10be869df2c5bd Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Sun, 19 Jan 2025 21:43:50 +0100 Subject: [PATCH 33/36] Revert usage of `streams` for UDP. `stream`s can fail only once and then they're done, whereas UDP packets should be able to fail/succeed independently without affecting the transmission of other packets. Without the stream parameter and return value, the `transfer` method is beginning to look an afwul lot like `connect` again, so we might as well call it that. --- wit-0.3.0-draft/types.wit | 125 +++++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 43 deletions(-) diff --git a/wit-0.3.0-draft/types.wit b/wit-0.3.0-draft/types.wit index 5d9cdcb..2c0d559 100644 --- a/wit-0.3.0-draft/types.wit +++ b/wit-0.3.0-draft/types.wit @@ -212,6 +212,7 @@ interface types { /// - `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) + /// /// # References /// - /// - @@ -492,7 +493,7 @@ interface types { /// The source address. /// - /// This field is guaranteed to match the remote address the stream was initialized with, if any. + /// This field is guaranteed to match the remote address the socket is connected to, if any. /// /// Equivalent to the `src_addr` out parameter of `recvfrom`. remote-address: ip-socket-address, @@ -506,8 +507,8 @@ interface types { /// 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. + /// The requirements on this field depend on how the socket is configured: + /// - with a remote address: this field must be None or match the socket'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`. @@ -556,67 +557,105 @@ interface types { @since(version = 0.3.0) bind: func(local-address: ip-socket-address) -> result<_, error-code>; - /// Set up inbound & outbound communication channels, optionally to a specific peer. + /// Associate this socket with a specific peer address. /// - /// The `datagrams` stream parameter represents the data to be sent out. - /// The returned stream represents the data that was received. + /// 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`. If the socket was not already explicitly + /// bound, this function will implicitly bind the socket to a random + /// free port. /// - /// 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`. If the socket was - /// not already explicitly bound, this function will implicitly bind the - /// socket to a random free port. + /// When a UDP socket is "connected", the `send` and `receive` methods + /// are limited to communicating with that peer only: + /// - `send` can only be used to send to this destination. + /// - `receive` will only return datagrams sent from the provided `remote-address`. + /// + /// The name "connect" was kept to align with the existing POSIX + /// terminology. Other than that, this function only changes the local + /// socket configuration and does not generate any network traffic. + /// The peer is not aware of this "connection". /// - /// When a `remote-address` is provided, the streams are limited to - /// communicating with that specific peer: - /// - The `outgoing-datagram` stream can only be used to send to this destination. - /// - The `incoming-datagram` stream 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 recent pair of streams will be - /// operational. Calling this function more than once effectively cancels - /// any previously started `transfer` tasks. - /// - /// 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) - /// } - /// ``` - /// + /// its association, but only the most recent one will be effective. + /// /// # 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) /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) /// + /// # Implementors note + /// If the socket is already connected, some platforms (e.g. Linux) + /// require a disconnect before connecting to a different peer address. + /// /// # References /// - + /// - + /// - + /// - + @since(version = 0.3.0) + connect: func(remote-address: ip-socket-address) -> result<_, error-code>; + + /// Dissociate this socket from its peer address. + /// + /// After calling this method, `send` & `receive` are free to communicate + /// with any address again. + /// + /// The POSIX equivalent of this is calling `connect` with an `AF_UNSPEC` address. + /// + /// # Typical errors + /// - `invalid-state`: The socket is not connected. + /// + /// # References + /// - + /// - + /// - + /// - + @since(version = 0.3.0) + disconnect: func() -> result<_, error-code>; + + /// Send a message on the socket. + /// + /// # 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 `connect`. (EISCONN) + /// - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) + /// - `invalid-state`: The socket has not been bound yet. + /// - `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 /// - /// - - /// - - /// - - /// - /// - /// - - /// - - /// - - /// - /// - /// - /// - - /// - + /// - + @since(version = 0.3.0) + send: func(data: outgoing-datagram) -> result<_, error-code>; + + /// Receive a message on the socket. + /// + /// # Typical errors + /// - `invalid-state`: The socket has not been bound yet. + /// - `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 + /// - + /// - + /// - + /// - /// - /// - - /// - - /// - /// - @since(version = 0.3.0) - transfer: func(datagrams: stream, remote-address: option) -> result, error-code>; + receive: func() -> result; /// Get the current bound address. /// @@ -637,10 +676,10 @@ interface types { @since(version = 0.3.0) local-address: func() -> result; - /// Get the address the socket is currently streaming to. + /// Get the address the socket is currently "connected" to. /// /// # Typical errors - /// - `invalid-state`: The socket is not streaming to a specific remote address. (ENOTCONN) + /// - `invalid-state`: The socket is not "connected" to a specific remote address. (ENOTCONN) /// /// # References /// - From 148f43e9e891e16e9e78fc129f294ade3c708683 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Sun, 19 Jan 2025 22:25:24 +0100 Subject: [PATCH 34/36] Remove inbound/outbound-datagram records. They were added in anticipation of ComponentModel-level subtyping of value types, so that we could add additional fields (e.g. TTL, TOS, etc.) without breaking backwards-compatibility. AFAIK, that idea has been put on hold indefinitely, so we might as well make the 0.3.0 interface as simple as possible --- wit-0.3.0-draft/types.wit | 58 +++++++++++++++------------------------ 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/wit-0.3.0-draft/types.wit b/wit-0.3.0-draft/types.wit index 2c0d559..4d454fb 100644 --- a/wit-0.3.0-draft/types.wit +++ b/wit-0.3.0-draft/types.wit @@ -483,38 +483,6 @@ interface types { set-send-buffer-size: func(value: u64) -> result<_, error-code>; } - /// A received datagram. - @since(version = 0.3.0) - 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 socket is connected to, if any. - /// - /// Equivalent to the `src_addr` out parameter of `recvfrom`. - remote-address: ip-socket-address, - } - - /// A datagram to be sent out. - @since(version = 0.3.0) - record outgoing-datagram { - /// The payload. - data: list, - - /// The destination address. - /// - /// The requirements on this field depend on how the socket is configured: - /// - with a remote address: this field must be None or match the socket'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. @since(version = 0.3.0) resource udp-socket { @@ -614,7 +582,17 @@ interface types { @since(version = 0.3.0) disconnect: func() -> result<_, error-code>; - /// Send a message on the socket. + /// Send a message on the socket to a particular peer. + /// + /// If the socket is connected, the peer address may be left empty. In + /// that case this is equivalent to `send` in POSIX. Otherwise it is + /// equivalent to `sendto`. + /// + /// Additionally, if the socket is connected, a `remote-address` argument + /// _may_ be provided but then it must be identical to the address + /// passed to `connect`. + /// + /// Implementations may trap if the `data` length exceeds 64 KiB. /// /// # Typical errors /// - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) @@ -637,10 +615,18 @@ interface types { /// - /// - @since(version = 0.3.0) - send: func(data: outgoing-datagram) -> result<_, error-code>; + send: func(data: list, remote-address: option) -> result<_, error-code>; /// Receive a message on the socket. /// + /// On success, the return value contains a tuple of the received data + /// and the address of the sender. Theoretical maximum length of the + /// data is 64 KiB. Though in practice, it will typically be less than + /// 1500 bytes. + /// + /// If the socket is connected, the sender address is guaranteed to + /// match the remote address passed to `connect`. + /// /// # Typical errors /// - `invalid-state`: The socket has not been bound yet. /// - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) @@ -655,7 +641,7 @@ interface types { /// - /// - @since(version = 0.3.0) - receive: func() -> result; + receive: func() -> result, ip-socket-address>, error-code>; /// Get the current bound address. /// @@ -664,7 +650,7 @@ interface types { /// > 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. /// From dcf0b7611bdf8b773aba5955bbfec581b71d3814 Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Sun, 19 Jan 2025 22:32:08 +0100 Subject: [PATCH 35/36] Remove outer result from `receive` --- wit-0.3.0-draft/types.wit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wit-0.3.0-draft/types.wit b/wit-0.3.0-draft/types.wit index 4d454fb..9b4a243 100644 --- a/wit-0.3.0-draft/types.wit +++ b/wit-0.3.0-draft/types.wit @@ -335,7 +335,7 @@ interface types { /// - /// - @since(version = 0.3.0) - receive: func() -> result, future>>>; + receive: func() -> tuple, future>>; /// Get the bound local address. /// From 0e6b058ce4d99aa27337cf57c2027bb93871cc9c Mon Sep 17 00:00:00 2001 From: Dave Bakker Date: Sun, 19 Jan 2025 23:13:42 +0100 Subject: [PATCH 36/36] Clarify `address-family` --- wit-0.3.0-draft/types.wit | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wit-0.3.0-draft/types.wit b/wit-0.3.0-draft/types.wit index 9b4a243..c20954d 100644 --- a/wit-0.3.0-draft/types.wit +++ b/wit-0.3.0-draft/types.wit @@ -377,6 +377,8 @@ interface types { /// Whether this is a IPv4 or IPv6 socket. /// + /// This is the value passed to the constructor. + /// /// Equivalent to the SO_DOMAIN socket option. @since(version = 0.3.0) address-family: func() -> ip-address-family; @@ -677,6 +679,8 @@ interface types { /// Whether this is a IPv4 or IPv6 socket. /// + /// This is the value passed to the constructor. + /// /// Equivalent to the SO_DOMAIN socket option. @since(version = 0.3.0) address-family: func() -> ip-address-family;