Skip to content

Fix OTA silently transferring a zero-length image - #2466

Open
fluffyspace wants to merge 1 commit into
InfiniTimeOrg:mainfrom
fluffyspace:fix-dfu-erase-inactivity-timeout
Open

Fix OTA silently transferring a zero-length image#2466
fluffyspace wants to merge 1 commit into
InfiniTimeOrg:mainfrom
fluffyspace:fix-dfu-erase-inactivity-timeout

Conversation

@fluffyspace

Copy link
Copy Markdown

The bug

DfuService::OnServiceData arms the 10 s one-shot inactivity timer before dispatching each access:

if (bleController.IsFirmwareUpdating()) {
  xTimerStart(timeoutTimer, 0);
}

The image-size write then calls DfuImage::Erase(), which erases the whole image slot a sector at a time and takes tens of seconds. That timer runs on the FreeRTOS timer task, so it fires part-way through the erase and calls Reset(), which zeroes applicationSize.

The erase then completes and the handler carries on regardless — it sets state = Init and reports success. ReceiveFirmwareImage later calls dfuImage.Init(20, 0, crc), so IsComplete() (totalWriteIndex == totalSize, now 0 == 0) is already true when the first data packet arrives. The watch tells the host the entire image has been received, emits no packet-receipt notifications, writes nothing to flash, and validation CRCs zero bytes.

What it looks like from the host

  • 10 03 01 ("whole image received") arrives a fraction of a second after Begin DFU — no real transfer can be that fast
  • zero 11 … packet-receipt notifications for the entire "transfer"
  • validation fails, and nothing was written

There is no indication that the erase timed out, which makes this very hard to attribute. It cost me a couple of evenings before I instrumented the watch.

Recovery mode is unaffected: its erase finishes inside the 10 s window. That is why OTA can fail consistently from the running firmware while succeeding from recovery — which reads like a host or radio problem rather than a firmware one.

The fix

Stop the timer around the erase and restart it afterwards. Three lines.

Testing

Tested on a PineTime (bootloader 1.0.1, InfiniTime 1.16.0). Before this change, every OTA from the running firmware failed exactly as described. After it, updates complete and validate normally, and recovery mode is no longer needed to flash — verified across several successful ~390 kB updates.

DfuService::OnServiceData arms the 10s one-shot inactivity timer before
dispatching each access. The image-size write then calls DfuImage::Erase(),
which erases the whole image slot a sector at a time and takes tens of
seconds. That timer runs on the FreeRTOS timer task, so it fires part-way
through the erase and calls Reset(), zeroing applicationSize.

The erase then finishes and the handler carries on regardless: it sets
state = Init and reports success. ReceiveFirmwareImage later calls
dfuImage.Init(20, 0, crc), so IsComplete() -- totalWriteIndex == totalSize,
now 0 == 0 -- is already true when the first data packet arrives. The watch
tells the host the entire image has been received, emits no packet-receipt
notifications, writes nothing to flash, and validation CRCs zero bytes.

From the host side this looks like an instant "whole image received" reply
a fraction of a second after Begin DFU, followed by a validation failure -
with no indication that the erase timed out. Recovery mode is unaffected
because its erase is fast enough to finish inside the 10s window, which is
why OTA can fail from the running firmware while succeeding from recovery.

Stop the timer around the erase and restart it afterwards.

Tested on a PineTime: before this change every OTA update from the running
firmware failed as described; after it, updates complete and validate
normally without needing recovery mode.
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Build size and comparison to main:

Section Size Difference
text 385264B 32B
data 944B 0B
bss 22640B 0B

Run in InfiniEmu

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant