wire.bend

bend-kit-wire@0.4.8.0 · 151 lines · 23 definitions

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