Skip to content

net_tcp_connect: unconditional CONNECTED on short-read OPEN_TCP response masks firmware failure as success #36

Description

@JC-000

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-464net_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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions