Skip to content
Closed
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
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ Variables:
`$(error)`s, not in `PACKAGE_VARIANTS`. `HTTPS_PIN_SPKI_SHA256` = trust
PIN, `M3_ALLOW_TLS12=1` offers hardened 1.2. UCI reads are unfenced
(writes keep `uci_fence`); `M3_READ_FENCE=1` restores the old client
byte for byte, `M3_TRACE=1` = per-READ timing records (measurement only;
byte for byte; READs ask 1472 B over one Data More continuation,
`M3_READ_ONE_BLOCK=1` = 893 B one-block READs (the previous default);
`M3_TRACE=1` = per-READ timing records (measurement only;
`rig_https_m3.py timed`). Model + mutants:
`tools/check_m3_client.py`, `tools/mutate_m3_client.py` (standalone,
need an m3 build); hardware: `rig_https_banner.py`, `rig_https_m3.py`.
Expand Down
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,12 @@ $(error M3_ALLOW_TLS12 must be 1 (offer TLS 1.3 and hardened 1.2) or 0/unset (TL
endif
# M3_READ_FENCE=1: a uci_fence after every UCI register READ too, as before
# (src/net/uci-m3/m3.inc, "Read pacing"): the A/B arm and the fallback.
# M3_READ_ONE_BLOCK=1: READs of one block (893 B), no Data More continuation
# (m3.inc, "READ size"): the stage-1 client, an A/B arm.
# M3_TRACE=1: per-net_poll timing records, a measurement build only.
$(foreach k,M3_READ_FENCE M3_TRACE,$(if $(filter-out 0 1,$($(k))),$(error $(k) must be 1 or 0/unset)))
$(foreach k,M3_READ_FENCE M3_READ_ONE_BLOCK M3_TRACE,$(if $(filter-out 0 1,$($(k))),$(error $(k) must be 1 or 0/unset)))
else
$(foreach k,M3_READ_FENCE M3_TRACE,$(if $(filter-out 0,$($(k))),$(error $(k)=$($(k)) applies only to BACKEND=uci-m3)))
$(foreach k,M3_READ_FENCE M3_READ_ONE_BLOCK M3_TRACE,$(if $(filter-out 0,$($(k))),$(error $(k)=$($(k)) applies only to BACKEND=uci-m3)))
endif

# X25519 is always the libs/x25519 sibling (issue #245 retired the in-tree
Expand Down Expand Up @@ -125,6 +127,9 @@ endif
ifeq ($(M3_READ_FENCE),1)
CA65FLAGS += -D M3_READ_FENCE=1
endif
ifeq ($(M3_READ_ONE_BLOCK),1)
CA65FLAGS += -D M3_READ_ONE_BLOCK=1
endif
ifeq ($(M3_TRACE),1)
CA65FLAGS += -D M3_TRACE=1
endif
Expand Down
18 changes: 18 additions & 0 deletions src/net/uci-m3/m3.inc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ M3_FLAG_TLS13_ONLY = $01 ; REQUIRE_TLS13; bits 1-7 must be 0 (82)
M3_HOST_MAX = 253 ; host 1..253 bytes, no $00 (S 1.1 tail grammar)
M3_WRITE_MAX = 892 ; WRITE_SOCKET plaintext per command
M3_READ_MAX = 893 ; one response block: 2-byte header + 893
M3_READ_MORE = 895 ; a continuation block: payload only
M3_READ_TOTAL = 1472 ; one READ, over Data More (S:2012-2013)
M3_STATUS_MAX = 40 ; every M3 status line is <= 40 B (ER-8)
M3_SWEEP_HANDLES = 16 ; this firmware's handle range 0..15 (ER-9)

Expand Down Expand Up @@ -81,6 +83,22 @@ M3_B_HTTP_IDLE = 6000 ; http_recv_body: 60 s with no byte arrived
; +8 bytes put into the ring (16-bit)
; +10 m3_poll_result
; +11 $DF1C read right after that READ's PUSH and its fence
; --- READ size (M3_READ_ONE_BLOCK) ----------------------------------------
; A READ asks for up to M3_READ_TOTAL: the first block holds the header and
; 893 bytes, the rest comes in ONE continuation block over Data More, which
; the client data-accepts after reading the first block to its end (S:2013,
; errata ER-1 N-1 and ER-5; firmware network_target.cc start_read_reply /
; get_more_data). A continuation is taken only after a FULL block and only
; while the header still owes bytes; anything else stays a hole (ER-5).
; `M3_READ_ONE_BLOCK=1` (and M3_READ_FENCE=1, the pre-change client) asks
; for at most one block and takes every Data More as a hole, as before.
.if .defined(M3_READ_FENCE) .or .defined(M3_READ_ONE_BLOCK)
M3_READ_REQ = M3_READ_MAX
.else
M3_READ_REQ = M3_READ_TOTAL
M3_MULTI_BLOCK = 1
.endif

M3_TR_BASE = $6000
M3_TR_REC = 12
M3_TR_CAP = ($A000 - M3_TR_BASE) / M3_TR_REC
Expand Down
32 changes: 32 additions & 0 deletions src/net/uci-m3/m3_cmd.s
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@
.export m3_code
.export m3_wedged
.export m3_dl
.ifdef M3_MULTI_BLOCK
.export m3_next_block
.endif
.ifdef M3_TRACE
.export m3_tr_sample
.export m3_tr_delta
Expand Down Expand Up @@ -465,6 +468,35 @@ m3_accept:
sta UCI_CONTROL
jmp m3_settle

.ifdef M3_MULTI_BLOCK
; =============================================================================
; m3_next_block — data-accept a Data More block (read to its end) and wait for
; the next one, up to 12 s from the accept (errata ER-1, N-1: the Nios serves
; it from its buffer). The accept drops the interface to state "01" until
; the next block is validated (command_protocol.vhd).
; Out: C=0 the next block is staged. C=1 ABORTed at the bound (net_last_error
; $89, m3_wedged if bit 2 never cleared): its bytes are lost.
; Clobbers: A, X, Y
; =============================================================================
m3_next_block:
jsr m3_accept
lda #<M3_B_TLS
ldx #>M3_B_TLS
ldy #M3_DL_CMD
jsr m3_dl_arm ; the accept + 12 s
lda #UCI_STAT_REPLY_VALID
jsr m3_wait_set
bcs :+
rts ; C=0
: lda #UCI_ERR_WAIT_TIMEOUT
sta net_last_error
lda #<M3_B_POST_ABORT
ldx #>M3_B_POST_ABORT
jsr m3_abort_wait
sec
rts
.endif

; m3_finish — drop the rest of the block, read the status line, accept.
; The status is read BEFORE the accept, which empties both queues.
m3_finish:
Expand Down
72 changes: 64 additions & 8 deletions src/net/uci-m3/net.s
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@
.import m3_status_seen
.import m3_code
.import m3_wedged
.ifdef M3_MULTI_BLOCK
.import m3_next_block
.endif
.ifdef M3_TRACE
.import m3_tr_sample
.import m3_tr_delta
Expand Down Expand Up @@ -708,14 +711,19 @@ m3_status_number:
; =============================================================================
; net_poll — one READ into the rx ring.
;
; Request min(ring free - 1, 893): one response block, no Data More (S 1.5).
; Request min(ring free - 1, M3_READ_REQ): 1472 over a first block and one
; Data More continuation, or 893 and one block under M3_READ_ONE_BLOCK /
; M3_READ_FENCE (m3.inc, "READ size"; S 1.5, S:2013).
; Then, by header and status (S 1.6, Appendix A, ER-5/7/12):
; no header (bit 7 clear) an empty reply (81/82): a READ of ours that
; the firmware refused. ERROR, still owned.
; n > 0 data into the ring. A block that is not read
; to its end (short, over-long, Data More)
; leaves a hole in the stream: ERROR, owned,
; so the CLOSE that follows is sent (ER-5).
; to its end, a short one, an over-long one, or
; a Data More the header does not owe, leaves a
; hole in the stream: ERROR, owned, so the CLOSE
; that follows is sent (ER-5). A continuation
; that does not come within 12 s of its accept
; is ABORTed: $89, ERROR, owned.
; $FFFF + 02,..: 11 nothing yet (M3_POLL_IDLE).
; $FFFF + anything else the number is not ours ("02,NO DATA: 9", ER-7):
; stop, and do NOT CLOSE it. ERROR, not owned.
Expand Down Expand Up @@ -773,13 +781,13 @@ net_poll:
rts
:
lda m3_req
cmp #<M3_READ_MAX
cmp #<M3_READ_REQ
lda m3_req+1
sbc #>M3_READ_MAX ; C=1 iff req >= MAX
sbc #>M3_READ_REQ ; C=1 iff req >= MAX
bcc :+
lda #<M3_READ_MAX
lda #<M3_READ_REQ
sta m3_req
lda #>M3_READ_MAX
lda #>M3_READ_REQ
sta m3_req+1
:
lda #M3_POLL_END
Expand Down Expand Up @@ -908,6 +916,16 @@ net_poll:
lda tcp_recv_tail+1
adc #>tcp_recv_buf
sta @p_store+2
.ifdef M3_MULTI_BLOCK
lda tcp_recv_tail+0 ; where this READ's bytes start
sta m3_cp0
lda tcp_recv_tail+1
sta m3_cp0+1
lda #<M3_READ_MAX ; the first block ends after 893
sta m3_blk_end
lda #>M3_READ_MAX
sta m3_blk_end+1
.endif
@p_copy:
lda UCI_STATUS ; ER-12: DATA_AV before EVERY byte, or a
bpl @p_short ; short block reads as $00s (bit 7)
Expand All @@ -929,6 +947,38 @@ net_poll:
bne @p_copy
beq @p_tail ; always
@p_short:
.ifdef M3_MULTI_BLOCK
; A = $DF1C, bit 7 clear: this block is read to its end and the
; header still owes bytes. Take the continuation only on Data More
; ("11") after a FULL block; anything else is a hole (ER-5).
and #UCI_STAT_STATE
cmp #UCI_STAT_STATE
bne @p_hole
stx m3_left
sty m3_left+1
lda @p_store+1 ; bytes so far = store - start, mod ring
sec
sbc m3_cp0
sta m3_got
lda @p_store+2 ; (>tcp_recv_buf is 0 under the mask)
sbc m3_cp0+1
and #>TCP_RECV_MASK
cmp m3_blk_end+1
bne @p_hole
lda m3_got
cmp m3_blk_end
bne @p_hole
jsr m3_next_block ; data-accept, <= 12 s from it (ER-1)
bcc :+
jmp @p_dead_owned ; ABORTed: $89, the bytes are lost
: ldx m3_left ; m3_blk_end stays 893: a second
ldy m3_left+1 ; Data More never matches it (ER-5)
jmp @p_copy
@p_hole:
.assert (>tcp_recv_buf & >TCP_RECV_MASK) = 0, error, "@p_short drops >tcp_recv_buf by masking"
.assert M3_READ_TOTAL <= M3_READ_MAX + M3_READ_MORE, error, "a READ is at most two blocks"
.assert M3_READ_TOTAL <= TCP_RECV_MASK, error, "bytes so far are counted mod the ring"
.endif
lda #UCI_ERR_SHORT_READ ; short of its header: a hole (ER-5)
sta m3_bad
@p_tail:
Expand Down Expand Up @@ -1364,6 +1414,12 @@ m3_rem: .res 2
m3_piece: .res 2
m3_cnt: .res 2
m3_wresp: .res 2
.ifdef M3_MULTI_BLOCK
m3_cp0: .res 2 ; ring tail where this READ's bytes start
m3_blk_end: .res 2 ; bytes in the READ when block 1 is full
m3_left: .res 2 ; the copy's X/Y over a continuation
m3_got: .res 1
.endif
.ifdef M3_TRACE
m3_tr_zero: ; net_init zeroes these
m3_tr_count: .res 2 ; CONNECTED net_poll calls since net_init
Expand Down
Loading