Skip to content

Wi-Fi XMODEM putc can truncate a frame on a short socket send #667

Description

@righteousgambit

Environment

  • Branch: develop
  • Validated revision: 2ea66c10e96d88013a04441134d3d4f8e8c88510
  • Transfer mode: XMODEM-8K with CRC over TCP
  • Hardware/firmware: deterministic socket double; no machine or firmware required

Summary

WIFIStream.putc() makes one socket.send() call for an entire XMODEM frame and returns immediately. Python documents that socket.send() returns the number of bytes sent and that applications are responsible for transmitting any remaining bytes. A successful call may therefore accept only a prefix of the frame.

The XMODEM sender supplies the complete frame to putc() once, ignores the callback's return count, and waits for the receiver's ACK. On a partial TCP write, the rest of that frame is never queued by the controller.

Root cause

Deterministic reproduction

Use a socket double whose send() accepts only 2,048 bytes from a valid 8,199-byte XMODEM-8K CRC frame. Call WIFIStream.putc(frame) once and assert that the complete frame reached the simulated wire.

Command run against pinned develop:

PYTHONDONTWRITEBYTECODE=1 PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 \
  PYTHONPATH=/path/to/pinned-develop \
  python3.11 -m pytest -c /dev/null -p no:cacheprovider \
  --rootdir=/tmp --import-mode=importlib tests/unit/test_wifi_stream.py -q

Observed failure:

F                                                                        [100%]
E       AssertionError: assert 2048 == 8199
1 failed

The socket accepted exactly the configured 2,048-byte prefix, while the complete frame length was 8,199 bytes.

Impact

This can cause uploads to retry, time out, or fail when the OS accepts only part of a frame in a send() call. The XMODEM CRC and ACK/NAK exchange prevent the truncated frame from being silently accepted as valid, so this is a transfer reliability/availability issue rather than demonstrated silent data corruption.

Expected behavior

WIFIStream.putc() should not return successfully until every byte in the supplied XMODEM frame has been sent, or the socket raises an error. Python's socket.sendall() provides that contract.

Acceptance criteria

  • Send the entire byte string supplied to WIFIStream.putc() before reporting success.
  • Return the input length so the callback preserves its existing integer success contract.
  • Propagate socket errors rather than reporting a complete write.
  • Add a deterministic regression using a valid 8,199-byte XMODEM-8K CRC frame and a socket double that models a 2,048-byte short write.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions