Landin library reference discuss source

hosted/blocking/network.ldn

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)