diff --git a/proposals/sockets/TcpSocketOperationalSemantics-0.3.0-draft.md b/proposals/sockets/TcpSocketOperationalSemantics-0.3.0-draft.md index 9aeeddff..51555c72 100644 --- a/proposals/sockets/TcpSocketOperationalSemantics-0.3.0-draft.md +++ b/proposals/sockets/TcpSocketOperationalSemantics-0.3.0-draft.md @@ -33,7 +33,7 @@ stateDiagram-v2 state "closed" as Closed - [*] --> Unbound: create-tcp-socket() -> ok + [*] --> Unbound: create() -> ok Unbound --> Bound: bind() -> ok Unbound --> Connecting: connect() diff --git a/proposals/sockets/wit-0.3.0-draft/ip-name-lookup.wit b/proposals/sockets/wit-0.3.0-draft/ip-name-lookup.wit index d6e85d7d..a234eef4 100644 --- a/proposals/sockets/wit-0.3.0-draft/ip-name-lookup.wit +++ b/proposals/sockets/wit-0.3.0-draft/ip-name-lookup.wit @@ -7,6 +7,9 @@ interface ip-name-lookup { @since(version = 0.3.0-rc-2026-02-09) enum error-code { /// Unknown error + /// + /// This is an escape hatch for WASI implementations to handle failures + /// that can not be categorized under any of the other error codes. unknown, /// Access denied. @@ -37,9 +40,9 @@ interface ip-name-lookup { /// 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. + /// 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. /// @@ -49,9 +52,6 @@ interface ip-name-lookup { /// with at least one address. Additionally, this function never returns /// 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. - /// /// # References: /// - /// - diff --git a/proposals/sockets/wit-0.3.0-draft/types.wit b/proposals/sockets/wit-0.3.0-draft/types.wit index fd01dca5..9968486b 100644 --- a/proposals/sockets/wit-0.3.0-draft/types.wit +++ b/proposals/sockets/wit-0.3.0-draft/types.wit @@ -16,7 +16,10 @@ interface types { /// See each individual API for what the POSIX equivalents are. They sometimes differ per API. @since(version = 0.3.0-rc-2026-02-09) enum error-code { - /// Unknown error + /// Unknown error. + /// + /// This is an escape hatch for WASI implementations to handle failures + /// that can not be categorized under any of the other error codes. unknown, /// Access denied. @@ -26,47 +29,62 @@ interface types { /// The operation is not supported. /// - /// POSIX equivalent: EOPNOTSUPP + /// POSIX equivalent: EOPNOTSUPP, ENOPROTOOPT, EPFNOSUPPORT, EPROTONOSUPPORT, ESOCKTNOSUPPORT not-supported, /// One of the arguments is invalid. /// - /// POSIX equivalent: EINVAL + /// POSIX equivalent: EINVAL, EDESTADDRREQ, EAFNOSUPPORT invalid-argument, /// Not enough memory to complete the operation. /// - /// POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY + /// POSIX equivalent: ENOMEM, ENOBUFS out-of-memory, - + /// The operation timed out before it could finish completely. + /// + /// POSIX equivalent: ETIMEDOUT timeout, - + /// The operation is not valid in the socket's current state. invalid-state, - - /// A bind operation failed because the provided address is not an address that the `network` can bind to. + + /// The local address is not available. + /// + /// POSIX equivalent: EADDRNOTAVAIL address-not-bindable, - - /// A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. + + /// A bind operation failed because the provided address is already in + /// use or because there are no ephemeral ports available. + /// + /// POSIX equivalent: EADDRINUSE address-in-use, - - /// The remote address is not reachable + + /// The remote address is not reachable. + /// + /// POSIX equivalent: EHOSTUNREACH, EHOSTDOWN, ENETDOWN, ENETUNREACH, ENONET remote-unreachable, - - /// The TCP connection was forcefully rejected + /// The connection was forcefully rejected. + /// + /// POSIX equivalent: ECONNREFUSED connection-refused, - - /// The TCP connection was reset. + + /// The connection was reset. + /// + /// POSIX equivalent: ECONNRESET connection-reset, - - /// A TCP connection was aborted. + + /// The connection was aborted. + /// + /// POSIX equivalent: ECONNABORTED connection-aborted, - /// The size of a datagram sent to a UDP socket exceeded the maximum /// supported size. + /// + /// POSIX equivalent: EMSGSIZE datagram-too-large, } @@ -132,6 +150,19 @@ interface types { /// the term "bound" without backticks it actually means: in the `bound` state *or higher*. /// (i.e. `bound`, `listening`, `connecting` or `connected`) /// + /// WASI uses shared ownership semantics: the `tcp-socket` handle and all + /// derived `stream` and `future` values reference a single underlying OS + /// socket: + /// - Send/receive streams remain functional after the original `tcp-socket` + /// handle is dropped. + /// - The stream returned by `listen` behaves similarly. + /// - Client sockets returned by `tcp-socket::listen` are independent and do + /// not keep the listening socket alive. + /// + /// The OS socket is closed only after the last handle is dropped. This + /// model has observable effects; for example, it affects when the local + /// port binding is released. + /// /// 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. @@ -140,13 +171,17 @@ interface types { /// 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. + /// 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. /// + /// # Typical errors + /// - `not-supported`: The `address-family` is not supported. (EAFNOSUPPORT) + /// /// # References /// - /// - @@ -157,9 +192,10 @@ interface types { /// 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. + /// 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 @@ -176,10 +212,11 @@ interface types { /// - `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. + /// The 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. /// /// # References /// - @@ -191,7 +228,12 @@ interface types { /// Connect to a remote endpoint. /// - /// On success, the socket is transitioned into the `connected` state and this function returns a connection resource. + /// On success, the socket is transitioned into the `connected` state + /// and 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. /// /// 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 @@ -306,6 +348,7 @@ interface types { /// /// # Typical errors /// - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) + /// - `invalid-state`: `send` has already been called on this socket. /// - `connection-reset`: The connection was reset. (ECONNRESET) /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) /// @@ -319,27 +362,23 @@ interface types { /// Read data from peer. /// - /// This function returns a `stream` which provides the data received from the - /// socket, and a `future` providing additional error information in case the - /// socket is closed abnormally. + /// Returns a `stream` of data sent by the peer. The implementation + /// drops the stream once no more data is available. At that point, the + /// returned `future` resolves to: + /// - `ok` after a graceful shutdown from the peer (i.e. a FIN packet), or + /// - `err` if the socket was closed abnormally. /// - /// If the socket is closed normally, `stream.read` on the `stream` will return - /// `read-status::closed` with no `error-context` and the future resolves to - /// the value `ok`. If the socket is closed abnormally, `stream.read` on the - /// `stream` returns `read-status::closed` with an `error-context` and the future - /// resolves to `err` with an `error-code`. + /// `receive` may be called only once per socket. Subsequent calls return + /// a closed stream and a future resolved to `err(invalid-state)`. /// - /// `receive` is meant to be called only once per socket. If it is called more - /// than once, the subsequent calls return a new `stream` that fails as if it - /// were closed abnormally. - /// - /// If the caller is not expecting to receive any data from the peer, - /// they may drop the stream. Any data still in the receive queue + /// If the caller is not expecting to receive any more data from the peer, + /// they should drop the stream. 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) + /// - `invalid-state`: `receive` has already been called on this socket. /// - `connection-reset`: The connection was reset. (ECONNRESET) /// - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) /// @@ -357,7 +396,8 @@ interface types { /// > 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 `get-local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// WASI is stricter and requires `get-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. @@ -397,10 +437,12 @@ interface types { @since(version = 0.3.0-rc-2026-02-09) get-address-family: func() -> ip-address-family; - /// Hints the desired listen queue size. Implementations are free to ignore this. + /// 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. + /// 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. @@ -415,7 +457,8 @@ interface types { /// - `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. + /// 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-rc-2026-02-09) @@ -423,11 +466,13 @@ interface types { @since(version = 0.3.0-rc-2026-02-09) 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. + /// 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. + /// All other values are accepted without error, but may be + /// clamped or rounded. As a result, the value read back from + /// this setting may differ from the value that was set. /// /// Equivalent to the TCP_KEEPIDLE socket option. (TCP_KEEPALIVE on MacOS) /// @@ -441,8 +486,9 @@ interface types { /// 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. + /// All other values are accepted without error, but may be + /// clamped or rounded. As a result, the value read back from + /// this setting may differ from the value that was set. /// /// Equivalent to the TCP_KEEPINTVL socket option. /// @@ -453,11 +499,13 @@ interface types { @since(version = 0.3.0-rc-2026-02-09) set-keep-alive-interval: func(value: duration) -> result<_, error-code>; - /// The maximum amount of keepalive packets TCP should send before aborting the connection. + /// 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. + /// All other values are accepted without error, but may be + /// clamped or rounded. As a result, the value read back from + /// this setting may differ from the value that was set. /// /// Equivalent to the TCP_KEEPCNT socket option. /// @@ -479,11 +527,22 @@ interface types { @since(version = 0.3.0-rc-2026-02-09) set-hop-limit: func(value: u8) -> result<_, error-code>; - /// The kernel buffer space reserved for sends/receives on this socket. + /// Kernel buffer space reserved for sending/receiving on this socket. + /// Implementations usually treat this as a cap the buffer can grow to, + /// rather than allocating the full amount immediately. /// /// 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. + /// All other values are accepted without error, but may be + /// clamped or rounded. As a result, the value read back from + /// this setting may differ from the value that was set. + /// + /// This is only a performance hint. The implementation may ignore it or + /// tweak it based on real traffic patterns. + /// Linux and macOS appear to behave differently depending on whether a + /// buffer size was explicitly set. When set, they tend to honor it; when + /// not set, they dynamically adjust the buffer size as the connection + /// progresses. This is especially noticeable when comparing the values + /// from before and after connection establishment. /// /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. /// @@ -505,8 +564,9 @@ interface types { /// 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. + /// 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 @@ -522,9 +582,10 @@ interface types { /// 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. + /// 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) @@ -583,7 +644,7 @@ interface types { /// Dissociate this socket from its peer address. /// /// After calling this method, `send` & `receive` are free to communicate - /// with any address again. + /// with any remote address again. /// /// The POSIX equivalent of this is calling `connect` with an `AF_UNSPEC` address. /// @@ -608,6 +669,9 @@ interface types { /// _may_ be provided but then it must be identical to the address /// passed to `connect`. /// + /// If the socket has not been explicitly bound, it will be + /// implicitly bound to a random free port. + /// /// Implementations may trap if the `data` length exceeds 64 KiB. /// /// # Typical errors @@ -619,6 +683,14 @@ interface types { /// - `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) + /// - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) + /// + /// # Implementors note + /// WASI requires `send` to perform an implicit bind if the socket + /// has not been bound. Not all platforms (notably Windows) exhibit + /// this behavior natively. On such platforms, the WASI implementation + /// should emulate it by performing the bind if the guest has not + /// already done so. /// /// # References /// - @@ -664,7 +736,8 @@ interface types { /// > 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 `get-local-address` to return `invalid-state` when the socket hasn't been bound yet. + /// WASI is stricter and requires `get-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. @@ -709,11 +782,14 @@ interface types { @since(version = 0.3.0-rc-2026-02-09) set-unicast-hop-limit: func(value: u8) -> result<_, error-code>; - /// The kernel buffer space reserved for sends/receives on this socket. + /// Kernel buffer space reserved for sending/receiving on this socket. + /// Implementations usually treat this as a cap the buffer can grow to, + /// rather than allocating the full amount immediately. /// /// 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. + /// All other values are accepted without error, but may be + /// clamped or rounded. As a result, the value read back from + /// this setting may differ from the value that was set. /// /// Equivalent to the SO_RCVBUF and SO_SNDBUF socket options. ///