Landin library reference discuss source

core/net/net.ldn

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