mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 19:05:20 +00:00
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>
77 lines
3.1 KiB
Swift
77 lines
3.1 KiB
Swift
//
|
|
// RequestSyncPacketFragmentFilterTests.swift
|
|
// bitchatTests
|
|
//
|
|
// This is free and unencumbered software released into the public domain.
|
|
// For more information, see <https://unlicense.org>
|
|
//
|
|
|
|
import Testing
|
|
import Foundation
|
|
import BitFoundation
|
|
@testable import bitchat
|
|
|
|
struct RequestSyncPacketFragmentFilterTests {
|
|
|
|
@Test func fragmentIdFilterRoundTripsThroughWireEncoding() throws {
|
|
let id1 = try #require(Data(hexString: "00112233445566aa"))
|
|
let id2 = try #require(Data(hexString: "ffeeddccbbaa9988"))
|
|
let filter = try #require(RequestSyncPacket.encodeFragmentIdFilter([id1, id2]))
|
|
|
|
let packet = RequestSyncPacket(p: 7, m: 128, data: Data([0x01]), types: .fragment, fragmentIdFilter: filter)
|
|
let decoded = try #require(RequestSyncPacket.decode(from: packet.encode()))
|
|
|
|
#expect(decoded.fragmentIdFilter == filter)
|
|
let ids = try #require(RequestSyncPacket.decodeFragmentIdFilter(decoded.fragmentIdFilter))
|
|
#expect(ids == Set([id1, id2]))
|
|
}
|
|
|
|
@Test func encodeCapsFilterAtMaxCountWithinDecoderBudget() throws {
|
|
let ids = (0..<100).map { i -> Data in
|
|
var id = Data(repeating: 0, count: 8)
|
|
id[7] = UInt8(i)
|
|
return id
|
|
}
|
|
let filter = try #require(RequestSyncPacket.encodeFragmentIdFilter(ids))
|
|
|
|
let tokens = filter.split(separator: ",")
|
|
#expect(tokens.count == RequestSyncPacket.maxFragmentIdFilterCount)
|
|
// 60 IDs * 17 bytes ("<16 hex>,") - 1 = 1019 ≤ the 1024-byte cap.
|
|
#expect(filter.utf8.count == 1019)
|
|
#expect(filter.utf8.count <= 1024)
|
|
}
|
|
|
|
@Test func encodeDropsMalformedIDs() throws {
|
|
let good = try #require(Data(hexString: "0011223344556677"))
|
|
let short = Data([0x01, 0x02])
|
|
let filter = try #require(RequestSyncPacket.encodeFragmentIdFilter([short, good]))
|
|
#expect(filter == good.hexEncodedString())
|
|
#expect(RequestSyncPacket.encodeFragmentIdFilter([short]) == nil)
|
|
#expect(RequestSyncPacket.encodeFragmentIdFilter([]) == nil)
|
|
}
|
|
|
|
@Test func decodeIgnoresMalformedTokens() throws {
|
|
let good = try #require(Data(hexString: "0011223344556677"))
|
|
let ids = try #require(
|
|
RequestSyncPacket.decodeFragmentIdFilter("zzzz,0011,0011223344556677,")
|
|
)
|
|
#expect(ids == Set([good]))
|
|
#expect(RequestSyncPacket.decodeFragmentIdFilter(nil) == nil)
|
|
#expect(RequestSyncPacket.decodeFragmentIdFilter("not-hex") == nil)
|
|
}
|
|
|
|
@Test func decoderIgnoresOversizedFilterValue() throws {
|
|
// Hand-roll a payload whose 0x06 TLV exceeds the acceptance cap; the
|
|
// request must still decode, with the filter dropped.
|
|
var payload = RequestSyncPacket(p: 7, m: 128, data: Data([0x01])).encode()
|
|
let oversized = Data(repeating: UInt8(ascii: "a"), count: 1025)
|
|
payload.append(0x06)
|
|
payload.append(UInt8((oversized.count >> 8) & 0xFF))
|
|
payload.append(UInt8(oversized.count & 0xFF))
|
|
payload.append(oversized)
|
|
|
|
let decoded = try #require(RequestSyncPacket.decode(from: payload))
|
|
#expect(decoded.fragmentIdFilter == nil)
|
|
}
|
|
}
|