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