mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 02:05:19 +00:00
Originate v2 source routes and wire fragmentIdFilter targeted resync
Part A — source-route origination policy: - Gate route application (BLESourceRouteOriginationPolicy): only packets we author, directed at a single peer, with TTL headroom, whose recipient is not directly connected. Relays no longer attach routes to (and re-sign) packets they merely forward. - Version-gate paths: MeshTopologyTracker records the highest protocol version observed per peer; BFS routes require every intermediate hop and the recipient to be v2-observed, capped at 4 intermediate hops. - Degrade on failure: BLESourceRouteFailureCache marks a routed send that sees no inbound traffic from the recipient within 10s as failed and floods for 60s before retrying routes. Part B — REQUEST_SYNC fragmentIdFilter (TLV 0x06): - Requester: BLEFragmentAssemblyBuffer reports stalled broadcast reassemblies (no new fragment for 5s, retried at most every 10s); the maintenance pass sends a types=fragment REQUEST_SYNC naming the stalled 8-byte fragment stream IDs to each connected peer. - Responder: GossipSyncManager restricts the fragment diff to exactly the named streams, bypassing the since-cursor while the GCS filter still excludes pieces the requester holds; RSR/TTL-0/rate-limit semantics unchanged and REQUEST_SYNC stays link-local. - Bounds: at most 60 IDs per request (60*17-1 = 1019 bytes <= the 1024-byte decoder cap); oversized 0x06 values are ignored, not fatal. Docs: SOURCE_ROUTING.md gains the iOS origination policy (§8); REQUEST_SYNC_MANAGER.md documents 0x05/0x06 as implemented. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -135,6 +135,82 @@ struct BLEFragmentAssemblyBufferTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func stalledBroadcastAssemblyReportsFragmentIDOnceUntilRetryLapses() throws {
|
||||
var buffer = BLEFragmentAssemblyBuffer()
|
||||
let fragmentID = Data((1...8).map { UInt8($0) })
|
||||
let packet = makePacket(payload: makePayload(count: 256))
|
||||
let fragments = try makeFragments(for: packet, chunkSize: 128, fragmentID: fragmentID)
|
||||
let first = try #require(BLEFragmentHeader(packet: fragments[0]))
|
||||
|
||||
let t0 = Date(timeIntervalSince1970: 100)
|
||||
_ = buffer.append(first, maxInFlightAssemblies: 8, now: t0)
|
||||
|
||||
// Not yet stalled.
|
||||
let early = buffer.stalledBroadcastFragmentIDs(stalledAfter: 5, retryAfter: 10, now: t0.addingTimeInterval(4))
|
||||
#expect(early.isEmpty)
|
||||
|
||||
// Stalled: reported once, big-endian stream ID.
|
||||
let stalled = buffer.stalledBroadcastFragmentIDs(stalledAfter: 5, retryAfter: 10, now: t0.addingTimeInterval(6))
|
||||
#expect(stalled == [fragmentID])
|
||||
|
||||
// Within the retry window: not re-reported.
|
||||
let repeated = buffer.stalledBroadcastFragmentIDs(stalledAfter: 5, retryAfter: 10, now: t0.addingTimeInterval(8))
|
||||
#expect(repeated.isEmpty)
|
||||
|
||||
// After the retry window it is requested again.
|
||||
let retried = buffer.stalledBroadcastFragmentIDs(stalledAfter: 5, retryAfter: 10, now: t0.addingTimeInterval(17))
|
||||
#expect(retried == [fragmentID])
|
||||
}
|
||||
|
||||
@Test
|
||||
func newFragmentResetsStallClockAndCompletionStopsRequests() throws {
|
||||
var buffer = BLEFragmentAssemblyBuffer()
|
||||
let fragmentID = Data((10...17).map { UInt8($0) })
|
||||
let packet = makePacket(payload: makePayload(count: 384))
|
||||
let fragments = try makeFragments(for: packet, chunkSize: 128, fragmentID: fragmentID)
|
||||
let headers = try fragments.map { try #require(BLEFragmentHeader(packet: $0)) }
|
||||
#expect(headers.count >= 3)
|
||||
|
||||
let t0 = Date(timeIntervalSince1970: 100)
|
||||
_ = buffer.append(headers[0], maxInFlightAssemblies: 8, now: t0)
|
||||
// A fragment arriving at t0+4 resets the stall clock.
|
||||
_ = buffer.append(headers[1], maxInFlightAssemblies: 8, now: t0.addingTimeInterval(4))
|
||||
let afterProgress = buffer.stalledBroadcastFragmentIDs(stalledAfter: 5, retryAfter: 10, now: t0.addingTimeInterval(6))
|
||||
#expect(afterProgress.isEmpty)
|
||||
|
||||
// Completion removes the assembly entirely.
|
||||
var result: BLEFragmentAssemblyBuffer.AppendResult?
|
||||
for header in headers.dropFirst(2) {
|
||||
result = buffer.append(header, maxInFlightAssemblies: 8, now: t0.addingTimeInterval(5))
|
||||
}
|
||||
guard case .complete = result else {
|
||||
Issue.record("Expected assembly to complete")
|
||||
return
|
||||
}
|
||||
let afterCompletion = buffer.stalledBroadcastFragmentIDs(stalledAfter: 5, retryAfter: 10, now: t0.addingTimeInterval(60))
|
||||
#expect(afterCompletion.isEmpty)
|
||||
}
|
||||
|
||||
@Test
|
||||
func directedAssembliesAreNeverReportedAsStalled() throws {
|
||||
var buffer = BLEFragmentAssemblyBuffer()
|
||||
let fragment = makeFragmentPacket(
|
||||
fragmentID: Data(repeating: 0x0A, count: 8),
|
||||
index: 0,
|
||||
total: 2,
|
||||
originalType: MessageType.message.rawValue,
|
||||
fragmentData: Data([0x01]),
|
||||
recipientID: Data(hexString: "0102030405060708")
|
||||
)
|
||||
let header = try #require(BLEFragmentHeader(packet: fragment))
|
||||
|
||||
let t0 = Date(timeIntervalSince1970: 100)
|
||||
_ = buffer.append(header, maxInFlightAssemblies: 8, now: t0)
|
||||
let stalled = buffer.stalledBroadcastFragmentIDs(stalledAfter: 5, retryAfter: 10, now: t0.addingTimeInterval(60))
|
||||
#expect(stalled.isEmpty)
|
||||
}
|
||||
|
||||
private func makePacket(payload: Data, timestamp: UInt64 = 0x0102030405) -> BitchatPacket {
|
||||
BitchatPacket(
|
||||
type: MessageType.message.rawValue,
|
||||
|
||||
Reference in New Issue
Block a user