A TCP/IP stack in C11 for targets that have no heap. It is independent of the operating system rather than written against the absence of one: it asks for no allocation, no thread, no timer and no socket, so it runs on bare hardware and under an OS alike. Where there is one, the OS is the caller - it hands the stack the memory, reads it the clock, gives it the frames, and calls it on its own schedule.
The library allocates nothing. Every unit that holds state is handed one uint8_t array by the
caller, sized by a macro that unit publishes, and reads and writes nothing outside it. There is no
file-scope mutable anywhere in src/, so two instances of a unit share not one byte and the whole
footprint of a build is the arrays the application declared.
Under FreeRTOS a borrow is a static array or one task's buffer, and the millisecond clock is whatever
the port reads xTaskGetTickCount() into. SafeRTOS is the same integration with its certification
constraints already met, and for the reason the design has anyway: execution is sequential and memory
is mapped. A call runs to completion and returns - there is no callback, no re-entry and nothing that
blocks, so BUSY is the answer to a call that cannot run now and it comes back on a later tick. Every
byte a unit will ever touch is at a known offset in the array the caller declared, which the unit
publishes the size of, so the memory map is a compile-time fact rather than a run-time one. A borrow
belongs to whoever calls into it: keep it to one task or put your own lock around it, since the
library takes none of its own and holds nothing outside the array it was handed.
Both of those together bound the behaviour, and they bound it on each axis an analysis asks about. In
time: the clock is an argument, so a deadline is arithmetic on a number the caller passed rather than
a race against one the library read. In address: every byte a unit touches stands at a published
offset in the array the caller declared. In resource: that array is the resource, and there is no
second one to account for. In instruction: each namespace table is const and its initializer is
visible to every translation unit, so TcpOut.build(work) compiles to a direct call - no callback, no
pointer to branch through, nothing to re-enter. For any given input the outcome is predetermined, and
the branch space is enumerable rather than merely testable: a tool can walk an entry's operand block,
drive every branch out of it, and fuzz the wire paths against the same borrows. That is not a safety
case, but it is the part of one that a design usually has to be rewritten to produce.
test/fuzz/ is the second half of that sentence taken up. Dispatch.input is one entry over one
frame that allocates nothing, so the harness is the borrows, a frame and the four numbers the caller
reads - no driver, no socket, no allocator interposition. Every borrow is cleared and rebound at the
head of each input and the clock is a function of the frame's place in it, so a crash file is the
whole reproduction. Every line and every branch of src/ is measured besides, and the few that
cannot be reached each carry the argument for why, written beside them.
#include "src/idemip.h"
static _Alignas(IDEMIP_ALIGN) uint8_t netif_w[IDEMIP_NETIF_BORROW];
static _Alignas(IDEMIP_ALIGN) uint8_t arp_w[IDEMIP_ARP_BORROW];src/idemip.h is the only header an application includes. It adds no type, macro or call of its
own; it names every unit's header, what the caller allocates, and the order things run in. Read its
opening comment for the worked example.
| CMake | 3.20 or newer |
| C compiler | any C11 compiler; the warning set and -Os apply to GCC and Clang |
| Python 3 | drives test/harness.py, which generates the Unity runners |
| Ruby | runs Unity's generate_test_runner.rb |
| Network | once, so FetchContent can clone Unity v2.6.1 |
Python and Ruby are needed only to build the tests.
tools/build.py drives every tree here. A first build starts with check, which names each
prerequisite, what that part is needed for, and whether this host has it:
python tools/build.py check
python tools/build.py all
all builds the library from src/, then the examples/ tree, then the suites, and runs ctest
over them. Each tree also has its own entry point:
python tools/build.py lib the static library, plus the idemip_sizes tool
python tools/build.py examples the examples/ tree
python tools/build.py tests the suites, then ctest over them
python tools/build.py configure write the CMake cache and stop
Every subcommand takes --dry-run, which prints the cmake and ctest invocations without
running them, and -D, which forwards a define to the configure step:
python tools/build.py lib --dry-run
python tools/build.py lib -D IDEMIP_ENABLE_TCP=OFF -D IDEMIP_ENABLE_UDP=OFF
The build directory lives outside the repository, and .clangd expects it at ../build. That is
the default tools/build.py configures, and --build-dir moves it. By hand the same build is:
cmake -S . -B ../build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build ../build
ctest --test-dir ../build --output-on-failure
Release builds take -Os and link-time optimization, which is what folds the namespace
indirection. IDEMIP_LTO=OFF turns the latter off.
A capability is a set of translation units. CMake selects them, so a capability that is off reaches the compiler as no file at all rather than as a file that compiles to nothing.
cmake -S . -B ../build -G Ninja -DIDEMIP_ENABLE_TCP=OFF -DIDEMIP_ENABLE_UDP=OFF
The feature tree is declared child to parent in CMakeLists.txt, over the helpers in
cmake/FeatureTree.cmake. ETHERNET is the root; each child names the parent it sits on, and a
build that turns a parent off is refused with the reason:
| capability | sits on | because |
|---|---|---|
IDEMIP_ENABLE_ETHERNET |
root | |
IDEMIP_ENABLE_IPV4 |
ETHERNET | arp.h resolves to a 48-bit Ethernet address (RFC 826) |
IDEMIP_ENABLE_IPV6 |
ETHERNET | the link layer here is Ethernet II (RFC 2464) |
IDEMIP_ENABLE_TCP |
IPV4 or IPV6 | the checksum covers a pseudo-header |
IDEMIP_ENABLE_UDP |
IPV4 or IPV6 | the checksum covers a pseudo-header |
Each value reaches the library and every consumer as a compile definition, because
idemip_config.h sizes tables with them: IDEMIP_TIMEOUTS is arithmetic over the five.
Where one unit serves both IP versions, the version arm is its own translation unit and the build
swaps in a stub for a version it left out. src/ip/pseudo.c holds the transport pseudo-header
branch once; pseudo_v4.c and pseudo_v6.c are the two forms, pseudo_v4_off.c and
pseudo_v6_off.c answer a version this build does not carry.
CMakeLists.txt the library, the capability tree, the tools and the test subdirectory
cmake/ FeatureTree.cmake: add_root_option, add_child_option, feature_sources,
feature_source_swap, enforce_mutually_exclusive_with_fallback
src/ the library, included as "src/..." with the repo root as the one include path
test/ one CTest target per suite, plus harness.py and unwired.py
tools/ build.py, the entry point for every build; idemip_sizes.c, the footprint tool;
dev_env/, the checkers the repository runs over its own source
Link. Ethernet II framing (RFC 894), the IEEE 802.1Q C-Tag, the MII management registers (IEEE 802.3 Clause 22), the PHY driver contract, the DMA descriptor rings and their pin protocol, the interface table, and a loopback interface (RFC 1122 sec 3.2.1.3, RFC 4291 sec 2.5.3).
IPv4. The header (RFC 791 sec 3.1), addresses (RFC 1122 sec 3.2.1.3), the routing table (RFC 1122 sec 3.3.1), forwarding (RFC 1812 sec 5.2.1.2), fragmentation (RFC 791 sec 3.2) and reassembly by the RFC 815 hole list, Path MTU Discovery (RFC 1191), ARP (RFC 826) with its translation table, Address Conflict Detection (RFC 5227), Link-Local addressing (RFC 3927), ICMP (RFC 792) under the RFC 1122 sec 3.2.2 origination rules, IGMPv2 (RFC 2236), and DHCP (RFC 2131).
IPv6. The header and its extension chain (RFC 8200 sec 3), addresses (RFC 4291 sec 2) with the zones of RFC 4007, default address selection (RFC 6724), forwarding, fragmentation and reassembly (RFC 8200 sec 4.5), Path MTU Discovery (RFC 8201), IPv6 over Ethernet (RFC 2464), ICMPv6 (RFC 4443), Neighbor Discovery (RFC 4861) with Duplicate Address Detection (RFC 4862 sec 5.4), SLAAC (RFC 4862 sec 5.3), the RDNSS option (RFC 8106), MLD (RFC 2710), and DHCPv6 (RFC 8415).
Transport. TCP (RFC 9293) — SEGMENT ARRIVES of sec 3.10.7, the send path with its timers and windows, the transmission control blocks and queues, and RFC 6528 initial sequence numbers. UDP (RFC 768), UDP-Lite (RFC 3828), raw bindings on an IP protocol number, and a stub resolver (RFC 1035).
Core. The receive path from one frame to the pcb that owns it, the scheduler, the deadline list, and the management counters — RFC 1213 for IPv4, ICMPv4, TCP, UDP and the interface, RFC 2465 and RFC 2466 for IPv6 and ICMPv6.
tools/idemip_sizes.c links the library and prints every borrow and their sum, as the compiler
computed them over idemip_config.h. It is run, never remembered. At the default counts
(IDEMIP_NETIF_COUNT 2, IDEMIP_TCP_PCBS 5, IDEMIP_UDP_PCBS 8, IDEMIP_IP6_ADDRESSES 4):
= IDEMIP_SHARED_BORROW 20328
= IDEMIP_PER_NETIF_BORROW 4976 x 2
IDEMIP_TOTAL_BORROW 30280
The driver's frame buffers are the driver's and are in no borrow, so that total does not count them.
Every count is an #ifndef in idemip_config.h, so a definition reaching the compiler overrides
it. These are preprocessor definitions rather than CMake options:
cmake -S . -B ../build -G Ninja -DCMAKE_C_FLAGS=-DIDEMIP_TCP_PCBS=2
which takes the total above to 29296. Each borrow is a formula over the counts, never a number, and
each unit's .c carries static_assert(sizeof(<Unit>Ctx) <= IDEMIP_<UNIT>_CTX_BYTES), so a
context that outgrows its budget fails the build naming the macro to raise.
One CTest target per suite. A suite is a directory holding exactly one .c of file-scope
void test_<name>(void) cases; harness.py turns it into unity_runner.c through Unity's own
generator, and says so when the generator would walk past a case.
test/CMakeLists.txt maps each suite to the capabilities it drives. A suite whose capabilities are
off is skipped loudly, because silently dropping it would leave a passing run that tested less than
it looks like.
python test/harness.py suites every suite, its cases, and the capabilities it needs
python test/harness.py cases <dir> what Unity registered, and what it walked past
python test/harness.py deps --strict dependencies a suite binds that no case asserts on
python test/unwired.py . identifiers a header defines that no library .c names
The last two are audit tools, not pass/fail gates. Both exist because a requirement encoded in a header and never wired to a caller is invisible to a green test run.
src/ is C11 with no heap, no auto, and no array with automatic storage duration. The only
system headers it takes are <assert.h> for static_assert, <stddef.h>, <stdint.h> for the
fixed widths, and <string.h> for memcpy, memset and memcmp; there is no <stdlib.h> and
nothing is parsed by the library. Names are flat idemip_snake_case in one namespace. Sizes are
powers of two so an index is a shift, because hardware divide is not on every target in the list.
test/ and tools/ are exempt and read as plain host C.
Warnings are attached to an INTERFACE target so they reach the suites as well as the library:
-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion -Wcast-align -Wundef, with
-Wstrict-prototypes -Wmissing-prototypes on the library.
Licensed AGPL-3.0-or-later. // various commercial contracts available It will always be free to use under the AGPL. Educators: If you would like an exception to use this in your classrooms or research projects, please feel free to email dstroy0 (Douglas Quigg) dquigg123@gmail.com from your _.edu or _.org faculty email address, I would be happy to grant you an exception on a case-by-case basis. Your exception governs your use, specifically the accreditation requirement of underlying systems in any research/presentation materials. Academic exemptions can lead to viable market products, in which case this license shifts to a royalty ladder, based arbitrarily off of the amount of goodwill you've shown and how well you've adhered to crediting students and other faculty involved in the project, a portion of the royalties go directly to your institution at a minimum and straight to your department if their rules allow for it. See CONTRIBUTING.md and SECURITY.md.