Symptom
After JSR net_tcp_connect, callers observe net_tcp_state = UCI_TCP_CONNECTED (0x01) even when the U64E firmware returned a short or empty OPEN_TCP response — i.e. no real socket was actually opened. The C64 then commits to a phantom socket (uci_socket_id = 0x00) and pushes data into a non-existent connection. Combined with the unbounded spin in uci_wait_idle (see companion issue), this manifests as a 600+ s test hang.
Discovery context
Surfaced during validation of c64-test-harness issue #90 (JC-000/c64-test-harness#90). The downstream tools/uci/test_https_local.py test wedges for 600 s while the C64 pushes a 137-byte ClientHello to socket_id=0. The Python listener never receives a SYN, but the C64 thinks it's connected and is dutifully writing bytes via SOCKET_WRITE.
Full forensic analysis: JC-000/c64-test-harness#90 (comment)
Specific code
src/net/uci/net.s:447-464 — net_tcp_connect@tc_no_err. After uci_read_resp_bytes populates uci_socket_id, control unconditionally falls through to set net_tcp_state = UCI_TCP_CONNECTED, regardless of whether the read succeeded with a non-zero socket_id.
- Healthy run:
uci_socket_id = 0x06 → CONNECTED is correct. - Failing run:
uci_socket_id = 0x00 → CONNECTED is wrong; the C64 proceeds as if it has a socket.
Evidence (from c64-test-harness #90 artifacts)
Three independent failing-run dumps in /tmp/uci_https_debug/* all show identical post-wedge state:
{
"tls_state": "01 (CLIENT_HELLO assembled)",
"net_tcp_state": "01 (CONNECTED)",
"uci_socket_id": "00",
"tcp_recv_head/tail":"0/0",
"server_result": {"listening": true, "client_addr": null,
"error": "TimeoutError: timed out"}
}The listener saw client_addr=None after 600 s. No SYN ever arrived. Yet the C64 wrote 227k UCI register accesses and the full ClientHello to a phantom socket.
The PASS run (sibling test test_https_print_body on same device, same session) shows uci_socket_id = 0x06 (real socket) and the listener received the SYN at ('10.43.23.81', 52436). The two outcomes differ only in whether the firmware honored the OPEN_TCP — i.e. they are non-deterministic outcomes of the same code path.
Suggested fix
After uci_read_resp_bytes, validate that uci_socket_id != 0 (or whatever sentinel matches the firmware's "no socket" return) before setting CONNECTED:
@tc_no_err: lda uci_socket_id beq @tc_no_socket ; firmware returned no socket lda #UCI_TCP_CONNECTED sta net_tcp_state rts@tc_no_socket: lda #UCI_TCP_FAILED ; or appropriate failure constant sta net_tcp_state sec rts
Callers (tls_handshake, etc.) can then check the state and propagate the error cleanly instead of wedging on a phantom SOCKET_WRITE.
Severity
High — converts a transient (and apparently non-deterministic) firmware OPEN_TCP failure on U64E 3.14d into a 600 s test hang, blocking CI on the UCI suite. Combined with the unbounded uci_wait_idle spin (companion issue), the failure mode is silent rather than loud.
Symptom
After
JSR net_tcp_connect, callers observenet_tcp_state = UCI_TCP_CONNECTED (0x01)even when the U64E firmware returned a short or emptyOPEN_TCPresponse — i.e. no real socket was actually opened. The C64 then commits to a phantom socket (uci_socket_id = 0x00) and pushes data into a non-existent connection. Combined with the unbounded spin inuci_wait_idle(see companion issue), this manifests as a 600+ s test hang.Discovery context
Surfaced during validation of c64-test-harness issue #90 (JC-000/c64-test-harness#90). The downstream
tools/uci/test_https_local.pytest wedges for 600 s while the C64 pushes a 137-byte ClientHello tosocket_id=0. The Python listener never receives a SYN, but the C64 thinks it's connected and is dutifully writing bytes viaSOCKET_WRITE.Full forensic analysis: JC-000/c64-test-harness#90 (comment)
Specific code
src/net/uci/net.s:447-464—net_tcp_connect@tc_no_err. Afteruci_read_resp_bytespopulatesuci_socket_id, control unconditionally falls through to setnet_tcp_state = UCI_TCP_CONNECTED, regardless of whether the read succeeded with a non-zero socket_id.uci_socket_id = 0x06→ CONNECTED is correct.uci_socket_id = 0x00→ CONNECTED is wrong; the C64 proceeds as if it has a socket.Evidence (from c64-test-harness #90 artifacts)
Three independent failing-run dumps in
/tmp/uci_https_debug/*all show identical post-wedge state:{ "tls_state": "01 (CLIENT_HELLO assembled)", "net_tcp_state": "01 (CONNECTED)", "uci_socket_id": "00", "tcp_recv_head/tail":"0/0", "server_result": {"listening": true, "client_addr": null, "error": "TimeoutError: timed out"} }The listener saw
client_addr=Noneafter 600 s. No SYN ever arrived. Yet the C64 wrote 227k UCI register accesses and the full ClientHello to a phantom socket.The PASS run (sibling test
test_https_print_bodyon same device, same session) showsuci_socket_id = 0x06(real socket) and the listener received the SYN at('10.43.23.81', 52436). The two outcomes differ only in whether the firmware honored the OPEN_TCP — i.e. they are non-deterministic outcomes of the same code path.Suggested fix
After
uci_read_resp_bytes, validate thatuci_socket_id != 0(or whatever sentinel matches the firmware's "no socket" return) before setting CONNECTED:Callers (
tls_handshake, etc.) can then check the state and propagate the error cleanly instead of wedging on a phantomSOCKET_WRITE.Severity
High — converts a transient (and apparently non-deterministic) firmware OPEN_TCP failure on U64E 3.14d into a 600 s test hang, blocking CI on the UCI suite. Combined with the unbounded
uci_wait_idlespin (companion issue), the failure mode is silent rather than loud.