wire.bend
bend-kit-wire@0.4.6.1 · 138 lines · 0 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# 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.
19def 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.
25def 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
30def 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
35def 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
40def 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.
47def 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.
54def 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").
60def 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.
67def 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.
73def 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.
79def 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.
85def 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.
91def 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.
97def recv.words(sock: Socket, max: U32, ms: U32) ->
98 IO(Socket & Result<&1, &1, U32 & String, U32 & Array<U32>>):
99 import "./effs/wire.c"
100 import "./effs/wire.js"
101
102# len past the end of words fails with EINVAL.
103def send.words(sock: Socket, len: U32, words: Array<U32>) ->
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.
112def send.words.deadline(sock: Socket, len: U32, words: Array<U32>, 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.
118def recv_from.words(sock: Socket, max: U32, ms: U32) ->
119 IO(Socket & Result<&1, &1, U32 & String, String & U32 & (U32 & Array<U32>)>):
120 import "./effs/wire.c"
121 import "./effs/wire.js"
122
123# len past the end of words fails with EINVAL.
124def send_to.words(sock: Socket, host: String, port: U32, len: U32, words: Array<U32>) ->
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.
130def tls.recv.words(sock: Socket, max: U32, ms: U32) ->
131 IO(Socket & Result<&1, &1, U32 & String, U32 & Array<U32>>):
132 import "./effs/wire.c"
133 import "./effs/wire.js"
134
135def tls.send.words(sock: Socket, len: U32, words: Array<U32>) ->
136 IO(Socket & Result<&1, &1, U32 & String, Unit>):
137 import "./effs/wire.c"
138 import "./effs/wire.js"