diff --git a/bitchat/Services/BLE/BLEInboundWriteBuffer.swift b/bitchat/Services/BLE/BLEInboundWriteBuffer.swift index 8174f76c..1a305405 100644 --- a/bitchat/Services/BLE/BLEInboundWriteBuffer.swift +++ b/bitchat/Services/BLE/BLEInboundWriteBuffer.swift @@ -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. diff --git a/bitchatTests/Services/BLEInboundWriteBufferTests.swift b/bitchatTests/Services/BLEInboundWriteBufferTests.swift index 3e1fd44b..8a8ae55b 100644 --- a/bitchatTests/Services/BLEInboundWriteBufferTests.swift +++ b/bitchatTests/Services/BLEInboundWriteBufferTests.swift @@ -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()