Landin library reference source

core/io/network.ldn

1--  The network: internet addresses and their text, and a concept beside
2--  the others and outside `world` through which a program connects,
3--  listens, accepts and sends and receives over TCP and UDP.  A provider
4--  without a network, and a firmware image that never asks for one,
5--  carries none of it.  Resolving a name to an address is not here.
6
7import core/mem
8import core/text
9
10--- The text of an address could not be read as one: not four decimal
11--- parts from 0 to 255, not an IPv6 address's groups, a zone, or a port
12--- that is missing, above 65535 or not decimal.
13public invalid_address: atom
14--- Nothing listens at the address a connection was asked for: the system
15--- answered it at once, as a closed port is answered.
16public refused: atom
17--- No route leads to the address: its network or its host cannot be
18--- reached from here.
19public unreachable: atom
20--- Another socket already holds the address and port: a listener or a
21--- datagram socket bound there.
22public address_in_use: atom
23--- The address cannot be used here: no interface of this system holds it,
24--- or the system does not have its family at all, as one without IPv6
25--- does not.
26public address_unavailable: atom
27--- The time limit `set_timeouts` gave passed before anything arrived or
28--- could be sent, or a connection's handshake took longer than the system
29--- waits.  The socket is still usable.
30public timed_out: atom
31--- The connection's peer has gone: it closed or reset the connection, so
32--- what is sent cannot be delivered.  A write to such a connection is this
33--- atom on a hosted system, never the end of the process.
34public disconnected: atom
35
36--- An IPv4 address: four bytes, `127.0.0.1` in its text.
37public ipv4: atom
38--- An IPv6 address: sixteen bytes, `::1` in its text.
39public ipv6: atom
40--- What an address is: `ipv4` or `ipv6`.
41public address_family: type = ipv4 | ipv6
42
43--- An internet address and a port: TCP's and UDP's endpoint.  `family`
44--- says which; an IPv4 address is `octets`' first four bytes, in the
45--- order its text writes them, and the other twelve are zero; an IPv6
46--- address is all sixteen, in network order.  `port` is a number from 0
47--- to 65535, and 0 asks the system for a free one when a socket is bound.
48--- There is no zone: an IPv6 link-local address that needs one, `fe80::1%eth0`,
49--- cannot be written.  Make one with `ipv4_address`, `ipv6_address`,
50--- `loopback`, `unspecified` or `parse_address`, and compare two with
51--- `same_address`.
52public address: type = struct
53    family: address_family
54    octets: [16]u8
55    port: u16
56end address
57
58--- What arrived in one `receive_from`: how many bytes, and the address and
59--- port of the socket that sent them.
60public datagram: type = struct
61    count: usize
62    sender: address
63end datagram
64
65--- Stop receiving: what arrives later is thrown away.
66public receiving: atom
67--- Stop sending: the peer reads the end of the stream once what was sent
68--- before has arrived, while this side may still receive.
69public sending: atom
70--- Stop both receiving and sending.
71public both_directions: atom
72--- Which way of a connection `shutdown` stops: `receiving`, `sending` or
73--- `both_directions`.
74public direction: type = receiving | sending | both_directions
75
76--- The network: TCP connections and UDP datagram sockets over IPv4 and
77--- IPv6, each a `file` of the provider.  It is a reader and a writer of
78--- them as well, so a connection is read and written through `io.read`
79--- and `io.write`, a line reader and a buffered writer among them, whose
80--- failures are `io_failed` with the provider's detail; `send` and
81--- `receive` say more, `timed_out` and `disconnected`.  A connection's
82--- `read` and `receive` return zero once its peer has finished sending.
83---
84--- Every call waits until it can do what it was asked, as a blocking
85--- socket does: `accept` for a connection, `receive` for bytes, `send` for
86--- room, unless `set_timeouts` gave the socket a limit.  A listener takes
87--- connections whose handshake the system completes on its own, so one
88--- thread can connect to its own listener and then accept, and a
89--- connection's sends wait only once the system's buffers are full.
90--- Close every socket with `close_socket`; one dropped unclosed is open
91--- until the process ends, since there are no destructors.
92public network: type = concept (provider: type) is reader, writer
93    --- Connect to a listener at `to` and return the connection.  A port
94    --- nothing listens on is `refused`.
95    connect: (self: provider, to: address) -> (connected: file)
96             ! refused | unreachable | address_unavailable | timed_out
97             | io_failed
98    --- Listen for connections at `at`, port 0 for a free one, which
99    --- `local_address` then gives.  A listener may take an address its
100    --- earlier connections still hold while they close, never one another
101    --- socket listens at, `address_in_use`.
102    listen: (self: provider, at: address) -> (listener: file)
103            ! address_in_use | address_unavailable | no_access | io_failed
104    --- Take the next connection made to a listener, waiting for one.
105    accept: (self: provider, listener: file) -> (accepted: file)
106            ! timed_out | io_failed
107    --- Open a UDP socket bound to `at`, port 0 for a free one.
108    open_datagram: (self: provider, at: address) -> (socket: file)
109                   ! address_in_use | address_unavailable | no_access
110                   | io_failed
111    --- Send all of `bytes` over a connection, waiting while its buffers
112    --- are full.  A failure may follow a part of them.
113    send: (self: provider, connection: file, bytes: []u8) -> none
114          ! disconnected | timed_out | io_failed
115    --- Receive what arrives next on a connection into `into`, waiting for
116    --- something: its count, zero once the peer has finished sending.
117    receive: (self: provider, connection: file, into: []mut u8)
118             -> (count: usize) ! disconnected | timed_out | io_failed
119    --- Send `bytes` as one datagram to `to`.  One that cannot be sent
120    --- whole, longer than the system takes, is `io_failed`.
121    send_to: (self: provider, socket: file, bytes: []u8, to: address)
122             -> none ! unreachable | timed_out | io_failed
123    --- Receive the next datagram into `into`, waiting for one, and say
124    --- who sent it.  A datagram longer than `into` is cut to it, the rest
125    --- thrown away.
126    receive_from: (self: provider, socket: file, into: []mut u8)
127                  -> (received: datagram) ! timed_out | io_failed
128    --- Stop receiving, sending or both on a connection.
129    shutdown: (self: provider, connection: file, stopping: direction)
130              -> none ! io_failed
131    --- The address and port a socket is bound to.
132    local_address: (self: provider, socket: file) -> (at: address)
133                   ! io_failed
134    --- The address and port of a connection's peer.
135    peer_address: (self: provider, connection: file) -> (at: address)
136                  ! io_failed
137    --- Limit how long a receive or an accept, and a send, waits on
138    --- `socket`; a span of zero is no limit.
139    set_timeouts: (self: provider, socket: file, receive_limit: duration,
140                   send_limit: duration) -> none ! io_failed
141    --- Send a connection's small writes at once, `on`, rather than gather
142    --- them while an earlier one is unacknowledged.
143    set_no_delay: (self: provider, connection: file, on: bool) -> none
144                  ! io_failed
145    --- Close a socket, which is consumed even when this fails.
146    close_socket: (self: provider, sink socket: file) -> none ! io_failed
147end network
148
149--- An IPv4 address `a.b.c.d` and a port.
150public ipv4_address: (a: u8, b: u8, c: u8, d: u8, port: u16)
151                     -> (at: address) =
152    at = (family: ipv4, octets: zeroed, port: port)
153    at.octets[0] = a
154    at.octets[1] = b
155    at.octets[2] = c
156    at.octets[3] = d
157end ipv4_address
158
159--- An IPv6 address of eight 16-bit groups, in the order its text writes
160--- them, and a port.
161public ipv6_address: (groups: [8]u16, port: u16) -> (at: address) =
162    at = (family: ipv6, octets: zeroed, port: port)
163    for index in usize(0)..<8 do
164        at.octets[2 * index] = u8(groups[index] >> 8)
165        at.octets[2 * index + 1] = u8(groups[index] & 255)
166    end for
167end ipv6_address
168
169--- The loopback address of `family`, `127.0.0.1` or `::1`, and a port:
170--- this system, reached without a network.
171public loopback: (family: address_family, port: u16) -> (at: address) =
172    at = (family: family, octets: zeroed, port: port)
173    if family == ipv4 then
174        at.octets[0] = 127
175        at.octets[3] = 1
176    else
177        at.octets[15] = 1
178    end if
179end loopback
180
181--- The unspecified address of `family`, `0.0.0.0` or `::`, and a port: a
182--- socket bound to it takes what arrives at the port on every interface.
183public unspecified: (family: address_family, port: u16) -> (at: address) =
184    at = (family: family, octets: zeroed, port: port)
185end unspecified
186
187--- The address `at` with another port.
188public with_port: (at: address, port: u16) -> (result: address) =
189    result = at
190    result.port = port
191end with_port
192
193--- Whether two addresses are the same family, address and port.
194public same_address: (left: address, right: address) -> (same: bool) =
195    same = left.family == right.family and left.port == right.port
196    for index in usize(0)..<16 do
197        if left.octets[index] <> right.octets[index] then
198            same = false
199        end if
200    end for
201end same_address
202
203--- Whether an address is its family's unspecified address, whatever its
204--- port.
205public is_unspecified: (at: address) -> (yes: bool) =
206    yes = true
207    for index in usize(0)..<16 do
208        if at.octets[index] <> 0 then yes = false end if
209    end for
210end is_unspecified
211
212--  A hexadecimal digit's value, and whether the byte is one.
213hex_digit: (byte: u8) -> (value: u32, valid: bool) =
214    valid = true
215    value = 0
216    if byte >= 48 and byte <= 57 then
217        value = u32(byte - 48)
218    elsif byte >= 97 and byte <= 102 then
219        value = u32(byte - 87)
220    elsif byte >= 65 and byte <= 70 then
221        value = u32(byte - 55)
222    else
223        valid = false
224    end if
225end hex_digit
226
227--  An IPv4 address's four parts: decimal, from 0 to 255, with no leading
228--  zero, which some readers take for octal.
229ipv4_octets: (source: []u8) -> (octets: [4]u8) ! invalid_address =
230    octets = zeroed
231    mut part: usize = 0
232    mut value: u32 = 0
233    mut digits: usize = 0
234    for byte in source do
235        if byte == 46 then
236            fail invalid_address when digits == 0 or part == 3
237            octets[part] = u8(value)
238            inc part
239            value = 0
240            digits = 0
241        else
242            fail invalid_address when byte < 48 or byte > 57
243              or (digits > 0 and value == 0)
244            value = value * 10 + u32(byte - 48)
245            fail invalid_address when value > 255
246            inc digits
247        end if
248    end for
249    fail invalid_address when digits == 0 or part <> 3
250    octets[3] = u8(value)
251end ipv4_octets
252
253--  Where in SOURCE, from AT, the next `:` is, or its length.
254next_colon: (source: []u8, at: usize) -> (found: usize) =
255    found = at
256    while found < lenof source and source[found] <> 58 do
257        inc found
258    end while
259end next_colon
260
261--  An IPv6 address's sixteen bytes, from its groups: one to four
262--  hexadecimal digits each, at most one `::` standing for one or more
263--  zero groups, and an IPv4 address in the last 32 bits.
264ipv6_octets: (source: []u8) -> (octets: [16]u8) ! invalid_address =
265    octets = zeroed
266    mut groups: [8]u16 = zeroed
267    mut count: usize = 0
268    mut gap: usize = 9
269    mut at: usize = 0
270    length: usize = lenof source
271    fail invalid_address when length < 2
272    if source[0] == 58 then
273        fail invalid_address when source[1] <> 58
274        gap = 0
275        at = 2
276    end if
277    while at < length do
278        fail invalid_address when count == 8
279        ends: usize = next_colon(source, at)
280        piece: []u8 = source[at..<ends]
281        if ends == length and lenof piece > 4 then
282            --  An IPv4 address is the last 32 bits, and nothing follows it.
283            fail invalid_address when count > 6
284            four: [4]u8 = try ipv4_octets(piece)
285            groups[count] = (u16(four[0]) << 8) | u16(four[1])
286            groups[count + 1] = (u16(four[2]) << 8) | u16(four[3])
287            count += 2
288            at = length
289            break
290        end if
291        fail invalid_address when lenof piece == 0 or lenof piece > 4
292        mut value: u32 = 0
293        for byte in piece do
294            (value: digit, valid) := hex_digit(byte)
295            fail invalid_address when not valid
296            value = value * 16 + digit
297        end for
298        groups[count] = u16(value)
299        inc count
300        at = ends
301        break when at == length
302        --  A `:` follows the group: another group, or a second `:`.
303        inc at
304        fail invalid_address when at == length
305        if source[at] == 58 then
306            fail invalid_address when gap <> 9
307            gap = count
308            inc at
309        end if
310    end while
311    if gap == 9 then
312        fail invalid_address when count <> 8
313    else
314        fail invalid_address when count > 7
315        --  The groups after the gap move to the end.
316        moved: usize = count - gap
317        mut index: usize = 0
318        while index < moved do
319            groups[7 - index] = groups[count - 1 - index]
320            groups[count - 1 - index] = 0
321            inc index
322        end while
323    end if
324    for index in usize(0)..<8 do
325        octets[2 * index] = u8(groups[index] >> 8)
326        octets[2 * index + 1] = u8(groups[index] & 255)
327    end for
328end ipv6_octets
329
330--- Read an address without a port from its text, `127.0.0.1` or
331--- `2001:db8::1`, with port 0: four decimal parts from 0 to 255 without a
332--- leading zero, or an IPv6 address's groups of one to four hexadecimal
333--- digits in either case, at most one `::` and an IPv4 address in the last
334--- 32 bits.  Anything else, a zone such as `%eth0` and surrounding spaces
335--- among it, is `invalid_address`.
336public parse_ip: (source: []u8) -> (at: address) ! invalid_address =
337    at = unspecified(ipv4, 0)
338    if mem.find(source, 58) < lenof source then
339        at.family = ipv6
340        at.octets = try ipv6_octets(source)
341        return
342    end if
343    four: [4]u8 = try ipv4_octets(source)
344    for index in usize(0)..<4 do
345        at.octets[index] = four[index]
346    end for
347end parse_ip
348
349--- Read an address and a port from their text: `127.0.0.1:8080`, or an
350--- IPv6 address in brackets, `[::1]:8080`, as a URL writes them.  The
351--- port is decimal, from 0 to 65535; an address is read as `parse_ip`
352--- reads one.  Anything else is `invalid_address`.
353public parse_address: (source: []u8) -> (at: address) ! invalid_address =
354    length: usize = lenof source
355    mut colon: usize = length
356    mut index: usize = length
357    while index > 0 do
358        dec index
359        if source[index] == 58 then
360            colon = index
361            break
362        end if
363    end while
364    fail invalid_address when colon == length or colon + 1 == length
365    port: u16 = text.parse_u16(source[colon + 1..<length]) else (problem)
366        _ = problem
367        fail invalid_address
368    end
369    host: []u8 = source[0..<colon]
370    if lenof host > 0 and host[0] == 91 then
371        fail invalid_address when lenof host < 2
372          or host[lenof host - 1] <> 93
373        at = (family: ipv6, octets: try ipv6_octets(host[1..<lenof host - 1]),
374              port: port)
375        return
376    end if
377    four: [4]u8 = try ipv4_octets(host)
378    at = ipv4_address(four[0], four[1], four[2], four[3], port)
379end parse_address
380
381--  The IPv6 address's group INDEX.
382group: (at: address, index: usize) -> (value: u32) =
383    value = (u32(at.octets[2 * index]) << 8) | u32(at.octets[2 * index + 1])
384end group
385
386--  Append the IPv4 address in the four bytes from FIRST.
387dotted: (inout into: text.builder, at: address, first: usize)
388        -> none ! text.no_space =
389    for index in usize(0)..<4 do
390        if index > 0 then try text.append_byte(into, 46) end if
391        try text.append_u32(into, u32(at.octets[first + index]))
392    end for
393end dotted
394
395--  Append an IPv6 address as RFC 5952 writes it: lower-case groups
396--  without leading zeros, the longest run of two or more zero groups, the
397--  first of equals, written `::`, and an IPv4-mapped address's last 32
398--  bits in dotted decimal.
399grouped: (inout into: text.builder, at: address) -> none ! text.no_space =
400    mut mapped: bool = group(at, 5) == 65535
401    for index in usize(0)..<5 do
402        if group(at, index) <> 0 then mapped = false end if
403    end for
404    last: usize = if mapped then 6 else 8 end if
405    mut best_start: usize = 8
406    mut best_length: usize = 1
407    mut index: usize = 0
408    while index < last do
409        if group(at, index) == 0 then
410            mut ends: usize = index
411            while ends < last and group(at, ends) == 0 do
412                inc ends
413            end while
414            if ends - index > best_length then
415                best_start = index
416                best_length = ends - index
417            end if
418            index = ends
419        else
420            inc index
421        end if
422    end while
423    index = 0
424    while index < last do
425        if index == best_start then
426            try text.append(into, "::")
427            index += best_length
428        else
429            if index > 0 and index <> best_start + best_length then
430                try text.append_byte(into, 58)
431            end if
432            try text.append_hex_u32(into, group(at, index))
433            inc index
434        end if
435    end while
436    if mapped then
437        if best_start + best_length <> 6 then
438            try text.append_byte(into, 58)
439        end if
440        try dotted(into, at, 12)
441    end if
442end grouped
443
444--- Append an address's text without its port: `127.0.0.1`, or an IPv6
445--- address as RFC 5952 writes it, `::1` and `2001:db8::1`, lower case with
446--- the longest run of zero groups shortened and an IPv4-mapped address as
447--- `::ffff:192.0.2.1`.  `parse_ip` reads it back.  It is written whole or
448--- not at all.
449public append_ip: (inout into: text.builder, at: address)
450                  -> none ! text.no_space =
451    start: usize = text.length(into)
452    address_written(into, at, false) else (problem)
453        text.truncate(into, start)
454        fail problem
455    end
456end append_ip
457
458--- Append an address's text with its port: `127.0.0.1:8080`, or an IPv6
459--- address in brackets, `[::1]:8080`.  `parse_address` reads it back.  It
460--- is written whole or not at all.
461public append_address: (inout into: text.builder, at: address)
462                       -> none ! text.no_space =
463    start: usize = text.length(into)
464    address_written(into, at, true) else (problem)
465        text.truncate(into, start)
466        fail problem
467    end
468end append_address
469
470address_written: (inout into: text.builder, at: address, with_port: bool)
471                 -> none ! text.no_space =
472    bracketed: bool = with_port and at.family == ipv6
473    if bracketed then try text.append_byte(into, 91) end if
474    if at.family == ipv4 then
475        try dotted(into, at, 0)
476    else
477        try grouped(into, at)
478    end if
479    if bracketed then try text.append_byte(into, 93) end if
480    if with_port then
481        try text.append_byte(into, 58)
482        try text.append_u32(into, u32(at.port))
483    end if
484end address_written
485
486address_text: (self: ptr address, inout into: text.builder)
487              -> none ! text.no_space =
488    try append_address(into, self.val)
489end address_text
490
491address is text.formattable (append: address_text)
492
493--- Connect to a listener at `to` through `network` evidence.  Close the
494--- connection with `close_socket`.
495public connect: (provider: type is network, host: provider, to: address)
496                -> (connected: file)
497                ! refused | unreachable | address_unavailable | timed_out
498                | io_failed =
499    connected = try provider.connect(host, to)
500end connect
501
502--- Listen for connections at `at`, port 0 for a free one; close the
503--- listener with `close_socket`.
504public listen: (provider: type is network, host: provider, at: address)
505               -> (listener: file)
506               ! address_in_use | address_unavailable | no_access
507               | io_failed =
508    listener = try provider.listen(host, at)
509end listen
510
511--- Take the next connection made to `listener`, waiting for one unless a
512--- receive limit was set.
513public accept: (provider: type is network, host: provider, listener: file)
514               -> (accepted: file) ! timed_out | io_failed =
515    accepted = try provider.accept(host, listener)
516end accept
517
518--- Open a UDP socket bound to `at`, port 0 for a free one.
519public open_datagram: (provider: type is network, host: provider,
520                       at: address) -> (socket: file)
521                      ! address_in_use | address_unavailable | no_access
522                      | io_failed =
523    socket = try provider.open_datagram(host, at)
524end open_datagram
525
526--- Send all of `bytes` over a connection.  A connection whose peer has
527--- gone is `disconnected`.
528public send: (provider: type is network, host: provider, connection: file,
529              bytes: []u8) -> none ! disconnected | timed_out | io_failed =
530    try provider.send(host, connection, bytes)
531end send
532
533--- Receive what arrives next on a connection into `into`: its count, zero
534--- once the peer has finished sending.
535public receive: (provider: type is network, host: provider,
536                 connection: file, into: []mut u8)
537                -> (count: usize) ! disconnected | timed_out | io_failed =
538    count = try provider.receive(host, connection, into)
539end receive
540
541--- Send `bytes` as one datagram to `to`.
542public send_to: (provider: type is network, host: provider, socket: file,
543                 bytes: []u8, to: address)
544                -> none ! unreachable | timed_out | io_failed =
545    try provider.send_to(host, socket, bytes, to)
546end send_to
547
548--- Receive the next datagram into `into` and say who sent it.
549public receive_from: (provider: type is network, host: provider,
550                      socket: file, into: []mut u8)
551                     -> (received: datagram) ! timed_out | io_failed =
552    received = try provider.receive_from(host, socket, into)
553end receive_from
554
555--- Stop receiving, sending or both on a connection: `io.sending` lets
556--- the peer read the end of what was sent.
557public shutdown: (provider: type is network, host: provider,
558                  connection: file, stopping: direction)
559                 -> none ! io_failed =
560    try provider.shutdown(host, connection, stopping)
561end shutdown
562
563--- The address and port a socket is bound to: a listener's or a datagram
564--- socket's port when it was bound to port 0.
565public local_address: (provider: type is network, host: provider,
566                       socket: file) -> (at: address) ! io_failed =
567    at = try provider.local_address(host, socket)
568end local_address
569
570--- The address and port of a connection's peer.
571public peer_address: (provider: type is network, host: provider,
572                      connection: file) -> (at: address) ! io_failed =
573    at = try provider.peer_address(host, connection)
574end peer_address
575
576--- Limit how long a receive or an accept, and a send, waits on `socket`;
577--- zero is no limit.  A limit that passes is `timed_out`.
578public set_timeouts: (provider: type is network, host: provider,
579                      socket: file, receive_limit: duration,
580                      send_limit: duration) -> none ! io_failed =
581    try provider.set_timeouts(host, socket, receive_limit, send_limit)
582end set_timeouts
583
584--- Send a connection's small writes at once rather than gather them.
585public set_no_delay: (provider: type is network, host: provider,
586                      connection: file, on: bool) -> none ! io_failed =
587    try provider.set_no_delay(host, connection, on)
588end set_no_delay
589
590--- Close a socket.  Treat it as consumed even if closing reports
591--- `io_failed`.
592public close_socket: (provider: type is network, host: provider,
593                      sink socket: file) -> none ! io_failed =
594    try provider.close_socket(host, socket)
595end close_socket