1 # Byte-exact TCP, UDP, and TLS sockets. Source: https://github.com/paymog/bend-kit/tree/main/wire 2 import Base 3 4 # Socket IO on octets: one Char per byte (0..255). Base's TCP/UDP effects 5 # decode UTF-8, which breaks byte counts and binary protocols. 6 # ms arguments are deadlines in milliseconds (0: none unless stated otherwise). 7 # A deadline miss is ETIMEDOUT. 8 # import ./wire.bend as Wire 9 10 # TCP connect to a numeric IPv4 or IPv6 address, with a deadline. 11 def connect(host: String, port: U32, ms: U32) -> 12 IO(Result<&1, &1, U32 & String, Socket>): 13 import "./effs/wire.c" 14 import "./effs/wire.js" 15 16 # One monotonic accept budget, ms: 1..2147483647. Every outcome returns the 17 # actual listener; timeout leaves it usable. Cooperative scheduling does not 18 # preempt arbitrary CPU work or blocking effects. 19 def accept.deadline(listener: Listener, ms: U32) -> 20 IO(Listener & Result<&1, &1, U32 & String, Socket>): 21 import "./effs/wire.c" 22 import "./effs/wire.js" 23 24 # Octet-String TCP and UDP effects are the slow path: one Char cell per byte. 25 def recv(sock: Socket, max: U32, ms: U32) -> 26 IO(Socket & Result<&1, &1, U32 & String, String>): 27 import "./effs/wire.c" 28 import "./effs/wire.js" 29 30 def send(sock: Socket, data: String) -> 31 IO(Socket & Result<&1, &1, U32 & String, Unit>): 32 import "./effs/wire.c" 33 import "./effs/wire.js" 34 35 def recv_from(sock: Socket, max: U32, ms: U32) -> 36 IO(Socket & Result<&1, &1, U32 & String, String & U32 & String>): 37 import "./effs/wire.c" 38 import "./effs/wire.js" 39 40 def send_to(sock: Socket, host: String, port: U32, data: String) -> 41 IO(Socket & Result<&1, &1, U32 & String, Unit>): 42 import "./effs/wire.c" 43 import "./effs/wire.js" 44 45 # TLS over a connected TCP socket (OpenSSL 3, loaded at run time; BEND_LIBSSL 46 # overrides its path). The certificate chain and host name are always checked. 47 def tls.connect(sock: Socket, host: String, ms: U32) -> 48 IO(Socket & Result<&1, &1, U32 & String, Unit>): 49 import "./effs/wire.c" 50 import "./effs/wire.js" 51 52 # Use a PEM certificate chain and its private key for mutual TLS. 53 # A load failure is EINVAL; a handshake or server verification failure is EPROTO. 54 def tls.connect.cert(sock: Socket, host: String, ms: U32, cert_path: String, key_path: String) -> 55 IO(Socket & Result<&1, &1, U32 & String, Unit>): 56 import "./effs/wire.c" 57 import "./effs/wire.js" 58 59 # protos is a comma-separated ALPN list (for example "h2,http/1.1"). 60 def tls.connect.alpn(sock: Socket, host: String, ms: U32, protos: String) -> 61 IO(Socket & Result<&1, &1, U32 & String, String>): 62 import "./effs/wire.c" 63 import "./effs/wire.js" 64 65 # Trust the PEM certificates in ca_path for this handshake, instead of the 66 # default verify paths. Verification stays on. A load failure is EINVAL. 67 def tls.connect.ca(sock: Socket, host: String, ms: U32, ca_path: String) -> 68 IO(Socket & Result<&1, &1, U32 & String, Unit>): 69 import "./effs/wire.c" 70 import "./effs/wire.js" 71 72 # tls.connect.ca with a comma-separated ALPN list. The result is the selected protocol. 73 def tls.connect.alpn.ca(sock: Socket, host: String, ms: U32, protos: String, ca_path: String) -> 74 IO(Socket & Result<&1, &1, U32 & String, String>): 75 import "./effs/wire.c" 76 import "./effs/wire.js" 77 78 # The String TLS effects are the slow path: one Char cell per octet. 79 def tls.send(sock: Socket, data: String) -> 80 IO(Socket & Result<&1, &1, U32 & String, Unit>): 81 import "./effs/wire.c" 82 import "./effs/wire.js" 83 84 # "" once the peer sends close_notify. A bare EOF is an error. 85 def tls.recv(sock: Socket, max: U32, ms: U32) -> 86 IO(Socket & Result<&1, &1, U32 & String, String>): 87 import "./effs/wire.c" 88 import "./effs/wire.js" 89 90 # Ends the TLS session and closes the socket. 91 def tls.close(sock: Socket) -> IO(Unit): 92 import "./effs/wire.c" 93 import "./effs/wire.js" 94 95 # The .words forms move (len, words): bytes packed four to a U32, laid out as 96 # bytes/bytes.bend packs them, so no String cell is made per byte. 97 def recv.words(sock: Socket, max: U32, ms: U32) -> 98 IO(Socket & Result<&1, &1, U32 & String, U32 & Array>): 99 import "./effs/wire.c" 100 import "./effs/wire.js" 101 102 # len past the end of words fails with EINVAL. 103 def send.words(sock: Socket, len: U32, words: Array) -> 104 IO(Socket & Result<&1, &1, U32 & String, Unit>): 105 import "./effs/wire.c" 106 import "./effs/wire.js" 107 108 # Total write budget, including packing; ms must be 1..2147483647. 109 # Returns socket, bytes accepted locally, and outcome on every path. Consumes 110 # words on every path. Timeout/disconnect may leave a sent prefix: close the 111 # returned socket, do not replay the whole buffer. Success is not peer receipt. 112 def send.words.deadline(sock: Socket, len: U32, words: Array, ms: U32) -> 113 IO(Socket & (U32 & Result<&1, &1, U32 & String, Unit>)): 114 import "./effs/wire.c" 115 import "./effs/wire.js" 116 117 # UDP keeps the sender address and port beside the packed datagram. 118 def recv_from.words(sock: Socket, max: U32, ms: U32) -> 119 IO(Socket & Result<&1, &1, U32 & String, String & U32 & (U32 & Array)>): 120 import "./effs/wire.c" 121 import "./effs/wire.js" 122 123 # len past the end of words fails with EINVAL. 124 def send_to.words(sock: Socket, host: String, port: U32, len: U32, words: Array) -> 125 IO(Socket & Result<&1, &1, U32 & String, Unit>): 126 import "./effs/wire.c" 127 import "./effs/wire.js" 128 129 # len 0 once the peer sends close_notify. 130 def tls.recv.words(sock: Socket, max: U32, ms: U32) -> 131 IO(Socket & Result<&1, &1, U32 & String, U32 & Array>): 132 import "./effs/wire.c" 133 import "./effs/wire.js" 134 135 def tls.send.words(sock: Socket, len: U32, words: Array) -> 136 IO(Socket & Result<&1, &1, U32 & String, Unit>): 137 import "./effs/wire.c" 138 import "./effs/wire.js"