Skip to content

Fix file-buffer exact-fit guard (off-by-one) to prevent silent data loss - #248

Merged
PaulZC merged 1 commit into
sparkfun:release_candidatefrom
94xhn:fix-filebuffer-exact-fit-guard
Jul 15, 2026
Merged

PaulZC merged 1 commit into
sparkfun:release_candidatefrom
94xhn:fix-filebuffer-exact-fit-guard

Conversation

@94xhn

@94xhn 94xhn commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

storePacket() and storeFileBytes() guard against overfilling the UBX file buffer with:

if (totalLength > fileBufferSpaceAvailable())   // storePacket
if (numBytes > fileBufferSpaceAvailable())      // storeFileBytes

Because the strict > allows a write of exactly fileBufferSpaceAvailable() bytes to proceed, such a write can bring fileBufferHead back around to equal fileBufferTail. Elsewhere in the class, fileBufferHead == fileBufferTail is the sentinel used to mean "buffer empty" (see fileBufferSpaceUsed()). So a write that exactly fills the buffer makes fileBufferSpaceUsed() report 0 and the just-written bytes become unreachable to extractFileBufferData() -- a silent data loss with no error indication.

This is reachable via the live NMEA/UBX file-buffer logging path: storeFileBytes(&incoming, 1) is called per incoming NMEA byte, and storePacket() is invoked from every dispatched UBX message handler. Any real logging session where the buffer is ever fully drained down to head == tail (routine once the pointers have wrapped once) and then receives a write of exactly the remaining free space will silently lose that write's data.

Fix

Change both guards from > to >=. This reserves fileBufferSize - 1 as the buffer's true usable capacity, which is the standard fix for ring buffers that use head == tail to mean "empty" -- a write can then never exactly fill the buffer and recreate the ambiguity.

Testing

Verified with a standalone extraction of the exact ring-buffer logic (compiled with g++ -Wall -Wextra):

  • Reproduced the loss: drain the buffer to head == tail == 7, then write exactly fileBufferSize (10) bytes -- the old guard (10 > 10 is false) wrongly accepts the write, and afterwards fileBufferSpaceUsed() reports 0 even though 10 real bytes are sitting in the buffer; extractFileBufferData() returns 0 bytes.
  • With the >= fix, that same write is correctly rejected, all partial-write/read/wrap-around behavior is unaffected, and a 256-byte/86-iteration multi-wrap sanity check through a 16-byte buffer showed zero loss or corruption.

Change is 2 lines, minimal per CONTRIBUTING.md guidance, and targets release_candidate as requested there.

storePacket() and storeFileBytes() used a strict '>' when checking
whether a write fits in the file buffer's available space. This let
a write of exactly fileBufferSpaceAvailable() bytes succeed, which
can bring fileBufferHead back around to equal fileBufferTail. Since
head == tail is used elsewhere to mean 'buffer empty', the just
written bytes become invisible to fileBufferSpaceUsed() and are
silently lost from extractFileBufferData().

Changing both guards to '>=' reserves fileBufferSize - 1 as the
buffer's usable capacity (the standard fix for ring buffers that use
head == tail to mean empty), so a write can never exactly fill the
buffer and recreate the ambiguity.
@PaulZC

PaulZC commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Hello @94xhn ,

Thank you for the contribution and correction.

The GNSS_v3 library has the same flaw here and here. Would you like to submit the same changes there?

Best wishes,
Paul

@PaulZC
PaulZC merged commit 6f6905a into sparkfun:release_candidate Jul 15, 2026
9 of 10 checks passed
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.

2 participants