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