# Byte-exact TCP, UDP, and TLS sockets. Source: https://github.com/paymog/bend-kit/tree/main/wire import Base # Socket IO on octets: one Char per byte (0..255). Base's TCP/UDP effects # decode UTF-8, which breaks byte counts and binary protocols. # ms arguments are deadlines in milliseconds (0: none unless stated otherwise). # A deadline miss is ETIMEDOUT. # import ./wire.bend as Wire # TCP connect to a numeric IPv4 or IPv6 address, with a deadline. def connect(host: String, port: U32, ms: U32) -> IO(Result<&1, &1, U32 & String, Socket>): import "./effs/wire.c" import "./effs/wire.js" # One monotonic accept budget, ms: 1..2147483647. Every outcome returns the # actual listener; timeout leaves it usable. Cooperative scheduling does not # preempt arbitrary CPU work or blocking effects. def accept.deadline(listener: Listener, ms: U32) -> IO(Listener & Result<&1, &1, U32 & String, Socket>): import "./effs/wire.c" import "./effs/wire.js" # Octet-String TCP and UDP effects are the slow path: one Char cell per byte. def recv(sock: Socket, max: U32, ms: U32) -> IO(Socket & Result<&1, &1, U32 & String, String>): import "./effs/wire.c" import "./effs/wire.js" def send(sock: Socket, data: String) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): import "./effs/wire.c" import "./effs/wire.js" def recv_from(sock: Socket, max: U32, ms: U32) -> IO(Socket & Result<&1, &1, U32 & String, String & U32 & String>): import "./effs/wire.c" import "./effs/wire.js" def send_to(sock: Socket, host: String, port: U32, data: String) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): import "./effs/wire.c" import "./effs/wire.js" # TLS over a connected TCP socket (OpenSSL 3, loaded at run time; BEND_LIBSSL # overrides its path). The certificate chain and host name are always checked. def tls.connect(sock: Socket, host: String, ms: U32) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): import "./effs/wire.c" import "./effs/wire.js" # Use a PEM certificate chain and its private key for mutual TLS. # A load failure is EINVAL; a handshake or server verification failure is EPROTO. def tls.connect.cert(sock: Socket, host: String, ms: U32, cert_path: String, key_path: String) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): import "./effs/wire.c" import "./effs/wire.js" # protos is a comma-separated ALPN list (for example "h2,http/1.1"). def tls.connect.alpn(sock: Socket, host: String, ms: U32, protos: String) -> IO(Socket & Result<&1, &1, U32 & String, String>): import "./effs/wire.c" import "./effs/wire.js" # Trust the PEM certificates in ca_path for this handshake, instead of the # default verify paths. Verification stays on. A load failure is EINVAL. def tls.connect.ca(sock: Socket, host: String, ms: U32, ca_path: String) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): import "./effs/wire.c" import "./effs/wire.js" # tls.connect.ca with a comma-separated ALPN list. The result is the selected protocol. def tls.connect.alpn.ca(sock: Socket, host: String, ms: U32, protos: String, ca_path: String) -> IO(Socket & Result<&1, &1, U32 & String, String>): import "./effs/wire.c" import "./effs/wire.js" # The String TLS effects are the slow path: one Char cell per octet. def tls.send(sock: Socket, data: String) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): import "./effs/wire.c" import "./effs/wire.js" # "" once the peer sends close_notify. A bare EOF is an error. def tls.recv(sock: Socket, max: U32, ms: U32) -> IO(Socket & Result<&1, &1, U32 & String, String>): import "./effs/wire.c" import "./effs/wire.js" # Ends the TLS session and closes the socket. def tls.close(sock: Socket) -> IO(Unit): import "./effs/wire.c" import "./effs/wire.js" # The .words forms move (len, words): bytes packed four to a U32, laid out as # bytes/bytes.bend packs them, so no String cell is made per byte. def recv.words(sock: Socket, max: U32, ms: U32) -> IO(Socket & Result<&1, &1, U32 & String, U32 & Array>): import "./effs/wire.c" import "./effs/wire.js" # len past the end of words fails with EINVAL. def send.words(sock: Socket, len: U32, words: Array) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): import "./effs/wire.c" import "./effs/wire.js" # Total write budget, including packing; ms must be 1..2147483647. # Returns socket, bytes accepted locally, and outcome on every path. Consumes # words on every path. Timeout/disconnect may leave a sent prefix: close the # returned socket, do not replay the whole buffer. Success is not peer receipt. def send.words.deadline(sock: Socket, len: U32, words: Array, ms: U32) -> IO(Socket & (U32 & Result<&1, &1, U32 & String, Unit>)): import "./effs/wire.c" import "./effs/wire.js" # UDP keeps the sender address and port beside the packed datagram. def recv_from.words(sock: Socket, max: U32, ms: U32) -> IO(Socket & Result<&1, &1, U32 & String, String & U32 & (U32 & Array)>): import "./effs/wire.c" import "./effs/wire.js" # len past the end of words fails with EINVAL. def send_to.words(sock: Socket, host: String, port: U32, len: U32, words: Array) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): import "./effs/wire.c" import "./effs/wire.js" # len 0 once the peer sends close_notify. def tls.recv.words(sock: Socket, max: U32, ms: U32) -> IO(Socket & Result<&1, &1, U32 & String, U32 & Array>): import "./effs/wire.c" import "./effs/wire.js" def tls.send.words(sock: Socket, len: U32, words: Array) -> IO(Socket & Result<&1, &1, U32 & String, Unit>): import "./effs/wire.c" import "./effs/wire.js"