Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 31 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,37 @@ bank 2; banks 6-7 reserved for the P-384 overlay experiment.

Switching backend = a different cfg + different `src/net/<backend>/*.o`.

**`src/net_abi.inc` is documentation, not an enforced interface.** Nothing
includes it; the surface TLS/HTTP/boot actually import is `net_init`,
`net_dhcp`, `net_poll`, `net_print_ip`, `net_dns_resolve`,
`net_tcp_connect/close/send`, `net_send_len`, `net_recv_byte`,
`net_banner_str`. ip65 exports only six of the header's twelve and has no
`net_last_error` (no error channel at all); UCI exports all twelve. Treat
the header as a TODO list until #70 lands.
**`src/net_abi.inc` is the build-enforced boundary** (c64-lib-contract
SPEC §13, issue #70). `boot.s`, `http.s`, `tls_record_io.s` and `tls13.s`
`.include` it and import no `net_*` symbol directly, so a backend that
drops a symbol fails the link by name on both backends. Surface:

core net_init, net_dhcp_acquire, net_poll, net_local_ip (4 B),
net_last_error (1 B)
TCP net_tcp_connect (A/X = port), net_tcp_send (+ net_send_len),
net_tcp_close, net_tcp_state (NET_TCP_* in src/net/net_states.inc),
consumer-owned rx ring tcp_recv_{buf,head,tail,overflow} (§13.3)
DNS net_dns_resolve, net_resolved_ip ($FF x4 = resolved by the device)
ours net_recv_byte (the drain entry, #72), net_banner_str

- `src/net/<backend>/net_manifest.s` exports `NET_BACKEND_FAMILIES`
(CORE|TCP|DNS on both; UCI's DNS is by deferral); ip65's also carries
the §13.7 blob footprint, link-asserted against the `.incbin`'d size.
`src/net_abi_asserts.s` (§13.8) asserts the families and the ring
mask. The `NET_FAMILY_*` bits are `src/net/net_families.inc`, copied
verbatim from the contract and never exported.
- Error codes: ip65 `$40-$7F` (`ip65_errors.inc`, `NET_ERR_IP65_*`),
UCI `$80-$BF` (`uci_errors.inc`, `UCI_ERR_*`). The UCI range is ONE
namespace shared with c64-wireguard — `$8A UCI_ERR_LONG_READ` is theirs
and reserved here; allocate new codes in SPEC §13.2's table first.
- Gone, per §13.1: `net_tcp_set_recv_cb` (stub), `net_recv_ready`,
`net_dhcp` (alias), and `net_print_ip` — IP printing is consumer UI and
is now `print_local_ip` in `boot.s`, one copy for both backends.
- Byte accounting on ip65 (the tight one): LOADER went from 16 B free to
58 B; `print_local_ip` rides LOADER_OVERFLOW, so the NET_CODE tail that
is `HTTPS_HOST`/`HTTPS_PATH`'s ip65 budget shrank from 170 to ~60 B
beyond the default strings (wikipedia's +46 B still builds on both ip65
profiles; the theoretical 165 B host+path maximum no longer does).

- `src/net/ip65/` — ip65/RR-Net (cs8900a). Blob loaded at $2000 via
`.incbin`; `net.s` is the adapter; `ip65_symbols.inc` is the single
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ IP65_DIR := ip65
IP65_BUILD := ip65-build
IP65_BIN := $(IP65_BUILD)/ip65-c64.bin

CA65FLAGS := -I src -I src/inc -I src/crypto/shared -I src/net/$(BACKEND) -I build --debug-info
CA65FLAGS := -I src -I src/inc -I src/crypto/shared -I src/net -I src/net/$(BACKEND) -I build --debug-info
# Binary-include search roots for `.incbin` (issue #116).
#
# `-I` above does NOT feed `.incbin` — that is a separate search path in ca65,
Expand Down Expand Up @@ -191,8 +191,8 @@ CRYPTO_SRCS_ALL := $(wildcard src/crypto/*.s)
# overlay swap dispatcher, init orchestrator, shared sqtab stub. Always
# linked; sibling-lib integration (Phase C.3) hangs off these.
CRYPTO_SHARED_SRCS := $(wildcard src/crypto/shared/*.s)
IP65_SRCS := src/net/ip65/ip65_blob.s src/net/ip65/net.s src/net/ip65/net_banner.s src/net/ip65/exports.s
UCI_SRCS := src/net/uci/net.s src/net/uci/uci_cmd.s
IP65_SRCS := src/net/ip65/ip65_blob.s src/net/ip65/net.s src/net/ip65/net_banner.s src/net/ip65/exports.s src/net/ip65/net_manifest.s
UCI_SRCS := src/net/uci/net.s src/net/uci/uci_cmd.s src/net/uci/net_manifest.s

# Sibling-lib archive set. Phase C.3's nistcurves-p384 archive remains an
# external overlay image (see below), not linked into the main PRG.
Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,15 +118,15 @@ The TLS, HTTP, and crypto layers are backend-agnostic: switching backend
is a link-line change (different cfg + different `src/net/<backend>/*.o`),
not a call-site change.

`src/net_abi.inc` is **documentation, not an enforced interface**. No
translation unit `.include`s it (`grep -rn net_abi src/ tools/ cfg/ Makefile`
returns only comments), so none of its twelve `.import`s is checked by
the assembler or the linker. The symbols it declares and the
symbols TLS/HTTP/boot actually import overlap in 6 of 17, and the ip65
adapter exports neither `net_dhcp_acquire`, `net_tcp_set_recv_cb`,
`net_local_ip`, `net_resolved_ip`, `net_last_error` nor `net_tcp_state`
(`net_last_error` exists only under UCI, so ip65 has no error channel).
Making the header real, or deleting it, is item P1 of issue #70.
`src/net_abi.inc` is the **build-enforced** boundary between those layers
and the backend, aligned with
[c64-lib-contract SPEC §13](https://github.com/JC-000/c64-lib-contract)
(issue #70): every consumer TU that touches the network `.include`s it and
imports nothing else, both backends export the full core + TCP + DNS
surface, each ships a `net_manifest.s` declaring its families (ip65 also
declares its blob footprint per §13.7), and `src/net_abi_asserts.s` fails
the link if a backend stops providing a family. Error codes are
namespaced per §13.2: ip65 `$40-$7F`, UCI `$80-$BF`.

### TLS 1.3 Cipher Suite

Expand Down
98 changes: 98 additions & 0 deletions docs/engineering-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2741,3 +2741,101 @@ plumbing stays in tree; deleting it is a separate, larger cleanup. The
`build/labels.txt` order-only defect is untouched — after this guard it
affects only `USE_OVERLAY_P384_EMBED`, which cannot complete anyway, so a
fix there would be unverifiable.

## Issue #70 — the network ABI becomes real (2026-08-24)

What `src/net_abi.inc` used to be is recorded above ("Networking backend
ABI": nothing included it, 6 of 17 symbols overlapped, ip65 had no error
channel). This entry records what changed and what it cost.

### The change

- `src/net_abi.inc` is `.include`d by `boot.s`, `http.s`,
`tls_record_io.s`, `tls13.s`; their ad-hoc `.import net_*` lines are
gone. ld65 requires every import to resolve even when unused, so the
header now forces both backends to export the whole core/TCP/DNS surface.
- ip65 grew `net_dhcp_acquire` (was `net_dhcp`), `net_local_ip`
(copy of `ip65_cfg_ip` on DHCP success), `net_resolved_ip` (copy of
`ip65_dns_ip_addr`), `net_last_error` (`NET_ERR_IP65_*`, `$41-$45`,
`src/net/ip65/ip65_errors.inc`) and `net_tcp_state`. The ip65 driver
reports only a carry, so each code names the adapter entry that failed.
- UCI: `net_dhcp` alias, `net_tcp_set_recv_cb` stub and `net_print_ip`
deleted; `UCI_TCP_*` → `NET_TCP_*` (aliases kept in `uci_errors.inc`);
ring-full now sets `tcp_recv_overflow` (§13.3); `$8A UCI_ERR_LONG_READ`
reserved (allocated by c64-wireguard, not emitted here).
- `net_print_ip` was two identical routines, one per backend, differing
only in which IP variable they read. With `net_local_ip` on both it is
one consumer-side `print_local_ip` in `boot.s` (SPEC §13.1 lists
`net_print_ip` under "deliberately not in the contract").
- `net_recv_ready` retired (nobody imported it; `net_recv_byte`'s C flag
is the same test). `net_tcp_recv_cb` / `net_save_zp` / `net_restore_zp`
un-exported (§13.5); `tools/test_net.py` reaches them via
`build/labels.txt`, which carries local labels.
- New: `src/net/net_families.inc` (contract block verbatim),
`src/net/net_states.inc`, `src/net/{ip65,uci}/net_manifest.s`,
`src/net_abi_asserts.s` (§13.8; also took the §13.3 ring-mask assert
from `lib_contract_asserts.s`). ip65's manifest carries the §13.7 blob
equates and link-asserts `LIB_NET_IP65_BLOB_SIZE` against
`ip65_blob_end - ip65_blob_start`, labels added around the `.incbin`.
- `NET_BACKEND_FAMILIES` is exported `: absolute`. Without it ld65 warns
`Address size mismatch` — the byte-sized value infers zeropage — which
is contract #74 recurring in §13.0; worth a spec note upstream.

### Byte accounting (the part that constrained the design)

ip65 LOADER had 16 B free. The new ip65 logic (~95 B: four state stores,
two 4-byte copies, six error stores) fits only because `net_print_ip`
(~83 B) and `net_recv_ready` (14 B) left LOADER first:

region / segment (ip65) before after
LOADER free 16 B 58 B
LOADER_OVERFLOW $DE (222 B) $14C (332 B) + print_local_ip
NET_CODE tail free 170 B 60 B
BSS (COLD_SHADOW) +10 B (net_local_ip, net_resolved_ip,
net_last_error, net_tcp_state)

`print_local_ip` went to `LOADER_OVERFLOW` (NET_CODE tail) rather than
`HTTP_AUX_CODE2` (CRYPTO_RESIDENT, 150 B free on ip65-plain) on purpose:
CRYPTO_RESIDENT is where the next `libs/nistcurves` bump lands, and
ip65-onchip's copy of it is much tighter than ip65-plain's. The cost is
the ip65 `HTTPS_HOST`/`HTTPS_PATH` budget, which is now ~60 B beyond the
default strings. Measured: the wikipedia target (+46 B) still builds on
ip65-plain and ip65-onchip (`HTTPS_TARGET_RODATA` ends `$3FF1`, 14 B to
spare on onchip); the theoretical 63+100 B maximum no longer does. ip65
cannot reach a real server in any case (~36 min per handshake), so the
budget that shrank is the one with the least to buy.

PRG sizes are unchanged (47,105 / 62,977 B — both images are padded to
region ends); hashes changed, as they must.

### Evidence

- VICE: `tools/test_net.py` 65/65 (ring, callback, ZP save/restore, the
new empty-ring `net_recv_byte` oracle), `tools/test_http.py` 61/61,
`tools/test_tls_handshake.py` 21/21.
- Builds: ip65-plain, uci-plain, ip65-onchip + wikipedia target,
uci-comb + wikipedia target + `HTTPS_BODY_TO_REU=1`, all clean, no
ld65 warnings.
- Hardware (U64E, uci-onchip PRG `9e31c1bc…`, 48 MHz): `boot_check` PASS;
`phase2_check` PASS — DHCP through `net_dhcp_acquire`, `net_local_ip` =
10.43.23.81 read back over DMA, `net_last_error` = 0; `rig_https_live`
github.com **PASS, HTTP 200, CFIN 48.1 s** through the full ABI path.
`rig_https_local` FAILs — and fails identically on master `ab5de2b` as a
control (`tls_last_state=4`, sub-progress `$31`): it sends the listener's
IP as the hostname and the v0.4.2 SAN check rejects it. Pre-existing,
filed as #141; not evidence about this change.
- The `UCI_TCP_*` → `NET_TCP_*` promotion is byte-neutral: the onchip PRG
hashed `9e31c1bc…` before and after the rename (aliases are equal).
- Not run: the ip65 VICE ethernet rig (`tests/rig_vice_https_macos.py`)
needs `sudo` for the feth pair; the ip65 changes are covered by
`test_net.py` at the adapter level only.

### Cross-repo

- c64-lib-contract: §13.2 gains the cross-consumer error-code allocation
table (ip65 `$41-$45`, UCI `$81-$8A`) and the allocate-here-first rule;
§13.0 gains a canonical `net_families.inc`; §13.8 records c64-https as
aligned. The `$8A` case is why: c64-wireguard allocated it on
2026-08-24 and only a cross-repo diff noticed.
- c64-wireguard#48 keeps its own items (UDP-family renames, manifests);
`d9cd021`'s ring clamp is flagged there as §13.3 context for wg#46.
88 changes: 78 additions & 10 deletions src/boot.s
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,8 @@
.import crypto_init

; ---- imports: network (backend adapter — ip65 or uci) ----
.import net_init
.import net_dhcp
.import net_poll
.import net_print_ip
.import net_dns_resolve
.import net_tcp_connect
.import net_tcp_close
.import net_banner_str
; Networking comes ONLY through the §13 ABI header (issue #70).
.include "net_abi.inc"

; ---- imports: TLS state machine ----
.import tls_connect
Expand Down Expand Up @@ -421,7 +415,7 @@ do_net_init:
ldy #>dhcp_msg
jsr print_string

jsr net_dhcp
jsr net_dhcp_acquire
bcc @dhcp_ok

lda #<dhcp_fail_msg
Expand All @@ -433,7 +427,7 @@ do_net_init:
lda #<dhcp_ok_msg
ldy #>dhcp_ok_msg
jsr print_string
jsr net_print_ip
jsr print_local_ip

lda #1
sta net_initialized
Expand Down Expand Up @@ -1122,6 +1116,80 @@ reu_p384_overlay_init:
; =============================================================================
; Strings (read-only)
; =============================================================================
; =============================================================================
; print_local_ip - print net_local_ip in dotted decimal + CR
;
; Consumer UI, deliberately outside the network ABI (c64-lib-contract SPEC
; §13.1 lists net_print_ip under "deliberately not in the contract"). One
; copy for both backends now that net_local_ip is ABI data on ip65 too;
; it replaced two identical per-backend routines. Rides LOADER_OVERFLOW:
; the ip65 LOADER region is the tightest in the image and this was the
; one routine that could leave it.
; =============================================================================
.segment "LOADER_OVERFLOW"
print_local_ip:
lda net_local_ip+0
jsr @print_byte
lda #'.'
jsr chrout
lda net_local_ip+1
jsr @print_byte
lda #'.'
jsr chrout
lda net_local_ip+2
jsr @print_byte
lda #'.'
jsr chrout
lda net_local_ip+3
jsr @print_byte
lda #$0d
jmp chrout

; print decimal byte value (0-255), no leading zeros
@print_byte:
sta @pb_val
ldx #0
sec
@pb_100:
sbc #100
bcc @pb_100d
inx
jmp @pb_100
@pb_100d:
adc #100
cpx #0
beq @pb_tens ; skip leading zero
pha
txa
ora #$30
jsr chrout
pla
@pb_tens:
ldx #0
sec
@pb_10:
sbc #10
bcc @pb_10d
inx
jmp @pb_10
@pb_10d:
adc #10
cpx #0
bne @pb_t_out
ldy @pb_val
cpy #10
bcc @pb_ones ; value < 10, skip tens digit
@pb_t_out:
pha
txa
ora #$30
jsr chrout
pla
@pb_ones:
ora #$30
jmp chrout
@pb_val: .byte 0

.segment "RODATA"

; Banner is split in two so the per-backend `net_banner_str` (imported
Expand Down
8 changes: 1 addition & 7 deletions src/http.s
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,7 @@
.import tls_recv

; ---- imports: net.asm wrappers around ip65 ----
.import net_dns_resolve
.import net_tcp_connect
.import net_tcp_close
.import net_tcp_send
.import net_send_len
.import net_poll
.import net_recv_byte
.include "net_abi.inc" ; the §13 surface, nothing imported directly

; =============================================================================
; http_get - perform an HTTPS GET request
Expand Down
9 changes: 3 additions & 6 deletions src/lib_contract_asserts.s
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,7 @@ APP_OWNED = LIB_SHARED_PRIMITIVES_SQTAB | LIB_SHARED_PRIMITIVES_REU_MUL | LIB_SH


; =====================================================================
; §13.3TCP rx ring shape
; §13 — network backend ABI
; =====================================================================
; The ring mask must be 2^n - 1 or the backends' `and TCP_RECV_MASK`
; wrap arithmetic aliases addresses instead of wrapping. Assemble-time
; (`error`, not `lderror`) because TCP_RECV_MASK is a local equate from
; constants.inc, not an import.
.assert (TCP_RECV_MASK & (TCP_RECV_MASK + 1)) = 0, error, "TCP_RECV_MASK must be 2^n - 1 (c64-lib-contract SPEC §13.3)"
; Lives in src/net_abi_asserts.s (issue #70): the §13.0 family asserts
; plus the §13.3 ring-mask check that used to sit here.
31 changes: 19 additions & 12 deletions src/net/ip65/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
# src/net/ip65 — ip65 / RR-Net backend

The default networking backend for c64-https. Provides `net_*` entry
The default networking backend for c64-https. Provides the `net_*` entry
points on top of the ip65 TCP/IP stack with the RR-Net ethernet driver.

**It does not implement all of `src/net_abi.inc`, and nothing checks
that it does.** That header is `.include`d by no translation unit, so
it is documentation rather than an enforced interface. This backend
exports six of its twelve symbols (`net_init`, `net_poll`,
`net_dns_resolve`, `net_tcp_connect`, `net_tcp_send`, `net_tcp_close`)
and omits `net_dhcp_acquire`, `net_tcp_set_recv_cb`, `net_local_ip`,
`net_resolved_ip`, `net_last_error` and `net_tcp_state` — see `net.s:28`
("deferred to Phase 7", which shipped). It exports `net_dhcp` in place
of `net_dhcp_acquire`. Alignment is tracked in issue #70; see the
"Networking backend ABI" section of `CLAUDE.md` for the measured
declared-vs-used surface.
It implements the full surface `src/net_abi.inc` imports, aligned with
c64-lib-contract SPEC §13 (issue #70): core, TCP and DNS families —
`net_manifest.s` declares `NET_BACKEND_FAMILIES = CORE|TCP|DNS` plus the
§13.7 footprint of the position-linked blob (`$2000`, `$1B27` bytes,
BSS `$4000-$4F8B`), and the blob size is link-asserted against the bytes
actually `.incbin`'d.

Error channel: `net_last_error` carries `NET_ERR_IP65_*` codes from
`ip65_errors.inc`, allocated in the §13.2 ip65-family range `$40-$7F`.
ip65 itself reports only a carry, so each code names the adapter entry
point that failed. `net_tcp_state` follows `NET_TCP_*`
(`src/net/net_states.inc`); `net_local_ip` / `net_resolved_ip` are copies
of `ip65_cfg_ip` / `ip65_dns_ip_addr` taken on success.

Adapter-internal and deliberately not exported (§13.5): the TCP receive
callback and the crypto-ZP save/restore around every ip65 call. The
save/restore is load-bearing — the callback fires inside `ip65_process`
while ip65's ZP `$02-$1B` is live.
5 changes: 5 additions & 0 deletions src/net/ip65/ip65_blob.s
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

.segment "NET_CODE"

; Span labels for the §13.7 footprint assert in net_manifest.s.
.export ip65_blob_start, ip65_blob_end
ip65_blob_start:

; The blob is found through ca65's BINARY include path, set by the
; Makefile to an absolute $(abspath $(IP65_BUILD)). Hence the bare
; filename: there is deliberately no `../` here to resolve.
Expand Down Expand Up @@ -55,3 +59,4 @@
; see through .incbin, so without it this object could be assembled before
; the blob exists (issue #89).
.incbin "ip65-c64.bin"
ip65_blob_end:
Loading