Landin library reference source

hosted/io/network.ldn

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)