fix(ble): treat offset-0 write on stale buffer as restart

Per review: the central role writes with .withoutResponse, so legit
inbound writes always land at offset 0. With a stale partial frame
pending, the previous validation rejected the fresh write as invalid
and dropped its bytes; clear the buffer and apply instead.

Non-monotonic test now uses [8, 4] since offset 0 restarts; adds a
test covering the stale-buffer restart path.
This commit is contained in:
kappaone 2026-08-23 18:20:01 +03:00
parent fd131a96a7
commit b74e92958d
No known key found for this signature in database
2 changed files with 40 additions and 2 deletions

View File

@ -41,6 +41,14 @@ struct BLEInboundWriteBuffer {
for chunk in chunks where !chunk.data.isEmpty {
offsets.append(chunk.offset)
// `.withoutResponse` writes always land at offset 0, so an
// offset-0 chunk arriving on a non-empty buffer is a fresh frame
// replacing stale partial bytes, not an overlap.
if chunk.offset == 0, !combined.isEmpty {
combined.removeAll()
lastEnd = 0
}
// Reject malformed writes before touching the buffer: a negative
// offset traps `Data.replaceSubrange`, and non-monotonic or
// overlapping offsets corrupt previously written bytes.

View File

@ -190,19 +190,49 @@ struct BLEInboundWriteBufferTests {
let result = buffer.append(
chunks: [
BLEInboundWriteChunk(offset: 8, data: Data(repeating: 0x01, count: 4)),
BLEInboundWriteChunk(offset: 0, data: Data(repeating: 0x02, count: 4))
BLEInboundWriteChunk(offset: 4, data: Data(repeating: 0x02, count: 4))
],
for: "central-1",
capBytes: 1024
)
if case let .invalid(metadata) = result {
#expect(metadata.offsets == [8, 0])
#expect(metadata.offsets == [8, 4])
} else {
Issue.record("Expected non-monotonic chunks to be rejected")
}
}
@Test
func appendTreatsOffsetZeroOnStaleBufferAsRestart() throws {
var buffer = BLEInboundWriteBuffer()
let packet = makePacket(timestamp: 0x456)
let frame = try #require(packet.toBinaryData(padding: false))
let splitIndex = max(1, frame.count / 2)
// Stale partial frame left pending from an earlier write.
_ = buffer.append(
chunks: [BLEInboundWriteChunk(offset: 0, data: frame.prefix(splitIndex))],
for: "central-1",
capBytes: 1024
)
// A fresh `.withoutResponse` write lands at offset 0 and replaces it
// instead of being rejected as an overlap with the stale bytes.
let result = buffer.append(
chunks: [BLEInboundWriteChunk(offset: 0, data: frame)],
for: "central-1",
capBytes: 1024
)
if case let .decoded(decoded, metadata) = result {
#expect(decoded.timestamp == packet.timestamp)
#expect(metadata.offsets == [0])
} else {
Issue.record("Expected offset-0 write to restart the stale buffer")
}
}
@Test
func appendRejectsOverlapWithPreviouslyBufferedBytes() {
var buffer = BLEInboundWriteBuffer()