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