Skip to content
Draft
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- Fixed: Ensure complete XMODEM packets are written over Wi-Fi
- Enhancement: Add multi-select to the remote file browser
- Enhancement: Display error message in halt popup. Requires halt errors to start with "ERROR: " in the firmware
- Enhancement: Add popup notice when using stock firmware instead of the Community Firmware
Expand Down
3 changes: 2 additions & 1 deletion carveracontroller/WIFIStream.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ def getc(self, size, timeout=0.5):
return None

def putc(self, data, timeout=0.5):
return self.socket.send(data) or None
self.socket.sendall(data)
return len(data)

def upload(self, filename, local_md5, callback):
# do upload
Expand Down
46 changes: 46 additions & 0 deletions tests/unit/test_wifi_stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Tests for WIFIStream socket write semantics."""

from carveracontroller.WIFIStream import WIFIStream
from carveracontroller.XMODEM import XMODEM


class DeterministicShortWriteSocket:
"""Socket double that accepts only a prefix from each ``send`` call."""

def __init__(self, short_write_size):
self.short_write_size = short_write_size
self.send_calls = 0
self.sendall_calls = 0
self.wire = bytearray()

def send(self, data):
self.send_calls += 1
accepted = min(self.short_write_size, len(data))
self.wire.extend(data[:accepted])
return accepted

def sendall(self, data):
self.sendall_calls += 1
self.wire.extend(data)


def _xmodem8k_frame():
modem = XMODEM(lambda _size, _timeout=0.5: None, lambda data, _timeout=0.5: len(data))
payload = b"x" * 8192
framed_payload = bytes([len(payload) >> 8, len(payload) & 0xFF]) + payload
return bytes(modem._make_send_header(8192, 1) + framed_payload + modem._make_send_checksum(1, framed_payload))


def test_putc_sends_the_complete_xmodem_frame_and_returns_its_length():
stream = WIFIStream.__new__(WIFIStream)
stream.socket = DeterministicShortWriteSocket(short_write_size=2048)
frame = _xmodem8k_frame()
assert len(frame) == 8199, "fixture must model a complete xmodem8k frame"

result = stream.putc(frame)

assert len(stream.socket.wire) == len(frame)
assert bytes(stream.socket.wire) == frame
assert stream.socket.send_calls == 0
assert stream.socket.sendall_calls == 1
assert result == len(frame)
Loading