core/net
Shared: available on every enabled target.
The network concept, whose connections are readers and writers, and internet addresses with their text.
The network is one more concept outside world, network, so an image that never connects carries none of it. It is a reader and a writer of its own sockets as well, so a connection is read and written through read and write, a line reader and a buffered writer among them, whose failures are io_failed; send and receive say more, timed_out when a limit has passed and disconnected when the peer has gone, and a receive or read of zero is the end of what the peer sends. listen takes an address and returns a listener, port 0 for a free port that local_address then gives, and may take an address its own closed connections still hold, never one another socket listens at, which is address_in_use; accept takes the next connection; connect makes one, refused where nothing listens and unreachable where no route leads; open_datagram binds a UDP socket, send_to sends a datagram whole and receive_from gives the next with its sender, cutting one longer than the buffer; shutdown stops receiving, sending or both_directions; peer_address names a connection's other end; set_timeouts limits how long a receive, an accept and a send wait, zero for no limit; set_no_delay turns Nagle's algorithm off; and close_socket closes a socket. An address the system does not have, or a family it lacks, is address_unavailable. Every call waits as a blocking socket does, so one thread can connect to its own listener and send before it accepts, since the system completes the handshake, but a receive waits for its peer. An address is a family, ipv4 or ipv6, sixteen bytes and a port: loopback, unspecified, ipv4_address, ipv6_address and with_port make one, same_address and is_unspecified compare, parse_ip reads 192.0.2.1 or 2001:db8::1 and parse_address 192.0.2.1:80 or [2001:db8::1]:80, refusing a leading zero, a zone and anything else with invalid_address, and append_ip and append_address write them back as RFC 5952 does, lower case, the longest run of zero groups shortened and an IPv4-mapped address in dotted decimal; an address is text.formattable. Nothing here resolves a name. network_over makes a fake.network over the caller's network_socket records, each made by socket_over from a buffer that is its receive buffer, and a network_space whose counters a test reads: a machine of loopback addresses on which a connection and a datagram arrive at once, whole and in order, a datagram no socket takes is lost and counted, and a call that would wait answers timed_out at once, since nothing in one thread could ever end the wait. A test may take IPv6 away with without_ipv6 and fail a call with fail_at. runtime/network-echo-memory runs the published echo over it, and runtime/network-addresses-firmware reads and writes addresses on Cortex-M0.
Items
- network concept
- address type
- address_family type
- datagram type
- direction type
- address_in_use atom
- address_unavailable atom
- both_directions atom
- disconnected atom
- invalid_address atom
- ipv4 atom
- ipv6 atom
- receiving atom
- refused atom
- sending atom
- timed_out atom
- unreachable atom
- accept function
- append_address function
- append_ip function
- close_socket function
- connect function
- ipv4_address function
- ipv6_address function
- is_unspecified function
- listen function
- local_address function
- loopback function
- open_datagram function
- parse_address function
- parse_ip function
- peer_address function
- receive function
- receive_from function
- same_address function
- send function
- send_to function
- set_no_delay function
- set_timeouts function
- shutdown function
- unspecified function
- with_port function
Executable example
This complete program is maintained in the repository runtime tests. View source.
-- Internet addresses read from their text and written back: IPv4's four -- decimal parts, IPv6's groups as RFC 5952 writes them, shortened and in -- lower case, an IPv4-mapped address in dotted decimal, and a port after -- either, an IPv6 address then in brackets. Text that is no address is -- `invalid_address`, a zone among it. It runs on every target, firmware -- included, since an address is plain data. import core/cmp import core/net import core/text -- Text read without a port and what it is written back as, or "" when it -- is no address. case: type = struct given: []u8 written: []u8 end case ips: [30]case = [(given: "127.0.0.1", written: "127.0.0.1"), (given: "0.0.0.0", written: "0.0.0.0"), (given: "255.255.255.255", written: "255.255.255.255"), (given: "192.0.2.10", written: "192.0.2.10"), (given: "256.1.1.1", written: ""), (given: "01.2.3.4", written: ""), (given: "1.2.3", written: ""), (given: "1.2.3.4.5", written: ""), (given: "1..2.3", written: ""), (given: " 1.2.3.4", written: ""), (given: "::", written: "::"), (given: "::1", written: "::1"), (given: "1::", written: "1::"), (given: "2001:DB8:0:0:0:0:0:1", written: "2001:db8::1"), (given: "2001:0db8:0000:0000:0000:ff00:0042:8329", written: "2001:db8::ff00:42:8329"), (given: "2001:db8::1:0:0:1", written: "2001:db8::1:0:0:1"), (given: "2001:0:0:1:0:0:0:1", written: "2001:0:0:1::1"), (given: "1:2:3:4:5:6:7::", written: "1:2:3:4:5:6:7:0"), (given: "::ffff:192.0.2.1", written: "::ffff:192.0.2.1"), (given: "::ffff:c000:201", written: "::ffff:192.0.2.1"), (given: "64:ff9b::192.0.2.33", written: "64:ff9b::c000:221"), (given: "1:2:3:4:5:6:7:8", written: "1:2:3:4:5:6:7:8"), (given: "fe80::1%eth0", written: ""), (given: "1::2::3", written: ""), (given: ":1", written: ""), (given: "1:", written: ""), (given: "12345::", written: ""), (given: "1:2:3:4:5:6:7:8:9", written: ""), (given: "::g", written: ""), (given: "", written: "")] -- Text read with a port and what it is written back as, or "". addresses: [11]case = [(given: "127.0.0.1:80", written: "127.0.0.1:80"), (given: "0.0.0.0:0", written: "0.0.0.0:0"), (given: "[::1]:8080", written: "[::1]:8080"), (given: "[2001:DB8::1]:65535", written: "[2001:db8::1]:65535"), (given: "[::ffff:10.0.0.1]:443", written: "[::ffff:10.0.0.1]:443"), (given: "[::1]", written: ""), (given: "1.2.3.4:65536", written: ""), (given: "1.2.3.4:", written: ""), (given: "::1:80", written: ""), (given: "[fe80::1%eth0]:80", written: ""), (given: "1.2.3.4:x", written: "")] -- Whether reading C's text, with or without a port, writes it back as it -- says, or refuses it when it says "". agrees: (item: case, port: bool) -> (yes: bool) ! text.no_space = mut room: [64]u8 = zeroed mut line := text.over(room[0..<64]) wanted: []u8 = item.written yes = lenof wanted == 0 at: net.address = read(item.given, port) else (problem) _ = problem return end yes = false return when lenof wanted == 0 if port then try net.append_address(line, at) else try net.append_ip(line, at) end if yes = cmp.compare(text.used(line), wanted) == cmp.equal return when not yes -- What is written reads back as the same address. again: net.address = read(text.used(line), port) else (problem) _ = problem yes = false return end yes = net.same_address(at, again) end agrees read: (source: []u8, port: bool) -> (at: net.address) ! net.invalid_address = if port then at = try net.parse_address(source) else at = try net.parse_ip(source) end if end read -- The constructors, the comparisons and the builder's rule that a piece -- is written whole or not at all. made: () -> (yes: bool) ! text.no_space | text.invalid_format = groups: [8]u16 = [0x2001, 0xdb8, 0, 0, 0, 0, 0, 1] v6: net.address = net.ipv6_address(groups, 443) v4: net.address = net.ipv4_address(192, 0, 2, 1, 80) mut room: [64]u8 = zeroed mut line := text.over(room[0..<64]) try text.format(line, "{} {}", [any(addr v4), any(addr v6)]) yes = cmp.compare(text.used(line), "192.0.2.1:80 [2001:db8::1]:443") == cmp.equal yes = yes and net.same_address(net.loopback(net.ipv4, 7), net.ipv4_address(127, 0, 0, 1, 7)) yes = yes and not net.same_address(net.loopback(net.ipv4, 7), net.loopback(net.ipv4, 8)) yes = yes and not net.same_address(net.unspecified(net.ipv4, 0), net.unspecified(net.ipv6, 0)) yes = yes and net.is_unspecified(net.unspecified(net.ipv6, 9)) and not net.is_unspecified(net.loopback(net.ipv6, 9)) moved: net.address = net.with_port(v4, 81) yes = yes and moved.port == 81 and moved.octets[3] == 1 -- Twelve bytes have room for `[::1]` but not for its port. mut small: [12]u8 = zeroed mut short := text.over(small[0..<12]) try text.append(short, "at ") mut full: bool = false net.append_address(short, v6) else (problem) full = problem == text.no_space end yes = yes and full and text.length(short) == 3 end made public main: () -> (code: i32) = code = 1 for index in usize(0)..<lenof ips do good: bool = agrees(ips[index], false) else (problem) _ = problem return end if not good then code = 10 + i32(index) return end if end for code = 2 for index in usize(0)..<lenof addresses do good: bool = agrees(addresses[index], true) else (problem) _ = problem return end if not good then code = 50 + i32(index) return end if end for code = 3 built: bool = made() else (problem) _ = problem return end return when not built code = 42 end main
network concept
public network: type = concept (provider: type) is io.reader, io.writer
--- Connect to a listener at `to` and return the connection. A port
--- nothing listens on is `refused`.
connect: (self: provider, to: address) -> (connected: io.file)
! refused | unreachable | address_unavailable | timed_out
| io.io_failed
--- Listen for connections at `at`, port 0 for a free one, which
--- `local_address` then gives. A listener may take an address its
--- earlier connections still hold while they close, never one another
--- socket listens at, `address_in_use`.
listen: (self: provider, at: address) -> (listener: io.file)
! address_in_use | address_unavailable | io.no_access | io.io_failed
--- Take the next connection made to a listener, waiting for one.
accept: (self: provider, listener: io.file) -> (accepted: io.file)
! timed_out | io.io_failed
--- Open a UDP socket bound to `at`, port 0 for a free one.
open_datagram: (self: provider, at: address) -> (socket: io.file)
! address_in_use | address_unavailable | io.no_access
| io.io_failed
--- Send all of `bytes` over a connection, waiting while its buffers
--- are full. A failure may follow a part of them.
send: (self: provider, connection: io.file, bytes: []u8) -> none
! disconnected | timed_out | io.io_failed
--- Receive what arrives next on a connection into `into`, waiting for
--- something: its count, zero once the peer has finished sending.
receive: (self: provider, connection: io.file, into: []mut u8)
-> (count: usize) ! disconnected | timed_out | io.io_failed
--- Send `bytes` as one datagram to `to`. One that cannot be sent
--- whole, longer than the system takes, is `io_failed`.
send_to: (self: provider, socket: io.file, bytes: []u8, to: address)
-> none ! unreachable | timed_out | io.io_failed
--- Receive the next datagram into `into`, waiting for one, and say
--- who sent it. A datagram longer than `into` is cut to it, the rest
--- thrown away.
receive_from: (self: provider, socket: io.file, into: []mut u8)
-> (received: datagram) ! timed_out | io.io_failed
--- Stop receiving, sending or both on a connection.
shutdown: (self: provider, connection: io.file, stopping: direction)
-> none ! io.io_failed
--- The address and port a socket is bound to.
local_address: (self: provider, socket: io.file) -> (at: address)
! io.io_failed
--- The address and port of a connection's peer.
peer_address: (self: provider, connection: io.file) -> (at: address)
! io.io_failed
--- Limit how long a receive or an accept, and a send, waits on
--- `socket`; a span of zero is no limit.
set_timeouts: (self: provider, socket: io.file, receive_limit: time.duration,
send_limit: time.duration) -> none ! io.io_failed
--- Send a connection's small writes at once, `on`, rather than gather
--- them while an earlier one is unacknowledged.
set_no_delay: (self: provider, connection: io.file, on: bool) -> none
! io.io_failed
--- Close a socket, which is consumed even when this fails.
close_socket: (self: provider, sink socket: io.file) -> none ! io.io_failed
end networkThe network: TCP connections and UDP datagram sockets over IPv4 and IPv6, each a file of the provider. It is a reader and a writer of them as well, so a connection is read and written through io.read and io.write, a line reader and a buffered writer among them, whose failures are io_failed with the provider's detail; send and receive say more, timed_out and disconnected. A connection's read and receive return zero once its peer has finished sending.
Every call waits until it can do what it was asked, as a blocking socket does: accept for a connection, receive for bytes, send for room, unless set_timeouts gave the socket a limit. A listener takes connections whose handshake the system completes on its own, so one thread can connect to its own listener and then accept, and a connection's sends wait only once the system's buffers are full. Close every socket with close_socket; one dropped unclosed is open until the process ends, since there are no destructors.
address type
public address: type = struct
family: address_family
octets: [16]u8
port: u16
end addressAn internet address and a port: TCP's and UDP's endpoint. family says which; an IPv4 address is octets' first four bytes, in the order its text writes them, and the other twelve are zero; an IPv6 address is all sixteen, in network order. port is a number from 0 to 65535, and 0 asks the system for a free one when a socket is bound. There is no zone: an IPv6 link-local address that needs one, fe80::1%eth0, cannot be written. Make one with ipv4_address, ipv6_address, loopback, unspecified or parse_address, and compare two with same_address.
address_family type
public address_family: type = ipv4 | ipv6What an address is: ipv4 or ipv6.
datagram type
public datagram: type = struct
count: usize
sender: address
end datagramWhat arrived in one receive_from: how many bytes, and the address and port of the socket that sent them.
direction type
public direction: type = receiving | sending | both_directionsWhich way of a connection shutdown stops: receiving, sending or both_directions.
address_in_use atom
public address_in_use: atomAnother socket already holds the address and port: a listener or a datagram socket bound there.
both_directions atom
public both_directions: atomStop both receiving and sending.
disconnected atom
public disconnected: atomThe connection's peer has gone: it closed or reset the connection, so what is sent cannot be delivered. A write to such a connection is this atom on a hosted system, never the end of the process.
invalid_address atom
public invalid_address: atomThe text of an address could not be read as one: not four decimal parts from 0 to 255, not an IPv6 address's groups, a zone, or a port that is missing, above 65535 or not decimal.
ipv4 atom
public ipv4: atomAn IPv4 address: four bytes, 127.0.0.1 in its text.
ipv6 atom
public ipv6: atomAn IPv6 address: sixteen bytes, ::1 in its text.
receiving atom
public receiving: atomStop receiving: what arrives later is thrown away.
refused atom
public refused: atomNothing listens at the address a connection was asked for: the system answered it at once, as a closed port is answered.
sending atom
public sending: atomStop sending: the peer reads the end of the stream once what was sent before has arrived, while this side may still receive.
timed_out atom
public timed_out: atomThe time limit set_timeouts gave passed before anything arrived or could be sent, or a connection's handshake took longer than the system waits. The socket is still usable.
unreachable atom
public unreachable: atomNo route leads to the address: its network or its host cannot be reached from here.
accept function
public accept: (provider: type is network, host: provider, listener: io.file)
-> (accepted: io.file) ! timed_out | io.io_failedTake the next connection made to listener, waiting for one unless a receive limit was set.
append_address function
public append_address: (inout into: text.builder, at: address)
-> none ! text.no_spaceAppend an address's text with its port: 127.0.0.1:8080, or an IPv6 address in brackets, [::1]:8080. parse_address reads it back. It is written whole or not at all.
append_ip function
public append_ip: (inout into: text.builder, at: address)
-> none ! text.no_spaceAppend an address's text without its port: 127.0.0.1, or an IPv6 address as RFC 5952 writes it, ::1 and 2001:db8::1, lower case with the longest run of zero groups shortened and an IPv4-mapped address as ::ffff:192.0.2.1. parse_ip reads it back. It is written whole or not at all.
close_socket function
public close_socket: (provider: type is network, host: provider,
sink socket: io.file) -> none ! io.io_failedClose a socket. Treat it as consumed even if closing reports io_failed.
connect function
public connect: (provider: type is network, host: provider, to: address)
-> (connected: io.file)
! refused | unreachable | address_unavailable | timed_out
| io.io_failedConnect to a listener at to through network evidence. Close the connection with close_socket.
ipv4_address function
public ipv4_address: (a: u8, b: u8, c: u8, d: u8, port: u16)
-> (at: address)An IPv4 address a.b.c.d and a port.
ipv6_address function
public ipv6_address: (groups: [8]u16, port: u16) -> (at: address)An IPv6 address of eight 16-bit groups, in the order its text writes them, and a port.
is_unspecified function
public is_unspecified: (at: address) -> (yes: bool)Whether an address is its family's unspecified address, whatever its port.
listen function
public listen: (provider: type is network, host: provider, at: address)
-> (listener: io.file)
! address_in_use | address_unavailable | io.no_access
| io.io_failedListen for connections at at, port 0 for a free one; close the listener with close_socket.
local_address function
public local_address: (provider: type is network, host: provider,
socket: io.file) -> (at: address) ! io.io_failedThe address and port a socket is bound to: a listener's or a datagram socket's port when it was bound to port 0.
loopback function
public loopback: (family: address_family, port: u16) -> (at: address)The loopback address of family, 127.0.0.1 or ::1, and a port: this system, reached without a network.
open_datagram function
public open_datagram: (provider: type is network, host: provider,
at: address) -> (socket: io.file)
! address_in_use | address_unavailable | io.no_access
| io.io_failedOpen a UDP socket bound to at, port 0 for a free one.
parse_address function
public parse_address: (source: []u8) -> (at: address) ! invalid_addressRead an address and a port from their text: 127.0.0.1:8080, or an IPv6 address in brackets, [::1]:8080, as a URL writes them. The port is decimal, from 0 to 65535; an address is read as parse_ip reads one. Anything else is invalid_address.
parse_ip function
public parse_ip: (source: []u8) -> (at: address) ! invalid_addressRead an address without a port from its text, 127.0.0.1 or 2001:db8::1, with port 0: four decimal parts from 0 to 255 without a leading zero, or an IPv6 address's groups of one to four hexadecimal digits in either case, at most one :: and an IPv4 address in the last 32 bits. Anything else, a zone such as %eth0 and surrounding spaces among it, is invalid_address.
peer_address function
public peer_address: (provider: type is network, host: provider,
connection: io.file) -> (at: address) ! io.io_failedThe address and port of a connection's peer.
receive function
public receive: (provider: type is network, host: provider,
connection: io.file, into: []mut u8)
-> (count: usize) ! disconnected | timed_out | io.io_failedReceive what arrives next on a connection into into: its count, zero once the peer has finished sending.
receive_from function
public receive_from: (provider: type is network, host: provider,
socket: io.file, into: []mut u8)
-> (received: datagram) ! timed_out | io.io_failedReceive the next datagram into into and say who sent it.
same_address function
public same_address: (left: address, right: address) -> (same: bool)Whether two addresses are the same family, address and port.
send function
public send: (provider: type is network, host: provider, connection: io.file,
bytes: []u8) -> none ! disconnected | timed_out | io.io_failedSend all of bytes over a connection. A connection whose peer has gone is disconnected.
send_to function
public send_to: (provider: type is network, host: provider, socket: io.file,
bytes: []u8, to: address)
-> none ! unreachable | timed_out | io.io_failedSend bytes as one datagram to to.
set_no_delay function
public set_no_delay: (provider: type is network, host: provider,
connection: io.file, on: bool) -> none ! io.io_failedSend a connection's small writes at once rather than gather them.
set_timeouts function
public set_timeouts: (provider: type is network, host: provider,
socket: io.file, receive_limit: time.duration,
send_limit: time.duration) -> none ! io.io_failedLimit how long a receive or an accept, and a send, waits on socket; zero is no limit. A limit that passes is timed_out.
shutdown function
public shutdown: (provider: type is network, host: provider,
connection: io.file, stopping: direction)
-> none ! io.io_failedStop receiving, sending or both on a connection: io.sending lets the peer read the end of what was sent.
unspecified function
public unspecified: (family: address_family, port: u16) -> (at: address)The unspecified address of family, 0.0.0.0 or ::, and a port: a socket bound to it takes what arrives at the port on every interface.
with_port function
public with_port: (at: address, port: u16) -> (result: address)The address at with another port.