Step 40 of 56
The chip-neutral network stack
One GNAT.Sockets subset, several possible NICs, and a routing table that fails traffic over when a link drops — so networking code does not name the hardware carrying it.
The contract a NIC must satisfy
Net_Devices.Device is the chip-neutral interface. Each interface
chip provides one concrete implementation; the facade keeps a registry and
dispatches, so a board can carry more than one NIC, of different types,
in a single binary.
type IPv4_Address is array (0 .. 3) of Octet;
type MAC_Address is array (0 .. 5) of Octet;
subtype Port_Number is Interfaces.Unsigned_16;
type Interface_Id is range 0 .. Max_Interfaces - 1;
type Status is (OK, Not_Open, Closed_By_Peer, Timed_Out, Refused, No_Space, Error);
type Transport is (TCP, UDP);
type Device is limited interface;
This is the offloaded-stack model: the device provides TCP and UDP sockets directly, addressed by an index the device maps to its own per-socket state — the W5500 has eight hardware sockets, for instance. A raw-MAC chip cannot satisfy this interface as it stands; it needs a software TCP/IP stack implementing it, which is exactly what the Wi-Fi side does.
Writing ordinary Ada networking code
GNAT.Sockets here is a bare-metal subset of the standard package.
Code written against the desktop API — Create_Socket,
Bind, Listen, Accept,
Connect, Send, Receive,
Close, and the stream over a socket — compiles and runs
unchanged within that subset. That is why DNS_Client
and NTP_Client are the same source on a desktop and on the board.
Routing and failover
Net_Routes is a small IPv4 table for boards with more than one
interface. Selection is: among routes whose destination matches
and whose interface is up, take the longest prefix, then the
lowest metric.
procedure Add_Route (...);
procedure Set_Default (Iface : Interface_Id; Metric : Natural := 100);
procedure Configure (Is_Up : Up_Query); -- liveness is INJECTED
So a wired interface at metric 10 and a cellular one at metric 100 give you automatic failover: traffic prefers wired and falls back to cellular only when wired is down. Liveness is injected rather than wired to a particular stack, which keeps the table pure logic — and host-testable against a mock up-state.
Register interfaces with Add_Interface (the first is the
default). An unpinned socket follows the table per destination: TCP at
Connect_Socket, UDP per datagram at the
To-form of Send_Socket. Set_Interface pins
a socket to one interface, fail-closed.
The concurrency contract
A Socket_Type value has exactly one owning
task. Nothing serialises concurrent operations on the same socket, and
the routing forms may re-home it mid-call. Different sockets may be
driven from different tasks — slot claim/release is a protected object and
per-socket state belongs to that socket alone — but sharing one socket
between tasks is your bug to avoid, not the library's to prevent.
Register interfaces and configure routes during bring-up, before tasks start using sockets. Requires the embedded or full profile.