1-- The event provider's `net.network`: every socket it makes is
2-- non-blocking, and a call that would wait parks its fibre on the loop
3-- until the socket is ready, its limit passes or the socket is shut or
4-- closed through the provider, which fails the call `io_failed` (D449).
5-- A socket is a file of hosted/system's like any other, so `io.read` and
6-- `io.write` take a connection as they take a file.
7
8import core/io
9import core/net
10import core/time
11import hosted/system
12import platform/posix
13
14-- How many connections may wait to be accepted, which each system caps at
15-- its own limit: the BSDs' SOMAXCONN.
16backlog: i32 = 128
17
18-- Whether an errno says the caller may not do what it asked.
19refused: (problem: posix.errno) -> (yes: bool) =
20 yes = problem == posix.eperm or problem == posix.eacces
21 or problem == posix.erofs
22end refused
23
24-- Close a socket no caller is told about.
25quietly_closed: (closing: posix.descriptor) -> none =
26 posix.close(closing) else none
27end quietly_closed
28
29-- The family of socket an address needs.
30family_of: (at: net.address) -> (family: posix.socket_family) =
31 family = if at.family == net.ipv4 then posix.internet
32 else posix.internet6 end if
33end family_of
34
35-- Whether an errno says the system does not have the address here.
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.
42gone: (problem: posix.errno) -> (yes: bool) =
43 yes = problem == posix.epipe or problem == posix.econnreset
44 or problem == posix.etimedout or problem == posix.enotconn
45end gone
46
47-- Whether an errno says the call would have waited.
48would_wait: (problem: posix.errno) -> (yes: bool) =
49 yes = problem == posix.eagain
50end would_wait
51
52-- A non-blocking socket bound to `at`, a listener when `listening`, as
53-- hosted/blocking binds one.
54bound: (at: net.address, kind: posix.socket_kind, listening: bool)
55 -> (socket: posix.descriptor) ! posix.errno =
56 socket = try posix.socket(family_of(at), kind)
57 undo quietly_closed(socket)
58 try posix.set_non_blocking(socket, true)
59 if at.family == net.ipv6 then
60 try posix.set_ipv6_only(socket, true)
61 end if
62 if listening then
63 try posix.set_reuse_address(socket, true)
64 end if
65 try posix.bind(socket, posix.internet_address(at))
66 if listening then
67 try posix.listen(socket, backlog)
68 end if
69end bound
70
71bind_failure: (self: host, problem: posix.errno)
72 -> none ! net.address_in_use | net.address_unavailable
73 | io.no_access | io.io_failed =
74 record(self, problem)
75 fail net.address_in_use when problem == posix.eaddrinuse
76 fail net.address_unavailable when unavailable(problem)
77 fail io.no_access when refused(problem)
78 fail io.io_failed
79end bind_failure
80
81events_listen: (self: host, at: net.address) -> (listener: io.file)
82 ! net.address_in_use | net.address_unavailable | io.no_access
83 | io.io_failed =
84 cleared(self)
85 made: posix.descriptor = bound(at, posix.stream, true) else (problem)
86 try bind_failure(self, problem)
87 fail io.io_failed
88 end
89 listener = system.socket_of(made)
90end events_listen
91
92events_open_datagram: (self: host, at: net.address) -> (socket: io.file)
93 ! net.address_in_use | net.address_unavailable
94 | io.no_access | io.io_failed =
95 cleared(self)
96 made: posix.descriptor = bound(at, posix.datagram, false) else (problem)
97 try bind_failure(self, problem)
98 fail io.io_failed
99 end
100 socket = system.socket_of(made)
101end events_open_datagram
102
103-- The deadline of a wait on `socket` limited by what `set_timeouts`
104-- gave it, zero for none. A limit the system does not give back, as
105-- QEMU's user emulation does not, is none.
106limit_of: (socket: posix.descriptor, sending: bool) -> (deadline: u64) =
107 deadline = 0
108 mut span: posix.time_value = (seconds: 0, nanoseconds: 0)
109 if sending then
110 span = posix.send_timeout(socket) else (problem)
111 _ = problem
112 return
113 end
114 else
115 span = posix.receive_timeout(socket) else (problem)
116 _ = problem
117 return
118 end
119 end if
120 return when span.seconds <= 0 and span.nanoseconds == 0
121 deadline = deadline_after(time.duration(u64(span.seconds) * second
122 + u64(span.nanoseconds)))
123end limit_of
124
125-- Wait for `socket` in one direction as a fibre does, until `deadline`:
126-- `timed_out` when it passed, `io_failed` when the socket was shut or
127-- closed through the provider.
128socket_ready: (self: host, socket: posix.descriptor, output: bool,
129 deadline: u64) -> none ! net.timed_out | io.io_failed =
130 answer: u8 = await(self, socket, output, deadline) else (problem)
131 record(self, problem)
132 fail io.io_failed
133 end
134 fail net.timed_out when answer == expired
135 fail io.io_failed when answer == failed
136end socket_ready
137
138-- A connection begun on a non-blocking socket, waited for as a fibre
139-- does, and its outcome taken from the socket. Nobody else holds the
140-- socket yet, so nothing closes it while the fibre waits.
141connected: (self: host, to: net.address)
142 -> (socket: posix.descriptor) ! posix.errno =
143 socket = try posix.socket(family_of(to), posix.stream)
144 undo quietly_closed(socket)
145 try posix.set_non_blocking(socket, true)
146 posix.connect(socket, posix.internet_address(to)) else (problem)
147 fail problem when problem <> posix.einprogress
148 and problem <> posix.eintr
149 answer: u8 = try await(self, socket, true, 0)
150 fail posix.ebadf when answer <> ready
151 try posix.pending_error(socket)
152 end
153end connected
154
155events_connect: (self: host, to: net.address) -> (opened: io.file)
156 ! net.refused | net.unreachable | net.address_unavailable
157 | net.timed_out | io.io_failed =
158 cleared(self)
159 made: posix.descriptor = connected(self, to) else (problem)
160 record(self, problem)
161 fail net.refused when problem == posix.econnrefused
162 fail net.unreachable when problem == posix.enetunreach
163 or problem == posix.ehostunreach
164 fail net.address_unavailable when unavailable(problem)
165 fail net.timed_out when problem == posix.etimedout
166 fail io.io_failed
167 end
168 opened = system.socket_of(made)
169end events_connect
170
171events_accept: (self: host, listener: io.file) -> (accepted: io.file)
172 ! net.timed_out | io.io_failed =
173 cleared(self)
174 socket: posix.descriptor = system.descriptor_of(listener)
175 deadline: u64 = limit_of(socket, false)
176 loop do
177 made: posix.descriptor =
178 posix.accept_non_blocking(socket) else (problem)
179 -- An interrupted accept took nothing, and a connection
180 -- its peer reset before it was taken is no connection.
181 continue when problem == posix.eintr
182 or problem == posix.econnaborted
183 if would_wait(problem) then
184 try socket_ready(self, socket, false, deadline)
185 continue
186 end if
187 record(self, problem)
188 fail io.io_failed
189 end
190 accepted = system.socket_of(made)
191 return
192 end loop
193end events_accept
194
195events_send: (self: host, connection: io.file, bytes: []u8)
196 -> none ! net.disconnected | net.timed_out | io.io_failed =
197 cleared(self)
198 socket: posix.descriptor = system.descriptor_of(connection)
199 mut sent: usize = 0
200 while sent < lenof bytes do
201 accepted: usize =
202 posix.send(socket, bytes[sent..<lenof bytes]) else (problem)
203 continue when problem == posix.eintr
204 if would_wait(problem) then
205 try socket_ready(self, socket, true,
206 limit_of(socket, true))
207 continue
208 end if
209 record(self, problem)
210 fail net.disconnected when gone(problem)
211 fail io.io_failed
212 end
213 fail io.io_failed when accepted == 0
214 or accepted > lenof bytes - sent
215 sent += accepted
216 end while
217end events_send
218
219events_receive: (self: host, connection: io.file, into: []mut u8)
220 -> (count: usize)
221 ! net.disconnected | net.timed_out | io.io_failed =
222 cleared(self)
223 count = 0
224 return when lenof into == 0
225 socket: posix.descriptor = system.descriptor_of(connection)
226 deadline: u64 = limit_of(socket, false)
227 loop do
228 got: usize = posix.receive(socket, into) else (problem)
229 continue when problem == posix.eintr
230 if would_wait(problem) then
231 try socket_ready(self, socket, false, deadline)
232 continue
233 end if
234 record(self, problem)
235 fail net.disconnected when gone(problem)
236 fail io.io_failed
237 end
238 fail io.io_failed when got > lenof into
239 count = got
240 return
241 end loop
242end events_receive
243
244events_send_to: (self: host, socket: io.file, bytes: []u8,
245 to: net.address) -> none
246 ! net.unreachable | net.timed_out | io.io_failed =
247 cleared(self)
248 opened: posix.descriptor = system.descriptor_of(socket)
249 deadline: u64 = limit_of(opened, true)
250 loop do
251 accepted: usize =
252 posix.send_to(opened, bytes, posix.internet_address(to))
253 else (problem)
254 continue when problem == posix.eintr
255 if would_wait(problem) then
256 try socket_ready(self, opened, true, deadline)
257 continue
258 end if
259 record(self, problem)
260 fail net.unreachable when problem == posix.enetunreach
261 or problem == posix.ehostunreach
262 fail io.io_failed
263 end
264 -- A datagram goes whole or not at all.
265 fail io.io_failed when accepted <> lenof bytes
266 return
267 end loop
268end events_send_to
269
270events_receive_from: (self: host, socket: io.file, into: []mut u8)
271 -> (taken: net.datagram) ! net.timed_out | io.io_failed =
272 cleared(self)
273 opened: posix.descriptor = system.descriptor_of(socket)
274 deadline: u64 = limit_of(opened, false)
275 loop do
276 got: posix.received = posix.receive_from(opened, into)
277 else (problem)
278 continue when problem == posix.eintr
279 if would_wait(problem) then
280 try socket_ready(self, opened, false, deadline)
281 continue
282 end if
283 record(self, problem)
284 fail io.io_failed
285 end
286 fail io.io_failed when got.count > lenof into
287 sender: net.address = posix.address_of(got.sender) else (problem)
288 record(self, problem)
289 fail io.io_failed
290 end
291 taken = (count: got.count, sender: sender)
292 return
293 end loop
294end events_receive_from
295
296-- Shutting a side wakes every fibre waiting on that side, failing its
297-- call (D449).
298events_shutdown: (self: host, connection: io.file,
299 stopping: net.direction) -> none ! io.io_failed =
300 cleared(self)
301 socket: posix.descriptor = system.descriptor_of(connection)
302 side: posix.shutdown_side = match stopping
303 net.receiving: posix.shut_reading
304 net.sending: posix.shut_writing
305 net.both_directions: posix.shut_both
306 end match
307 posix.shutdown(socket, side) else (problem)
308 record(self, problem)
309 fail io.io_failed
310 end
311 answer_descriptor(self.state, i32(socket),
312 stopping <> net.sending, stopping <> net.receiving,
313 failed)
314end events_shutdown
315
316named: (socket: io.file, peer: bool) -> (at: net.address) ! posix.errno =
317 record: posix.socket_address =
318 if peer then try posix.peer_name(system.descriptor_of(socket))
319 else try posix.socket_name(system.descriptor_of(socket)) end if
320 at = try posix.address_of(record)
321end named
322
323events_local_address: (self: host, socket: io.file) -> (at: net.address)
324 ! io.io_failed =
325 cleared(self)
326 at = named(socket, false) else (problem)
327 record(self, problem)
328 fail io.io_failed
329 end
330end events_local_address
331
332events_peer_address: (self: host, connection: io.file)
333 -> (at: net.address) ! io.io_failed =
334 cleared(self)
335 at = named(connection, true) else (problem)
336 record(self, problem)
337 fail io.io_failed
338 end
339end events_peer_address
340
341-- A span as the system's seconds and nanoseconds.
342time_of: (span: time.duration) -> (reading: posix.time_value) =
343 whole: u64 = u64(span) / second
344 reading = (seconds: i64(whole),
345 nanoseconds: u32(u64(span) - whole * second))
346end time_of
347
348-- The limits are kept on the socket, as the system's own time limits,
349-- which a non-blocking socket never waits on: each wait reads them back.
350limited: (socket: io.file, receive_limit: time.duration,
351 send_limit: time.duration) -> none ! posix.errno =
352 try posix.set_receive_timeout(system.descriptor_of(socket),
353 time_of(receive_limit))
354 try posix.set_send_timeout(system.descriptor_of(socket),
355 time_of(send_limit))
356end limited
357
358events_set_timeouts: (self: host, socket: io.file,
359 receive_limit: time.duration, send_limit: time.duration)
360 -> none ! io.io_failed =
361 cleared(self)
362 limited(socket, receive_limit, send_limit) else (problem)
363 record(self, problem)
364 fail io.io_failed
365 end
366end events_set_timeouts
367
368events_set_no_delay: (self: host, connection: io.file, on: bool)
369 -> none ! io.io_failed =
370 cleared(self)
371 posix.set_no_delay(system.descriptor_of(connection), on) else (problem)
372 record(self, problem)
373 fail io.io_failed
374 end
375end events_set_no_delay
376
377-- Closing wakes every fibre waiting on the socket first (D449). A
378-- failed close may already have released the descriptor, so it is never
379-- retried.
380events_close_socket: (self: host, sink socket: io.file)
381 -> none ! io.io_failed =
382 cleared(self)
383 closing: posix.descriptor = system.descriptor_of(socket)
384 forget(self, closing)
385 posix.close(closing) else (problem)
386 record(self, problem)
387 fail io.io_failed
388 end
389end events_close_socket
390
391host is net.network
392 (connect: events_connect, listen: events_listen,
393 accept: events_accept, open_datagram: events_open_datagram,
394 send: events_send, receive: events_receive, send_to: events_send_to,
395 receive_from: events_receive_from, shutdown: events_shutdown,
396 local_address: events_local_address,
397 peer_address: events_peer_address,
398 set_timeouts: events_set_timeouts, set_no_delay: events_set_no_delay,
399 close_socket: events_close_socket)