Skip to content

Integrate ip65 TCP/IP stack with RR-Net driver - #1

Merged
JC-000 merged 5 commits into
masterfrom
feature/ip65-integration
May 6, 2026
Merged

JC-000 merged 5 commits into
masterfrom
feature/ip65-integration

Conversation

@JC-000

@JC-000 JC-000 commented May 6, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add ip65 as git submodule, build TCP + RR-Net CS8900a driver into a 6.8 KB binary blob at $2000
  • Wire all net.asm stubs to call ip65 through a fixed jump table with ZP save/restore ($02-$1B)
  • Menu-driven startup (I=init, G=get, Q=quit) — no auto-init prevents crashes without hardware
  • 56-test Python suite verifying build integrity, jump table, ZP round-trip, ring buffer, and ip65_init

Test plan

  • make clean && make — two-stage build (ca65/ld65 → ACME)
  • python3 tools/test_net.py — 56/56 tests pass
  • Test with VICE + virtual RR-Net when available

🤖 Generated with Claude Code


Originally posted by @JC-000 on 2026-03-14

JC-000 and others added 5 commits March 14, 2026 14:29
- Add ip65 as git submodule (cc65/ip65)
- Build ip65 TCP + RR-Net driver into binary blob at $2000 via ca65/ld65
  - ip65_stub.s: jump table (11 entries) + variable address table
  - ip65.cfg: custom linker config placing code at $2000, BSS at $4000
- Wire net.asm to call ip65 through the jump table with ZP save/restore
  - net_init, net_dhcp, net_poll, net_dns_resolve, net_tcp_connect/send/close
  - net_print_ip: decimal IP display from ip65 config area
  - TCP receive ring buffer (256 bytes) for callback data
- Update constants.asm with ip65 jump table offsets and variable addresses
- Change boot.asm to menu-driven startup (I=init, G=get, Q=quit)
  - No auto-init on startup — prevents crash without RR-Net hardware
- Update Makefile for two-stage build: ca65/ld65 (ip65) then ACME (our code)
- Add test suite: tools/test_net.py (56 tests)
  - Build integrity (14 labels)
  - ip65 jump table validation (22 entries)
  - ZP save/restore round-trip (10 random patterns)
  - TCP receive ring buffer (drain + wrap-around)
  - ip65_init without hardware (graceful failure + ZP preservation)

Binary: 15.4 KB ($0801-$458F), ip65 blob: 6.8 KB at $2000-$3B26

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comprehensive per-address documentation of all 256 ZP bytes:
- 5-tier safety classification (always safe, BASIC-only, KERNAL I/O,
  IRQ-clobbered, system-reserved)
- Complete IRQ handler ($EA31) footprint with code paths
- KERNAL call side effects for CHROUT, CHRIN, GETIN, OPEN, CLOSE,
  LOAD, SAVE, SETLFS, SETNAM, CLRCHN, CHKIN, CHKOUT
- cc65/ip65 ZP convention ($02-$1B)
- Cursor blink disable optimization ($CC)
- Recommended allocation strategy for multi-module projects
- Test harness jsr() BRK artifact documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy and adapt 6 crypto modules from sibling projects:
- word32.asm (505 lines) — 32-bit arithmetic for ChaCha20
- chacha20.asm (326 lines) — ChaCha20 stream cipher (RFC 7539)
- poly1305.asm (610 lines) — Poly1305 MAC with quarter-square multiply
- aead.asm (311 lines) — ChaCha20-Poly1305 AEAD (RFC 7539 S2.8)
- sha256.asm (1029 lines) — SHA-256 with optimized byte-rotate decomposition
- hmac_drbg.asm (621 lines) — HMAC-SHA256, HMAC-DRBG, entropy collection

Adaptations: strip menu UI code, rename zp_ptr1->zp_ptr, add extra_sid
stubs for entropy, add sqtab/SID equates to constants.asm, merge all
crypto data buffers into data.asm (~1.1 KB).

Test suites (85 tests total):
- test_sha256.py: 7 tests (init IV, NIST "abc", empty, boundary 1/55/56/63)
- test_crypto.py: 22 tests (sqtab_init, ChaCha20 block+encrypt RFC vectors,
  Poly1305 MAC RFC vector, AEAD encrypt/decrypt RFC vector + 5 random)
- test_net.py: 56 tests (unchanged, regression pass)

Binary: 22 KB, 406 labels. All crypto at $3B27-$4E88.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hkdf.asm stubs with working implementations:
- hkdf_extract: HMAC-SHA256(salt, IKM) with zero-salt fallback
- hkdf_expand: HMAC-SHA256(PRK, info || 0x01) for L ≤ 32
- hkdf_expand_label: builds TLS 1.3 HkdfLabel struct in hkdf_info_buf,
  copies label via ZP indirect addressing, then calls hkdf_expand
- tls_derive_secret: convenience wrapper (unchanged)

Optimizations: ZP pointer ($FB) for indirect copies in expand_label,
tight DEX/BPL loops for 32-byte copies, direct info_buf construction.

Test suite: tools/test_hkdf.py (12 tests)
- RFC 5869 Extract cases 1 + 3 (empty salt)
- RFC 5869 Expand cases 1 + 3 (L=32 truncated)
- TLS 1.3 early_secret derivation
- HKDF-Expand-Label with "derived" label + empty hash context
- 3 random Extract + 3 random Expand-Label vs Python hmac reference

All 97 tests pass (56 net + 7 SHA-256 + 22 crypto + 12 HKDF).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Mark ip65 integration, crypto modules, and HKDF as complete.
Update test section with all 4 suites (97 tests total).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@JC-000
JC-000 merged commit 4b4faa1 into master May 6, 2026
@JC-000
JC-000 deleted the feature/ip65-integration branch May 6, 2026 19:03
JC-000 added a commit that referenced this pull request Sep 30, 2026
…consume the load; refuse data on non-read replies (adv-l2 #1-#4)

#1 (MAJOR) A valid slot beside one whose open/read failed (DOS, NOPATH,
   IO) or whose version is unknown was loaded as VALID. That slot can
   be the NEWER one, so the host looked absent and a save could write
   over it: a rollback (reproduced; the write landed). Only ABSENT and
   torn damage (FORMAT, CHECKSUM) are tolerated now (ts_tolerable); the
   Python mirror and its test follow.
#2 stage made the unsaved record visible to lookup, and a failed save
   kept it there (and allowed a blind retry after CHANGED). stage now
   consumes the load (state NONE, lookup C=1); every save attempt
   consumes load and staged record (ts_consume).
#3 test gap: a stale-but-valid read-back case now fails if the
   read-back generation compare is removed.
#4 dos_finish stored reply data for OPEN/CLOSE/WRITE at the stale
   dos_store whenever dos_cnt was nonzero; a non-read reply with data is
   now refused before anything is stored.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JC-000 added a commit that referenced this pull request Oct 1, 2026
A key refused mid-entry, or a host refused at RETURN, returned straight
to main_loop, so the rest of what the operator typed ran as menu
commands: 'H' dialled zimmers.net over plain HTTP, 'I' re-initialised
the network, 'Q' quit. Now a refused key's line is swallowed unechoed up
to RETURN, a refused host still gets the PATH prompt, the KERNAL key
buffer is flushed, and only then is INVALID TARGET printed.

test_typed_target no longer hides this: every refusal case types the
rest of its entry, lets main_loop run afterwards, and fails on a dial,
an HTTP GET, a re-init, a quit or keys left queued. Four cases type
h/i/q after the refused key. Current head before this commit: 112/132;
after: 132/132.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JC-000 added a commit that referenced this pull request Oct 1, 2026
A key refused mid-entry, or a host refused at RETURN, returned straight
to main_loop, so the rest of what the operator typed ran as menu
commands: 'H' dialled zimmers.net over plain HTTP, 'I' re-initialised
the network, 'Q' quit. Now a refused key's line is swallowed unechoed up
to RETURN, a refused host still gets the PATH prompt, the KERNAL key
buffer is flushed, and only then is INVALID TARGET printed.

test_typed_target no longer hides this: every refusal case types the
rest of its entry, lets main_loop run afterwards, and fails on a dial,
an HTTP GET, a re-init, a quit or keys left queued. Four cases type
h/i/q after the refused key. Current head before this commit: 112/132;
after: 132/132.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JC-000 added a commit that referenced this pull request Oct 6, 2026
…K_CODE)

1. MEDIUM-HIGH. The hook stored tp_bwarn from A after a failed
   tp_cmp_got, and A is then the bundle pin's byte where the hashes first
   differ (from byte 31 down). A $00 there left tp_bwarn clear: BUNDLE
   MISMATCH printed, RECORD KEY? was never asked, and trust_post recorded
   the server's key as TOFU -- free when the pin's byte 31 is $00, ~2^8 /
   2^16 grinds for byte 30 / 29. Now `lda #1`. Regression: pins with byte
   31 = $00, and byte 30 = $00 with 31 equal (red on 04af8b9: recorded
   without A); mutant. Audit: tp_bwarn was the only flag stored from an
   incidental A; the other stores are explicit, or clear (the safe way).
2. LOW. TRUST_POLICY_BSS is not boot-zeroed, so an accept armed before 'Q'
   survived RUN (or a reset and SYS). start now calls trust_state_init,
   which clears it and the bundle cache. Test + mutant (drop the call).
3. LOW, comb. A cold-bank refusal of trust_post (socket ERROR, latched DMA
   timeout) skipped its clears, keeping tp_ovr_armed and tp_mode. The
   resident stubs now clear both whenever the bank itself refuses. Red on
   04af8b9 (armed=1 mode=1 survived); mutant.

Under TRUST_STORE the hook's code is TRUST_HOOK_CODE, not CERT_PIN_CODE:
the comb cfgs place it in NET_CODE (CRYPTO_HOT has no room for it beside
#273's body-sink bound), the uci cfg where CERT_PIN_CODE already was. The
plain pin keeps CERT_PIN_CODE, so non-trust pinned builds do not move.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JC-000 added a commit that referenced this pull request Oct 6, 2026
…K_CODE)

1. MEDIUM-HIGH. The hook stored tp_bwarn from A after a failed
   tp_cmp_got, and A is then the bundle pin's byte where the hashes first
   differ (from byte 31 down). A $00 there left tp_bwarn clear: BUNDLE
   MISMATCH printed, RECORD KEY? was never asked, and trust_post recorded
   the server's key as TOFU -- free when the pin's byte 31 is $00, ~2^8 /
   2^16 grinds for byte 30 / 29. Now `lda #1`. Regression: pins with byte
   31 = $00, and byte 30 = $00 with 31 equal (red on 04af8b9: recorded
   without A); mutant. Audit: tp_bwarn was the only flag stored from an
   incidental A; the other stores are explicit, or clear (the safe way).
2. LOW. TRUST_POLICY_BSS is not boot-zeroed, so an accept armed before 'Q'
   survived RUN (or a reset and SYS). start now calls trust_state_init,
   which clears it and the bundle cache. Test + mutant (drop the call).
3. LOW, comb. A cold-bank refusal of trust_post (socket ERROR, latched DMA
   timeout) skipped its clears, keeping tp_ovr_armed and tp_mode. The
   resident stubs now clear both whenever the bank itself refuses. Red on
   04af8b9 (armed=1 mode=1 survived); mutant.

Under TRUST_STORE the hook's code is TRUST_HOOK_CODE, not CERT_PIN_CODE:
the comb cfgs place it in NET_CODE (CRYPTO_HOT has no room for it beside
#273's body-sink bound), the uci cfg where CERT_PIN_CODE already was. The
plain pin keeps CERT_PIN_CODE, so non-trust pinned builds do not move.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
JC-000 added a commit that referenced this pull request Oct 14, 2026
…e read budget

Amendments from the adv-296 review:
- test_copy_counts_at_page_edges: headers 1/255/256/257/512/768/893 at
  ring tails $0000/$0E7D/$0F00/$0FFF, plus short, exact-request and
  over-claim cases. The mutant count-always-iny (an unconditional iny at
  @p_copy_init) killed every session whose header is a multiple of 256
  and no test fed one.
- _pacing_flows traces a CLOSE whose PUSH is refused once, so the
  rejected path's CLR_ERR write fence is checked (rejected-clr-unfenced).
- test_read_pacing's read side was SBC #1-shaped only; it now also caps
  the instructions after each read (READ_GAP_BUDGET, from the measured
  maxima), whatever the delay's shape (statline-nopsled). The docstring
  says what it detects and on which flows.

check 53/53 (default 44648860, M3_READ_FENCE f5d9278b); mutants 74/74.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant