⍝? 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. #import("wasi:sockets/network", "network") class 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. enumerate ErrorCode { ⍝? Unknown error #export("unknown") Unknown, ⍝? Access denied. ⍝? ⍝? POSIX equivalent: EACCES, EPERM #export("access-denied") AccessDenied, ⍝? The operation is not supported. ⍝? ⍝? POSIX equivalent: EOPNOTSUPP #export("not-supported") NotSupported, ⍝? One of the arguments is invalid. ⍝? ⍝? POSIX equivalent: EINVAL #export("invalid-argument") InvalidArgument, ⍝? Not enough memory to complete the operation. ⍝? ⍝? POSIX equivalent: ENOMEM, ENOBUFS, EAI_MEMORY #export("out-of-memory") OutOfMemory, ⍝? The operation timed out before it could finish completely. #export("timeout") Timeout, ⍝? This operation is incompatible with another asynchronous operation that is already in progress. ⍝? ⍝? POSIX equivalent: EALREADY #export("concurrency-conflict") ConcurrencyConflict, ⍝? Trying to finish an asynchronous operation that: ⍝? - has not been started yet, or: ⍝? - was already finished by a previous `finish-*` call. ⍝? ⍝? Note: this is scheduled to be removed when `future`s are natively supported. #export("not-in-progress") NotInProgress, ⍝? The operation has been aborted because it could not be completed immediately. ⍝? ⍝? Note: this is scheduled to be removed when `future`s are natively supported. #export("would-block") WouldBlock, ⍝? The operation is not valid in the socket's current state. #export("invalid-state") InvalidState, ⍝? A new socket resource could not be created because of a system limit. #export("new-socket-limit") NewSocketLimit, ⍝? A bind operation failed because the provided address is not an address that the `network` can bind to. #export("address-not-bindable") AddressNotBindable, ⍝? A bind operation failed because the provided address is already in use or because there are no ephemeral ports available. #export("address-in-use") AddressInUse, ⍝? The remote address is not reachable #export("remote-unreachable") RemoteUnreachable, ⍝? The TCP connection was forcefully rejected #export("connection-refused") ConnectionRefused, ⍝? The TCP connection was reset. #export("connection-reset") ConnectionReset, ⍝? A TCP connection was aborted. #export("connection-aborted") ConnectionAborted, ⍝? The size of a datagram sent to a UDP socket exceeded the maximum ⍝? supported size. #export("datagram-too-large") DatagramTooLarge, ⍝? Name does not exist or has no suitable associated IP addresses. #export("name-unresolvable") NameUnresolvable, ⍝? A temporary failure in name resolution occurred. #export("temporary-resolver-failure") TemporaryResolverFailure, ⍝? A permanent failure in name resolution occurred. #export("permanent-resolver-failure") PermanentResolverFailure, } enumerate IpAddressFamily { ⍝? Similar to `AF_INET` in POSIX. #export("ipv4") Ipv4, ⍝? Similar to `AF_INET6` in POSIX. #export("ipv6") Ipv6, } alias typus Ipv4Address: (u8, u8, u8, u8) { } alias typus Ipv6Address: (u16, u16, u16, u16, u16, u16, u16, u16) { } unite IpAddress { #export("ipv4") Ipv4 { value: (u8, u8, u8, u8) }, #export("ipv6") Ipv6 { value: (u16, u16, u16, u16, u16, u16, u16, u16) }, } class Ipv4SocketAddress { ⍝? sin_port port: u16, ⍝? sin_addr address: (u8, u8, u8, u8), } class Ipv6SocketAddress { ⍝? sin6_port port: u16, ⍝? sin6_flowinfo flow_info: u32, ⍝? sin6_addr address: (u16, u16, u16, u16, u16, u16, u16, u16), ⍝? sin6_scope_id scope_id: u32, } unite IpSocketAddress { #export("ipv4") Ipv4 { value: Ipv4SocketAddress }, #export("ipv6") Ipv6 { value: Ipv6SocketAddress }, } alias typus Network: Network { } ⍝? Get a handle to the default network. #import("wasi:sockets/instance-network", "instance-network") micro instance_network() -> Network { } alias typus Pollable: Pollable { } alias typus Network: Network { } alias typus ErrorCode: ErrorCode { } alias typus IpAddress: IpAddress { } #import("wasi:sockets/ip-name-lookup", "resolve-address-stream") class ResolveAddressStream { ⍝? Returns the next address from the resolver. ⍝? ⍝? This function should be called multiple times. On each call, it will ⍝? return the next address in connection order preference. If all ⍝? addresses have been exhausted, this function returns `none`. ⍝? ⍝? This function never returns IPv4-mapped IPv6 addresses. ⍝? ⍝? # Typical errors ⍝? - `name-unresolvable`: Name does not exist or has no suitable associated IP addresses. (EAI_NONAME, EAI_NODATA, EAI_ADDRFAMILY) ⍝? - `temporary-resolver-failure`: A temporary failure in name resolution occurred. (EAI_AGAIN) ⍝? - `permanent-resolver-failure`: A permanent failure in name resolution occurred. (EAI_FAIL) ⍝? - `would-block`: A result is not available yet. (EWOULDBLOCK, EAGAIN) #import("wasi:sockets/ip-name-lookup", "[method]resolve-address-stream.resolve-next-address") resolve_next_address(self) -> Result { } ⍝? Create a `pollable` which will resolve once the stream is ready for I/O. ⍝? ⍝? Note: this function is here for WASI Preview2 only. ⍝? It's planned to be removed when `future` is natively supported in Preview3. #import("wasi:sockets/ip-name-lookup", "[method]resolve-address-stream.subscribe") subscribe(self) -> Pollable { } } ⍝? 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: ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/ip-name-lookup", "resolve-addresses") micro resolve_addresses(network: &Network, name: utf8) -> Result { } alias typus InputStream: InputStream { } alias typus OutputStream: OutputStream { } alias typus Pollable: Pollable { } alias typus Duration: u64 { } alias typus Network: Network { } alias typus ErrorCode: ErrorCode { } alias typus IpSocketAddress: IpSocketAddress { } alias typus IpAddressFamily: IpAddressFamily { } enumerate ShutdownType { ⍝? Similar to `SHUT_RD` in POSIX. #export("receive") Receive, ⍝? Similar to `SHUT_WR` in POSIX. #export("send") Send, ⍝? Similar to `SHUT_RDWR` in POSIX. #export("both") Both, } ⍝? A TCP socket resource. ⍝? ⍝? The socket can be in one of the following states: ⍝? - `unbound` ⍝? - `bind-in-progress` ⍝? - `bound` (See note below) ⍝? - `listen-in-progress` ⍝? - `listening` ⍝? - `connect-in-progress` ⍝? - `connected` ⍝? - `closed` ⍝? See ⍝? for a more information. ⍝? ⍝? Note: Except where explicitly mentioned, whenever this documentation uses ⍝? the term "bound" without backticks it actually means: in the `bound` state *or higher*. ⍝? (i.e. `bound`, `listen-in-progress`, `listening`, `connect-in-progress` or `connected`) ⍝? ⍝? In addition to the general error codes documented on the ⍝? `network::error-code` type, TCP socket methods may always return ⍝? `error(invalid-state)` when in the `closed` state. #import("wasi:sockets/tcp", "tcp-socket") class TcpSocket { ⍝? Bind the socket to a specific network on the provided IP address and port. ⍝? ⍝? If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which ⍝? network interface(s) to bind to. ⍝? If the TCP/UDP port is zero, the socket will be bound to a random free port. ⍝? ⍝? Bind can be attempted multiple times on the same socket, even with ⍝? different arguments on each iteration. But never concurrently and ⍝? only as long as the previous bind failed. Once a bind succeeds, the ⍝? binding can't be changed anymore. ⍝? ⍝? # Typical errors ⍝? - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) ⍝? - `invalid-argument`: `local-address` is not a unicast address. (EINVAL) ⍝? - `invalid-argument`: `local-address` is an IPv4-mapped IPv6 address. (EINVAL) ⍝? - `invalid-state`: The socket is already bound. (EINVAL) ⍝? - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) ⍝? - `address-in-use`: Address is already in use. (EADDRINUSE) ⍝? - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) ⍝? - `not-in-progress`: A `bind` operation is not in progress. ⍝? - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) ⍝? ⍝? # Implementors note ⍝? When binding to a non-zero port, this bind operation shouldn't be affected by the TIME_WAIT ⍝? state of a recently closed socket on the same local address. In practice this means that the SO_REUSEADDR ⍝? socket option should be set implicitly on all platforms, except on Windows where this is the default behavior ⍝? and SO_REUSEADDR performs something different entirely. ⍝? ⍝? Unlike in POSIX, in WASI the bind operation is async. This enables ⍝? interactive WASI hosts to inject permission prompts. Runtimes that ⍝? don't want to make use of this ability can simply call the native ⍝? `bind` as part of either `start-bind` or `finish-bind`. ⍝? ⍝? # References ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/tcp", "[method]tcp-socket.start-bind") start_bind(self, network: &Network, local_address: IpSocketAddress) -> Result<(), ErrorCode> { } #import("wasi:sockets/tcp", "[method]tcp-socket.finish-bind") finish_bind(self) -> Result<(), ErrorCode> { } ⍝? Connect to a remote endpoint. ⍝? ⍝? On success: ⍝? - the socket is transitioned into the `connection` state. ⍝? - a pair of streams is returned that can be used to read & write to the connection ⍝? ⍝? After a failed connection attempt, the socket will be in the `closed` ⍝? state and the only valid action left is to `drop` the socket. A single ⍝? socket can not be used to connect more than once. ⍝? ⍝? # Typical errors ⍝? - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) ⍝? - `invalid-argument`: `remote-address` is not a unicast address. (EINVAL, ENETUNREACH on Linux, EAFNOSUPPORT on MacOS) ⍝? - `invalid-argument`: `remote-address` is an IPv4-mapped IPv6 address. (EINVAL, EADDRNOTAVAIL on Illumos) ⍝? - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EADDRNOTAVAIL on Windows) ⍝? - `invalid-argument`: The port in `remote-address` is set to 0. (EADDRNOTAVAIL on Windows) ⍝? - `invalid-argument`: The socket is already attached to a different network. The `network` passed to `connect` must be identical to the one passed to `bind`. ⍝? - `invalid-state`: The socket is already in the `connected` state. (EISCONN) ⍝? - `invalid-state`: The socket is already in the `listening` state. (EOPNOTSUPP, EINVAL on Windows) ⍝? - `timeout`: Connection timed out. (ETIMEDOUT) ⍝? - `connection-refused`: The connection was forcefully rejected. (ECONNREFUSED) ⍝? - `connection-reset`: The connection was reset. (ECONNRESET) ⍝? - `connection-aborted`: The connection was aborted. (ECONNABORTED) ⍝? - `remote-unreachable`: The remote address is not reachable. (EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) ⍝? - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE, EADDRNOTAVAIL on Linux, EAGAIN on BSD) ⍝? - `not-in-progress`: A connect operation is not in progress. ⍝? - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) ⍝? ⍝? # Implementors note ⍝? The POSIX equivalent of `start-connect` is the regular `connect` syscall. ⍝? Because all WASI sockets are non-blocking this is expected to return ⍝? EINPROGRESS, which should be translated to `ok()` in WASI. ⍝? ⍝? The POSIX equivalent of `finish-connect` is a `poll` for event `POLLOUT` ⍝? with a timeout of 0 on the socket descriptor. Followed by a check for ⍝? the `SO_ERROR` socket option, in case the poll signaled readiness. ⍝? ⍝? # References ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/tcp", "[method]tcp-socket.start-connect") start_connect(self, network: &Network, remote_address: IpSocketAddress) -> Result<(), ErrorCode> { } #import("wasi:sockets/tcp", "[method]tcp-socket.finish-connect") finish_connect(self) -> Result<(InputStream, OutputStream), ErrorCode> { } ⍝? Start listening for new connections. ⍝? ⍝? Transitions the socket into the `listening` state. ⍝? ⍝? Unlike POSIX, the socket must already be explicitly bound. ⍝? ⍝? # Typical errors ⍝? - `invalid-state`: The socket is not bound to any local address. (EDESTADDRREQ) ⍝? - `invalid-state`: The socket is already in the `connected` state. (EISCONN, EINVAL on BSD) ⍝? - `invalid-state`: The socket is already in the `listening` state. ⍝? - `address-in-use`: Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE) ⍝? - `not-in-progress`: A listen operation is not in progress. ⍝? - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) ⍝? ⍝? # Implementors note ⍝? Unlike in POSIX, in WASI the listen operation is async. This enables ⍝? interactive WASI hosts to inject permission prompts. Runtimes that ⍝? don't want to make use of this ability can simply call the native ⍝? `listen` as part of either `start-listen` or `finish-listen`. ⍝? ⍝? # References ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/tcp", "[method]tcp-socket.start-listen") start_listen(self) -> Result<(), ErrorCode> { } #import("wasi:sockets/tcp", "[method]tcp-socket.finish-listen") finish_listen(self) -> Result<(), ErrorCode> { } ⍝? Accept a new client socket. ⍝? ⍝? The returned socket is bound and in the `connected` state. The following properties are inherited from the listener socket: ⍝? - `address-family` ⍝? - `keep-alive-enabled` ⍝? - `keep-alive-idle-time` ⍝? - `keep-alive-interval` ⍝? - `keep-alive-count` ⍝? - `hop-limit` ⍝? - `receive-buffer-size` ⍝? - `send-buffer-size` ⍝? ⍝? On success, this function returns the newly accepted client socket along with ⍝? a pair of streams that can be used to read & write to the connection. ⍝? ⍝? # Typical errors ⍝? - `invalid-state`: Socket is not in the `listening` state. (EINVAL) ⍝? - `would-block`: No pending connections at the moment. (EWOULDBLOCK, EAGAIN) ⍝? - `connection-aborted`: An incoming connection was pending, but was terminated by the client before this listener could accept it. (ECONNABORTED) ⍝? - `new-socket-limit`: The new socket resource could not be created because of a system limit. (EMFILE, ENFILE) ⍝? ⍝? # References ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/tcp", "[method]tcp-socket.accept") accept(self) -> Result<(TcpSocket, InputStream, OutputStream), ErrorCode> { } ⍝? 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 ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/tcp", "[method]tcp-socket.local-address") local_address(self) -> Result { } ⍝? Get the remote address. ⍝? ⍝? # Typical errors ⍝? - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN) ⍝? ⍝? # References ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/tcp", "[method]tcp-socket.remote-address") remote_address(self) -> Result { } ⍝? Whether the socket is in the `listening` state. ⍝? ⍝? Equivalent to the SO_ACCEPTCONN socket option. #import("wasi:sockets/tcp", "[method]tcp-socket.is-listening") is_listening(self) -> bool { } ⍝? Whether this is a IPv4 or IPv6 socket. ⍝? ⍝? Equivalent to the SO_DOMAIN socket option. #import("wasi:sockets/tcp", "[method]tcp-socket.address-family") address_family(self) -> IpAddressFamily { } ⍝? 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. #import("wasi:sockets/tcp", "[method]tcp-socket.set-listen-backlog-size") set_listen_backlog_size(self, value: u64) -> Result<(), ErrorCode> { } ⍝? 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. #import("wasi:sockets/tcp", "[method]tcp-socket.keep-alive-enabled") keep_alive_enabled(self) -> Result { } #import("wasi:sockets/tcp", "[method]tcp-socket.set-keep-alive-enabled") set_keep_alive_enabled(self, value: bool) -> Result<(), ErrorCode> { } ⍝? 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. #import("wasi:sockets/tcp", "[method]tcp-socket.keep-alive-idle-time") keep_alive_idle_time(self) -> Result { } #import("wasi:sockets/tcp", "[method]tcp-socket.set-keep-alive-idle-time") set_keep_alive_idle_time(self, value: u64) -> Result<(), ErrorCode> { } ⍝? 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. #import("wasi:sockets/tcp", "[method]tcp-socket.keep-alive-interval") keep_alive_interval(self) -> Result { } #import("wasi:sockets/tcp", "[method]tcp-socket.set-keep-alive-interval") set_keep_alive_interval(self, value: u64) -> Result<(), ErrorCode> { } ⍝? 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. #import("wasi:sockets/tcp", "[method]tcp-socket.keep-alive-count") keep_alive_count(self) -> Result { } #import("wasi:sockets/tcp", "[method]tcp-socket.set-keep-alive-count") set_keep_alive_count(self, value: u32) -> Result<(), ErrorCode> { } ⍝? 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. #import("wasi:sockets/tcp", "[method]tcp-socket.hop-limit") hop_limit(self) -> Result { } #import("wasi:sockets/tcp", "[method]tcp-socket.set-hop-limit") set_hop_limit(self, value: u8) -> Result<(), ErrorCode> { } ⍝? 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. #import("wasi:sockets/tcp", "[method]tcp-socket.receive-buffer-size") receive_buffer_size(self) -> Result { } #import("wasi:sockets/tcp", "[method]tcp-socket.set-receive-buffer-size") set_receive_buffer_size(self, value: u64) -> Result<(), ErrorCode> { } #import("wasi:sockets/tcp", "[method]tcp-socket.send-buffer-size") send_buffer_size(self) -> Result { } #import("wasi:sockets/tcp", "[method]tcp-socket.set-send-buffer-size") set_send_buffer_size(self, value: u64) -> Result<(), ErrorCode> { } ⍝? Create a `pollable` which can be used to poll for, or block on, ⍝? completion of any of the asynchronous operations of this socket. ⍝? ⍝? When `finish-bind`, `finish-listen`, `finish-connect` or `accept` ⍝? return `error(would-block)`, this pollable can be used to wait for ⍝? their success or failure, after which the method can be retried. ⍝? ⍝? The pollable is not limited to the async operation that happens to be ⍝? in progress at the time of calling `subscribe` (if any). Theoretically, ⍝? `subscribe` only has to be called once per socket and can then be ⍝? (re)used for the remainder of the socket's lifetime. ⍝? ⍝? See ⍝? for a more information. ⍝? ⍝? Note: this function is here for WASI Preview2 only. ⍝? It's planned to be removed when `future` is natively supported in Preview3. #import("wasi:sockets/tcp", "[method]tcp-socket.subscribe") subscribe(self) -> Pollable { } ⍝? Initiate a graceful shutdown. ⍝? ⍝? - `receive`: The socket is not expecting to receive any data from ⍝? the peer. The `input-stream` associated with this socket will be ⍝? closed. Any data still in the receive queue at time of calling ⍝? this method will be discarded. ⍝? - `send`: The socket has no more data to send to the peer. The `output-stream` ⍝? associated with this socket will be closed and a FIN packet will be sent. ⍝? - `both`: Same effect as `receive` & `send` combined. ⍝? ⍝? This function is idempotent. Shutting a down a direction more than once ⍝? has no effect and returns `ok`. ⍝? ⍝? The shutdown function does not close (drop) the socket. ⍝? ⍝? # Typical errors ⍝? - `invalid-state`: The socket is not in the `connected` state. (ENOTCONN) ⍝? ⍝? # References ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/tcp", "[method]tcp-socket.shutdown") shutdown(self, shutdown_type: ShutdownType) -> Result<(), ErrorCode> { } } alias typus Network: Network { } alias typus ErrorCode: ErrorCode { } alias typus IpAddressFamily: IpAddressFamily { } alias typus TcpSocket: TcpSocket { } ⍝? 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 ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/tcp-create-socket", "create-tcp-socket") micro create_tcp_socket(address_family: IpAddressFamily) -> Result { } alias typus Pollable: Pollable { } alias typus Network: Network { } alias typus ErrorCode: ErrorCode { } alias typus IpSocketAddress: IpSocketAddress { } alias typus IpAddressFamily: IpAddressFamily { } ⍝? A received datagram. class IncomingDatagram { ⍝? The payload. ⍝? ⍝? Theoretical max size: ~64 KiB. In practice, typically less than 1500 bytes. data: Array, ⍝? 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: IpSocketAddress, } ⍝? A datagram to be sent out. class OutgoingDatagram { ⍝? The payload. data: Array, ⍝? 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: IpSocketAddress?, } ⍝? A UDP socket handle. #import("wasi:sockets/udp", "udp-socket") class UdpSocket { ⍝? Bind the socket to a specific network on the provided IP address and port. ⍝? ⍝? If the IP address is zero (`0.0.0.0` in IPv4, `::` in IPv6), it is left to the implementation to decide which ⍝? network interface(s) to bind to. ⍝? If the port is zero, the socket will be bound to a random free port. ⍝? ⍝? # Typical errors ⍝? - `invalid-argument`: The `local-address` has the wrong address family. (EAFNOSUPPORT, EFAULT on Windows) ⍝? - `invalid-state`: The socket is already bound. (EINVAL) ⍝? - `address-in-use`: No ephemeral ports available. (EADDRINUSE, ENOBUFS on Windows) ⍝? - `address-in-use`: Address is already in use. (EADDRINUSE) ⍝? - `address-not-bindable`: `local-address` is not an address that the `network` can bind to. (EADDRNOTAVAIL) ⍝? - `not-in-progress`: A `bind` operation is not in progress. ⍝? - `would-block`: Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN) ⍝? ⍝? # Implementors note ⍝? Unlike in POSIX, in WASI the bind operation is async. This enables ⍝? interactive WASI hosts to inject permission prompts. Runtimes that ⍝? don't want to make use of this ability can simply call the native ⍝? `bind` as part of either `start-bind` or `finish-bind`. ⍝? ⍝? # References ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/udp", "[method]udp-socket.start-bind") start_bind(self, network: &Network, local_address: IpSocketAddress) -> Result<(), ErrorCode> { } #import("wasi:sockets/udp", "[method]udp-socket.finish-bind") finish_bind(self) -> Result<(), ErrorCode> { } ⍝? 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 ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/udp", "[method]udp-socket.stream") stream(self, remote_address: IpSocketAddress?) -> Result<(IncomingDatagramStream, OutgoingDatagramStream), ErrorCode> { } ⍝? 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 ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/udp", "[method]udp-socket.local-address") local_address(self) -> 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 ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/udp", "[method]udp-socket.remote-address") remote_address(self) -> Result { } ⍝? Whether this is a IPv4 or IPv6 socket. ⍝? ⍝? Equivalent to the SO_DOMAIN socket option. #import("wasi:sockets/udp", "[method]udp-socket.address-family") address_family(self) -> IpAddressFamily { } ⍝? 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. #import("wasi:sockets/udp", "[method]udp-socket.unicast-hop-limit") unicast_hop_limit(self) -> Result { } #import("wasi:sockets/udp", "[method]udp-socket.set-unicast-hop-limit") set_unicast_hop_limit(self, value: u8) -> Result<(), ErrorCode> { } ⍝? 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. #import("wasi:sockets/udp", "[method]udp-socket.receive-buffer-size") receive_buffer_size(self) -> Result { } #import("wasi:sockets/udp", "[method]udp-socket.set-receive-buffer-size") set_receive_buffer_size(self, value: u64) -> Result<(), ErrorCode> { } #import("wasi:sockets/udp", "[method]udp-socket.send-buffer-size") send_buffer_size(self) -> Result { } #import("wasi:sockets/udp", "[method]udp-socket.set-send-buffer-size") set_send_buffer_size(self, value: u64) -> Result<(), ErrorCode> { } ⍝? Create a `pollable` which will resolve once the socket is ready for I/O. ⍝? ⍝? Note: this function is here for WASI Preview2 only. ⍝? It's planned to be removed when `future` is natively supported in Preview3. #import("wasi:sockets/udp", "[method]udp-socket.subscribe") subscribe(self) -> Pollable { } } #import("wasi:sockets/udp", "incoming-datagram-stream") class IncomingDatagramStream { ⍝? Receive messages on the socket. ⍝? ⍝? This function attempts to receive up to `max-results` datagrams on the socket without blocking. ⍝? The returned list may contain fewer elements than requested, but never more. ⍝? ⍝? This function returns successfully with an empty list when either: ⍝? - `max-results` is 0, or: ⍝? - `max-results` is greater than 0, but no results are immediately available. ⍝? This function never returns `error(would-block)`. ⍝? ⍝? # Typical errors ⍝? - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) ⍝? - `connection-refused`: The connection was refused. (ECONNREFUSED) ⍝? ⍝? # References ⍝? - ⍝? - ⍝? - ⍝? - ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/udp", "[method]incoming-datagram-stream.receive") receive(self, max_results: u64) -> Result, ErrorCode> { } ⍝? Create a `pollable` which will resolve once the stream is ready to receive again. ⍝? ⍝? Note: this function is here for WASI Preview2 only. ⍝? It's planned to be removed when `future` is natively supported in Preview3. #import("wasi:sockets/udp", "[method]incoming-datagram-stream.subscribe") subscribe(self) -> Pollable { } } #import("wasi:sockets/udp", "outgoing-datagram-stream") class OutgoingDatagramStream { ⍝? Check readiness for sending. This function never blocks. ⍝? ⍝? Returns the number of datagrams permitted for the next call to `send`, ⍝? or an error. Calling `send` with more datagrams than this function has ⍝? permitted will trap. ⍝? ⍝? When this function returns ok(0), the `subscribe` pollable will ⍝? become ready when this function will report at least ok(1), or an ⍝? error. ⍝? ⍝? Never returns `would-block`. #import("wasi:sockets/udp", "[method]outgoing-datagram-stream.check-send") check_send(self) -> Result { } ⍝? Send messages on the socket. ⍝? ⍝? This function attempts to send all provided `datagrams` on the socket without blocking and ⍝? returns how many messages were actually sent (or queued for sending). This function never ⍝? returns `error(would-block)`. If none of the datagrams were able to be sent, `ok(0)` is returned. ⍝? ⍝? This function semantically behaves the same as iterating the `datagrams` list and sequentially ⍝? sending each individual datagram until either the end of the list has been reached or the first error occurred. ⍝? If at least one datagram has been sent successfully, this function never returns an error. ⍝? ⍝? If the input list is empty, the function returns `ok(0)`. ⍝? ⍝? Each call to `send` must be permitted by a preceding `check-send`. Implementations must trap if ⍝? either `check-send` was not called or `datagrams` contains more items than `check-send` permitted. ⍝? ⍝? # Typical errors ⍝? - `invalid-argument`: The `remote-address` has the wrong address family. (EAFNOSUPPORT) ⍝? - `invalid-argument`: The IP address in `remote-address` is set to INADDR_ANY (`0.0.0.0` / `::`). (EDESTADDRREQ, EADDRNOTAVAIL) ⍝? - `invalid-argument`: The port in `remote-address` is set to 0. (EDESTADDRREQ, EADDRNOTAVAIL) ⍝? - `invalid-argument`: The socket is in "connected" mode and `remote-address` is `some` value that does not match the address passed to `stream`. (EISCONN) ⍝? - `invalid-argument`: The socket is not "connected" and no value for `remote-address` was provided. (EDESTADDRREQ) ⍝? - `remote-unreachable`: The remote address is not reachable. (ECONNRESET, ENETRESET on Windows, EHOSTUNREACH, EHOSTDOWN, ENETUNREACH, ENETDOWN, ENONET) ⍝? - `connection-refused`: The connection was refused. (ECONNREFUSED) ⍝? - `datagram-too-large`: The datagram is too large. (EMSGSIZE) ⍝? ⍝? # References ⍝? - ⍝? - ⍝? - ⍝? - ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/udp", "[method]outgoing-datagram-stream.send") send(self, datagrams: Array) -> Result { } ⍝? Create a `pollable` which will resolve once the stream is ready to send again. ⍝? ⍝? Note: this function is here for WASI Preview2 only. ⍝? It's planned to be removed when `future` is natively supported in Preview3. #import("wasi:sockets/udp", "[method]outgoing-datagram-stream.subscribe") subscribe(self) -> Pollable { } } alias typus Network: Network { } alias typus ErrorCode: ErrorCode { } alias typus IpAddressFamily: IpAddressFamily { } alias typus UdpSocket: UdpSocket { } ⍝? 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: ⍝? - ⍝? - ⍝? - ⍝? - #import("wasi:sockets/udp-create-socket", "create-udp-socket") micro create_udp_socket(address_family: IpAddressFamily) -> Result { }