@since(version = 0.2.0)
interface tcp {
    @since(version = 0.2.0)
    use wasi:io/streams@0.2.6.{input-stream, output-stream};
    @since(version = 0.2.0)
    use wasi:io/poll@0.2.6.{pollable};
    @since(version = 0.2.0)
    use wasi:clocks/monotonic-clock@0.2.6.{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 <https://github.com/WebAssembly/wasi-sockets/blob/main/TcpSocketOperationalSemantics.md>
    /// 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)
        /// - `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
        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/bind.html>
        /// - <https://man7.org/linux/man-pages/man2/bind.2.html>
        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind>
        /// - <https://man.freebsd.org/cgi/man.cgi?query=bind&sektion=2&format=html>
        @since(version = 0.2.0)
        start-bind: func(network: borrow<network>, local-address: ip-socket-address) -> result<_, error-code>;
        @since(version = 0.2.0)
        finish-bind: func() -> result<_, error-code>;

        /// Connect to a remote endpoint.
        ///
        /// On success:
        /// - the socket is transitioned into the `connected` 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
        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/connect.html>
        /// - <https://man7.org/linux/man-pages/man2/connect.2.html>
        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect>
        /// - <https://man.freebsd.org/cgi/man.cgi?connect>
        @since(version = 0.2.0)
        start-connect: func(network: borrow<network>, remote-address: ip-socket-address) -> result<_, error-code>;
        @since(version = 0.2.0)
        finish-connect: func() -> result<tuple<input-stream, output-stream>, error-code>;

        /// Start listening for new connections.
        ///
        /// Transitions the socket into the `listening` state.
        ///
        /// Unlike POSIX, the socket must already be explicitly bound.
        ///
        /// # Typical errors
        /// - `invalid-state`:             The socket is not bound to any local address. (EDESTADDRREQ)
        /// - `invalid-state`:             The socket is already in the `connected` state. (EISCONN, EINVAL on BSD)
        /// - `invalid-state`:             The socket is already in the `listening` state.
        /// - `address-in-use`:            Tried to perform an implicit bind, but there were no ephemeral ports available. (EADDRINUSE)
        /// - `not-in-progress`:           A listen operation is not in progress.
        /// - `would-block`:               Can't finish the operation, it is still in progress. (EWOULDBLOCK, EAGAIN)
        ///
        /// # Implementors note
        /// Unlike in POSIX, in WASI the listen operation is async. This enables
        /// interactive WASI hosts to inject permission prompts. Runtimes that
        /// don't want to make use of this ability can simply call the native
        /// `listen` as part of either `start-listen` or `finish-listen`.
        ///
        /// # References
        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/listen.html>
        /// - <https://man7.org/linux/man-pages/man2/listen.2.html>
        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen>
        /// - <https://man.freebsd.org/cgi/man.cgi?query=listen&sektion=2>
        @since(version = 0.2.0)
        start-listen: func() -> result<_, error-code>;
        @since(version = 0.2.0)
        finish-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:
        /// - `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
        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/accept.html>
        /// - <https://man7.org/linux/man-pages/man2/accept.2.html>
        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept>
        /// - <https://man.freebsd.org/cgi/man.cgi?query=accept&sektion=2>
        @since(version = 0.2.0)
        accept: func() -> result<tuple<tcp-socket, input-stream, output-stream>, 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
        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getsockname.html>
        /// - <https://man7.org/linux/man-pages/man2/getsockname.2.html>
        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getsockname>
        /// - <https://man.freebsd.org/cgi/man.cgi?getsockname>
        @since(version = 0.2.0)
        local-address: func() -> result<ip-socket-address, error-code>;

        /// Get the remote address.
        ///
        /// # Typical errors
        /// - `invalid-state`: The socket is not connected to a remote address. (ENOTCONN)
        ///
        /// # References
        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/getpeername.html>
        /// - <https://man7.org/linux/man-pages/man2/getpeername.2.html>
        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-getpeername>
        /// - <https://man.freebsd.org/cgi/man.cgi?query=getpeername&sektion=2&n=1>
        @since(version = 0.2.0)
        remote-address: func() -> result<ip-socket-address, error-code>;

        /// 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<bool, error-code>;
        @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<duration, error-code>;
        @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<duration, error-code>;
        @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<u32, error-code>;
        @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<u8, error-code>;
        @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<u64, error-code>;
        @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<u64, error-code>;
        @since(version = 0.2.0)
        set-send-buffer-size: func(value: u64) -> result<_, error-code>;

        /// 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 <https://github.com/WebAssembly/wasi-sockets/blob/main/TcpSocketOperationalSemantics.md#pollable-readiness>
        /// for more information.
        ///
        /// Note: this function is here for WASI 0.2 only.
        /// It's planned to be removed when `future` is natively supported in Preview3.
        @since(version = 0.2.0)
        subscribe: func() -> 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 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
        /// - <https://pubs.opengroup.org/onlinepubs/9699919799/functions/shutdown.html>
        /// - <https://man7.org/linux/man-pages/man2/shutdown.2.html>
        /// - <https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-shutdown>
        /// - <https://man.freebsd.org/cgi/man.cgi?query=shutdown&sektion=2>
        @since(version = 0.2.0)
        shutdown: func(shutdown-type: shutdown-type) -> result<_, error-code>;
    }
}
