1-- The blocking provider's `net.network`: a socket is a descriptor of
2-- platform/posix's, a file of hosted/system's like any other, so `io.read`
3-- and `io.write` take a connection as they take a file, and every call
4-- that waits blocks the thread. Written once, over platform/posix, like
5-- the rest.
6
7import core/io
8import core/net
9import core/time
10import hosted/system
11import platform/posix
12
13-- How many connections may wait to be accepted, which each system caps at
14-- its own limit: the BSDs' SOMAXCONN.
15backlog: i32 = 128
16
17-- Whether an errno says the caller may not do what it asked.
18refused: (problem: posix.errno) -> (yes: bool) =
19 yes = problem == posix.eperm or problem == posix.eacces
20 or problem == posix.erofs
21end refused
22
23-- Close a socket no caller is told about.
24quietly_closed: (closing: posix.descriptor) -> none =
25 posix.close(closing) else none
26end quietly_closed
27
28-- The family of socket an address needs.
29family_of: (at: net.address) -> (family: posix.socket_family) =
30 family = if at.family == net.ipv4 then posix.internet
31 else posix.internet6 end if
32end family_of
33
34-- Whether an errno says the system does not have the address here: no
35-- interface holds it, or the family is missing altogether.
36unavailable: (problem: posix.errno) -> (yes: bool) =
37 yes = problem == posix.eaddrnotavail or problem == posix.eafnosupport
38 or problem == posix.epfnosupport
39end unavailable
40
41-- Whether an errno says the peer has gone: it closed or reset the
42-- connection, or the system gave up on it.
43gone: (problem: posix.errno) -> (yes: bool) =
44 yes = problem == posix.epipe or problem == posix.econnreset
45 or problem == posix.etimedout or problem == posix.enotconn
46end gone
47
48-- A socket bound to `at`, a listener when `listening`: an IPv6 socket
49-- takes IPv6 alone on every system, and a listener may take an address
50-- its earlier connections still hold.
51bound: (at: net.address, kind: posix.socket_kind, listening: bool)
52 -> (socket: posix.descriptor) ! posix.errno =
53 socket = try posix.socket(family_of(at), kind)
54 undo quietly_closed(socket)
55 if at.family == net.ipv6 then
56 try posix.set_ipv6_only(socket, true)
57 end if
58 if listening then
59 try posix.set_reuse_address(socket, true)
60 end if
61 try posix.bind(socket, posix.internet_address(at))
62 if listening then
63 try posix.listen(socket, backlog)
64 end if
65end bound
66
67-- The errno a binding failed with, as the concept's atoms.
68bind_failure: (self: host, problem: posix.errno)
69 -> none ! net.address_in_use | net.address_unavailable
70 | io.no_access | io.io_failed =
71 record(self, problem)
72 fail net.address_in_use when problem == posix.eaddrinuse
73 fail net.address_unavailable when unavailable(problem)
74 fail io.no_access when refused(problem)
75 fail io.io_failed
76end bind_failure
77
78blocking_listen: (self: host, at: net.address) -> (listener: io.file)
79 ! net.address_in_use | net.address_unavailable | io.no_access
80 | io.io_failed =
81 cleared(self)
82 made: posix.descriptor = bound(at, posix.stream, true) else (problem)
83 try bind_failure(self, problem)
84 fail io.io_failed
85 end
86 listener = system.socket_of(made)
87end blocking_listen
88
89blocking_open_datagram: (self: host, at: net.address) -> (socket: io.file)
90 ! net.address_in_use | net.address_unavailable
91 | io.no_access | io.io_failed =
92 cleared(self)
93 made: posix.descriptor = bound(at, posix.datagram, false) else (problem)
94 try bind_failure(self, problem)
95 fail io.io_failed
96 end
97 socket = system.socket_of(made)
98end blocking_open_datagram
99
100-- A connection a signal interrupted goes on without its caller: wait
101-- until it is writable, which it is once it has ended either way, and
102-- take its outcome from the socket.
103finished: (socket: posix.descriptor) -> none ! posix.errno =
104 mut entries: [1]posix.poll_entry =
105 [posix.watch(socket, posix.poll_output)]
106 loop do
107 _ = posix.poll(entries[0..<1], -1) else (problem)
108 continue when problem == posix.eintr
109 fail problem
110 end
111 break
112 end loop
113 try posix.pending_error(socket)
114end finished
115
116connected: (to: net.address) -> (socket: posix.descriptor) ! posix.errno =
117 socket = try posix.socket(family_of(to), posix.stream)
118 undo quietly_closed(socket)
119 posix.connect(socket, posix.internet_address(to)) else (problem)
120 fail problem when problem <> posix.eintr
121 try finished(socket)
122 end
123end connected
124
125blocking_connect: (self: host, to: net.address) -> (opened: io.file)
126 ! net.refused | net.unreachable | net.address_unavailable
127 | net.timed_out | io.io_failed =
128 cleared(self)
129 made: posix.descriptor = connected(to) else (problem)
130 record(self, problem)
131 fail net.refused when problem == posix.econnrefused
132 fail net.unreachable when problem == posix.enetunreach
133 or problem == posix.ehostunreach
134 fail net.address_unavailable when unavailable(problem)
135 fail net.timed_out when problem == posix.etimedout
136 fail io.io_failed
137 end
138 opened = system.socket_of(made)
139end blocking_connect
140
141blocking_accept: (self: host, listener: io.file) -> (accepted: io.file)
142 ! net.timed_out | io.io_failed =
143 cleared(self)
144 loop do
145 made: posix.descriptor =
146 posix.accept(system.descriptor_of(listener)) else (problem)
147 -- An interrupted accept took nothing, and a connection
148 -- its peer reset before it was taken is no connection.
149 continue when problem == posix.eintr
150 or problem == posix.econnaborted
151 record(self, problem)
152 fail net.timed_out when problem == posix.eagain
153 fail io.io_failed
154 end
155 accepted = system.socket_of(made)
156 return
157 end loop
158end blocking_accept
159
160blocking_send: (self: host, connection: io.file, bytes: []u8)
161 -> none ! net.disconnected | net.timed_out | io.io_failed =
162 cleared(self)
163 mut sent: usize = 0
164 while sent < lenof bytes do
165 -- A send interrupted before it sent anything is made again with
166 -- what is left; a short one goes on from where it stopped.
167 accepted: usize =
168 posix.send(system.descriptor_of(connection),
169 bytes[sent..<lenof bytes]) else (problem)
170 continue when problem == posix.eintr
171 record(self, problem)
172 fail net.disconnected when gone(problem)
173 fail net.timed_out when problem == posix.eagain
174 fail io.io_failed
175 end
176 fail io.io_failed when accepted == 0
177 or accepted > lenof bytes - sent
178 sent += accepted
179 end while
180end blocking_send
181
182blocking_receive: (self: host, connection: io.file, into: []mut u8)
183 -> (count: usize)
184 ! net.disconnected | net.timed_out | io.io_failed =
185 cleared(self)
186 count = 0
187 return when lenof into == 0
188 loop do
189 got: usize = posix.receive(system.descriptor_of(connection), into)
190 else (problem)
191 continue when problem == posix.eintr
192 record(self, problem)
193 fail net.disconnected when gone(problem)
194 fail net.timed_out when problem == posix.eagain
195 fail io.io_failed
196 end
197 fail io.io_failed when got > lenof into
198 count = got
199 return
200 end loop
201end blocking_receive
202
203blocking_send_to: (self: host, socket: io.file, bytes: []u8,
204 to: net.address) -> none
205 ! net.unreachable | net.timed_out | io.io_failed =
206 cleared(self)
207 loop do
208 accepted: usize =
209 posix.send_to(system.descriptor_of(socket), bytes,
210 posix.internet_address(to)) else (problem)
211 continue when problem == posix.eintr
212 record(self, problem)
213 fail net.unreachable when problem == posix.enetunreach
214 or problem == posix.ehostunreach
215 fail net.timed_out when problem == posix.eagain
216 fail io.io_failed
217 end
218 -- A datagram goes whole or not at all.
219 fail io.io_failed when accepted <> lenof bytes
220 return
221 end loop
222end blocking_send_to
223
224blocking_receive_from: (self: host, socket: io.file, into: []mut u8)
225 -> (taken: net.datagram) ! net.timed_out | io.io_failed =
226 cleared(self)
227 loop do
228 got: posix.received =
229 posix.receive_from(system.descriptor_of(socket), into)
230 else (problem)
231 continue when problem == posix.eintr
232 record(self, problem)
233 fail net.timed_out when problem == posix.eagain
234 fail io.io_failed
235 end
236 fail io.io_failed when got.count > lenof into
237 sender: net.address = posix.address_of(got.sender) else (problem)
238 record(self, problem)
239 fail io.io_failed
240 end
241 taken = (count: got.count, sender: sender)
242 return
243 end loop
244end blocking_receive_from
245
246blocking_shutdown: (self: host, connection: io.file,
247 stopping: net.direction) -> none ! io.io_failed =
248 cleared(self)
249 side: posix.shutdown_side = match stopping
250 net.receiving: posix.shut_reading
251 net.sending: posix.shut_writing
252 net.both_directions: posix.shut_both
253 end match
254 posix.shutdown(system.descriptor_of(connection), side) else (problem)
255 record(self, problem)
256 fail io.io_failed
257 end
258end blocking_shutdown
259
260-- The internet address of a socket's own end, or with `peer` of the
261-- other end.
262named: (socket: io.file, peer: bool) -> (at: net.address) ! posix.errno =
263 record: posix.socket_address =
264 if peer then try posix.peer_name(system.descriptor_of(socket))
265 else try posix.socket_name(system.descriptor_of(socket)) end if
266 at = try posix.address_of(record)
267end named
268
269blocking_local_address: (self: host, socket: io.file) -> (at: net.address)
270 ! io.io_failed =
271 cleared(self)
272 at = named(socket, false) else (problem)
273 record(self, problem)
274 fail io.io_failed
275 end
276end blocking_local_address
277
278blocking_peer_address: (self: host, connection: io.file)
279 -> (at: net.address) ! io.io_failed =
280 cleared(self)
281 at = named(connection, true) else (problem)
282 record(self, problem)
283 fail io.io_failed
284 end
285end blocking_peer_address
286
287-- A span as the system's seconds and nanoseconds.
288time_of: (span: time.duration) -> (reading: posix.time_value) =
289 whole: u64 = u64(span) / 1000000000
290 reading = (seconds: i64(whole),
291 nanoseconds: u32(u64(span) - whole * 1000000000))
292end time_of
293
294limited: (socket: io.file, receive_limit: time.duration,
295 send_limit: time.duration) -> none ! posix.errno =
296 try posix.set_receive_timeout(system.descriptor_of(socket),
297 time_of(receive_limit))
298 try posix.set_send_timeout(system.descriptor_of(socket),
299 time_of(send_limit))
300end limited
301
302blocking_set_timeouts: (self: host, socket: io.file,
303 receive_limit: time.duration, send_limit: time.duration)
304 -> none ! io.io_failed =
305 cleared(self)
306 limited(socket, receive_limit, send_limit) else (problem)
307 record(self, problem)
308 fail io.io_failed
309 end
310end blocking_set_timeouts
311
312blocking_set_no_delay: (self: host, connection: io.file, on: bool)
313 -> none ! io.io_failed =
314 cleared(self)
315 posix.set_no_delay(system.descriptor_of(connection), on) else (problem)
316 record(self, problem)
317 fail io.io_failed
318 end
319end blocking_set_no_delay
320
321-- A failed close may already have released the descriptor, as a file's
322-- may, so it is never retried.
323blocking_close_socket: (self: host, sink socket: io.file)
324 -> none ! io.io_failed =
325 cleared(self)
326 posix.close(system.descriptor_of(socket)) else (problem)
327 record(self, problem)
328 fail io.io_failed
329 end
330end blocking_close_socket
331
332host is net.network
333 (connect: blocking_connect, listen: blocking_listen,
334 accept: blocking_accept, open_datagram: blocking_open_datagram,
335 send: blocking_send, receive: blocking_receive, send_to: blocking_send_to,
336 receive_from: blocking_receive_from, shutdown: blocking_shutdown,
337 local_address: blocking_local_address,
338 peer_address: blocking_peer_address,
339 set_timeouts: blocking_set_timeouts, set_no_delay: blocking_set_no_delay,
340 close_socket: blocking_close_socket)