mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 00:05:18 +00:00
Fix BLE stream crashes and gossip sync races (#663)
* Handle long BLE packets safely * Keep BLE stream aligned after partial drops --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import XCTest
|
||||
@testable import bitchat
|
||||
|
||||
final class GCSFilterTests: XCTestCase {
|
||||
func testBuildFilterWithDuplicateIdsProducesStableEncoding() {
|
||||
let id = Data(repeating: 0xAB, count: 16)
|
||||
let ids = Array(repeating: id, count: 64)
|
||||
|
||||
let params = GCSFilter.buildFilter(ids: ids, maxBytes: 128, targetFpr: 0.01)
|
||||
XCTAssertGreaterThanOrEqual(params.m, 1)
|
||||
|
||||
let decoded = GCSFilter.decodeToSortedSet(p: params.p, m: params.m, data: params.data)
|
||||
XCTAssertLessThanOrEqual(decoded.count, 1)
|
||||
}
|
||||
|
||||
func testBucketAvoidsZeroCandidate() {
|
||||
let id = Data(repeating: 0x01, count: 16)
|
||||
let bucket = GCSFilter.bucket(for: id, modulus: 2)
|
||||
XCTAssertNotEqual(bucket, 0)
|
||||
XCTAssertLessThan(bucket, 2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import Foundation
|
||||
import XCTest
|
||||
@testable import bitchat
|
||||
|
||||
final class GossipSyncManagerTests: XCTestCase {
|
||||
func testConcurrentPacketIntakeAndSyncRequest() {
|
||||
let manager = GossipSyncManager(myPeerID: "0102030405060708")
|
||||
let delegate = RecordingDelegate()
|
||||
let sendExpectation = expectation(description: "sync request sent")
|
||||
delegate.onSend = { sendExpectation.fulfill() }
|
||||
manager.delegate = delegate
|
||||
|
||||
let iterations = 200
|
||||
let group = DispatchGroup()
|
||||
|
||||
for i in 0..<iterations {
|
||||
group.enter()
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
let packet = BitchatPacket(
|
||||
type: MessageType.message.rawValue,
|
||||
senderID: Data(hexString: "1122334455667788") ?? Data(),
|
||||
recipientID: nil,
|
||||
timestamp: 1_000_000 + UInt64(i),
|
||||
payload: Data([UInt8(truncatingIfNeeded: i)]),
|
||||
signature: nil,
|
||||
ttl: 1
|
||||
)
|
||||
manager.onPublicPacketSeen(packet)
|
||||
Thread.sleep(forTimeInterval: 0.001)
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.002) {
|
||||
manager.scheduleInitialSyncToPeer("FFFFFFFFFFFFFFFF", delaySeconds: 0.0)
|
||||
}
|
||||
|
||||
group.wait()
|
||||
wait(for: [sendExpectation], timeout: 2.0)
|
||||
|
||||
guard let lastPacket = delegate.lastPacket else {
|
||||
XCTFail("Expected sync packet to be sent")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(lastPacket.type, MessageType.requestSync.rawValue)
|
||||
XCTAssertNotNil(RequestSyncPacket.decode(from: lastPacket.payload))
|
||||
}
|
||||
}
|
||||
|
||||
private final class RecordingDelegate: GossipSyncManager.Delegate {
|
||||
var onSend: (() -> Void)?
|
||||
private(set) var lastPacket: BitchatPacket?
|
||||
private let lock = NSLock()
|
||||
|
||||
func sendPacket(_ packet: BitchatPacket) {
|
||||
lock.lock()
|
||||
lastPacket = packet
|
||||
lock.unlock()
|
||||
onSend?()
|
||||
}
|
||||
|
||||
func sendPacket(to peerID: String, packet: BitchatPacket) {
|
||||
sendPacket(packet)
|
||||
}
|
||||
|
||||
func signPacketForBroadcast(_ packet: BitchatPacket) -> BitchatPacket {
|
||||
packet
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import XCTest
|
||||
@testable import bitchat
|
||||
|
||||
final class NotificationStreamAssemblerTests: XCTestCase {
|
||||
private func makePacket(timestamp: UInt64 = 0x0102030405) -> BitchatPacket {
|
||||
let sender = Data([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77])
|
||||
return BitchatPacket(
|
||||
type: MessageType.message.rawValue,
|
||||
senderID: sender,
|
||||
recipientID: nil,
|
||||
timestamp: timestamp,
|
||||
payload: Data([0xDE, 0xAD, 0xBE, 0xEF]),
|
||||
signature: nil,
|
||||
ttl: 3
|
||||
)
|
||||
}
|
||||
|
||||
func testAssemblesSingleFrameAcrossChunks() {
|
||||
var assembler = NotificationStreamAssembler()
|
||||
let packet = makePacket()
|
||||
guard let frame = packet.toBinaryData(padding: false) else {
|
||||
return XCTFail("Failed to encode packet")
|
||||
}
|
||||
XCTAssertNotNil(BinaryProtocol.decode(frame))
|
||||
let payloadLen = (Int(frame[12]) << 8) | Int(frame[13])
|
||||
XCTAssertEqual(payloadLen, packet.payload.count)
|
||||
|
||||
let splitIndex = min(20, max(1, frame.count / 2))
|
||||
let first = frame.prefix(splitIndex)
|
||||
let second = frame.suffix(from: splitIndex)
|
||||
XCTAssertEqual(first.count + second.count, frame.count)
|
||||
|
||||
var result = assembler.append(first)
|
||||
XCTAssertTrue(result.frames.isEmpty)
|
||||
XCTAssertTrue(result.droppedPrefixes.isEmpty)
|
||||
XCTAssertFalse(result.reset)
|
||||
|
||||
result = assembler.append(second)
|
||||
XCTAssertEqual(result.frames.count, 1)
|
||||
XCTAssertTrue(result.droppedPrefixes.isEmpty)
|
||||
XCTAssertFalse(result.reset)
|
||||
|
||||
guard let frameData = result.frames.first else {
|
||||
return XCTFail("Missing frame data")
|
||||
}
|
||||
if frameData.count != frame.count {
|
||||
XCTFail("Frame size mismatch: expected \(frame.count) got \(frameData.count)\nframe=\(Array(frame))\nassembled=\(Array(frameData))")
|
||||
return
|
||||
}
|
||||
guard let decoded = BinaryProtocol.decode(frameData) else {
|
||||
return XCTFail("Failed to decode frame")
|
||||
}
|
||||
XCTAssertEqual(decoded.type, packet.type)
|
||||
XCTAssertEqual(decoded.payload, packet.payload)
|
||||
XCTAssertEqual(decoded.senderID, packet.senderID)
|
||||
XCTAssertEqual(decoded.timestamp, packet.timestamp)
|
||||
|
||||
var directAssembler = NotificationStreamAssembler()
|
||||
let directResult = directAssembler.append(frame)
|
||||
XCTAssertEqual(directResult.frames.first?.count, frame.count)
|
||||
}
|
||||
|
||||
func testAssemblesMultipleFramesSequentially() {
|
||||
var assembler = NotificationStreamAssembler()
|
||||
let packet1 = makePacket(timestamp: 0xABC)
|
||||
let packet2 = makePacket(timestamp: 0xDEF)
|
||||
|
||||
guard let frame1 = packet1.toBinaryData(padding: false),
|
||||
let frame2 = packet2.toBinaryData(padding: false) else {
|
||||
return XCTFail("Failed to encode packets")
|
||||
}
|
||||
|
||||
var combined = Data()
|
||||
combined.append(frame1)
|
||||
combined.append(frame2)
|
||||
let firstChunk = combined.prefix(20)
|
||||
let secondChunk = combined.suffix(from: 20)
|
||||
|
||||
var result = assembler.append(firstChunk)
|
||||
XCTAssertTrue(result.frames.isEmpty)
|
||||
|
||||
result = assembler.append(secondChunk)
|
||||
XCTAssertEqual(result.frames.count, 2)
|
||||
guard let decoded1 = BinaryProtocol.decode(result.frames[0]),
|
||||
let decoded2 = BinaryProtocol.decode(result.frames[1]) else {
|
||||
return XCTFail("Failed to decode frames")
|
||||
}
|
||||
XCTAssertEqual(decoded1.timestamp, packet1.timestamp)
|
||||
XCTAssertEqual(decoded2.timestamp, packet2.timestamp)
|
||||
}
|
||||
|
||||
func testDropsInvalidPrefixByte() {
|
||||
var assembler = NotificationStreamAssembler()
|
||||
let packet = makePacket(timestamp: 0xF00)
|
||||
guard let frame = packet.toBinaryData(padding: false) else {
|
||||
return XCTFail("Failed to encode packet")
|
||||
}
|
||||
var noisyFrame = Data([0x00])
|
||||
noisyFrame.append(frame)
|
||||
|
||||
let result = assembler.append(noisyFrame)
|
||||
XCTAssertEqual(result.droppedPrefixes, [0x00])
|
||||
XCTAssertEqual(result.frames.count, 1)
|
||||
XCTAssertFalse(result.reset)
|
||||
|
||||
guard let decoded = BinaryProtocol.decode(result.frames[0]) else {
|
||||
return XCTFail("Failed to decode frame after drop")
|
||||
}
|
||||
XCTAssertEqual(decoded.timestamp, packet.timestamp)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user