Files
bitchat/bitchatTests/Services/BLEOutboundLinkPlannerTests.swift
T
jackandGitHub 820c933958 Harden PTT audio and the 1.7.1 release (#1423)
Centralize PTT and voice audio-session ownership, harden courier/bridge/outbox delivery and recovery, correct location and delivery-state races, add privacy/release metadata, and ship reproducible universal Arti slices with Release CI coverage.

Validated by the full iOS suite, repeated audio/fragment/performance regressions, BitFoundation tests, strict lint and dead-code analysis, universal iOS Release builds, and iOS/macOS archives.
2026-07-10 14:04:09 +02:00

228 lines
7.9 KiB
Swift

import BitFoundation
import Foundation
import Testing
@testable import bitchat
struct BLEOutboundLinkPlannerTests {
@Test
func recipientIDSuppliesDirectedHintAndUsesAllAvailableLinks() throws {
let recipient = PeerID(str: "1122334455667788")
let packet = makePacket(type: .noiseEncrypted, recipient: recipient)
let plan = BLEOutboundLinkPlanner.plan(
packet: packet,
dataCount: 32,
peripheralIDs: ["p1", "p2"],
peripheralWriteLimits: [128, 128],
centralIDs: ["c1"],
centralNotifyLimits: [128],
ingressRecord: nil,
excludedLinks: [],
directedOnlyPeer: nil
)
#expect(plan.directedPeerHint == recipient)
#expect(plan.fragmentChunkSize == nil)
#expect(plan.selectedLinks.peripheralIDs == Set(["p1", "p2"]))
#expect(plan.selectedLinks.centralIDs == Set(["c1"]))
#expect(!plan.shouldSpoolDirectedPacket)
}
@Test
func oversizedNonFragmentPacketPlansFragmentationFromSmallestLinkLimit() {
let packet = makePacket(type: .message)
let smallestLimit = 96
let plan = BLEOutboundLinkPlanner.plan(
packet: packet,
dataCount: 160,
peripheralIDs: ["p1"],
peripheralWriteLimits: [128],
centralIDs: ["c1"],
centralNotifyLimits: [smallestLimit],
ingressRecord: nil,
excludedLinks: [],
directedOnlyPeer: nil
)
#expect(plan.fragmentChunkSize == BLEOutboundPacketPolicy.fragmentChunkSize(forLinkLimit: smallestLimit))
#expect(plan.selectedLinks.peripheralIDs == Set(["p1"]))
#expect(plan.selectedLinks.centralIDs == Set(["c1"]))
#expect(!plan.shouldSpoolDirectedPacket)
}
@Test
func oversizedDirectedCourierDoesNotUseUnrelatedPeersMTUAsHandoffSuccess() {
let recipient = PeerID(str: "1122334455667788")
let unrelated = PeerID(str: "8877665544332211")
let packet = makePacket(type: .courierEnvelope, recipient: recipient)
let plan = BLEOutboundLinkPlanner.plan(
packet: packet,
dataCount: 512,
peripheralIDs: ["unrelated-link"],
peripheralWriteLimits: [64],
centralIDs: [],
centralNotifyLimits: [],
ingressRecord: nil,
excludedLinks: [],
peripheralPeerBindings: ["unrelated-link": unrelated],
directedOnlyPeer: recipient,
requireDirectPeerLink: true
)
#expect(plan.directedPeerHint == recipient)
#expect(plan.fragmentChunkSize == nil)
#expect(plan.selectedLinks.peripheralIDs.isEmpty)
#expect(plan.selectedLinks.centralIDs.isEmpty)
#expect(!plan.shouldSpoolDirectedPacket)
}
@Test
func fragmentPacketsBypassFragmentationPlanning() {
let packet = makePacket(type: .fragment)
let plan = BLEOutboundLinkPlanner.plan(
packet: packet,
dataCount: 512,
peripheralIDs: ["p1"],
peripheralWriteLimits: [64],
centralIDs: ["c1"],
centralNotifyLimits: [64],
ingressRecord: nil,
excludedLinks: [],
directedOnlyPeer: nil
)
#expect(plan.fragmentChunkSize == nil)
#expect(plan.selectedLinks.peripheralIDs == Set(["p1"]))
#expect(plan.selectedLinks.centralIDs == Set(["c1"]))
}
@Test
func directedEncryptedPacketSpoolsWhenNoLinksAreAvailable() {
let recipient = PeerID(str: "1122334455667788")
let packet = makePacket(type: .noiseEncrypted, recipient: recipient)
let plan = BLEOutboundLinkPlanner.plan(
packet: packet,
dataCount: 32,
peripheralIDs: [],
peripheralWriteLimits: [],
centralIDs: [],
centralNotifyLimits: [],
ingressRecord: nil,
excludedLinks: [],
directedOnlyPeer: recipient
)
#expect(plan.directedPeerHint == recipient)
#expect(plan.shouldSpoolDirectedPacket)
}
@Test
func bridgeCourierPacketDoesNotTurnProcessLocalSpoolIntoHandoffSuccess() {
let recipient = PeerID(str: "1122334455667788")
let packet = makePacket(type: .courierEnvelope, recipient: recipient)
let plan = BLEOutboundLinkPlanner.plan(
packet: packet,
dataCount: 32,
peripheralIDs: [],
peripheralWriteLimits: [],
centralIDs: [],
centralNotifyLimits: [],
ingressRecord: nil,
excludedLinks: [],
directedOnlyPeer: recipient
)
#expect(plan.selectedLinks.peripheralIDs.isEmpty)
#expect(plan.selectedLinks.centralIDs.isEmpty)
#expect(!plan.shouldSpoolDirectedPacket)
}
@Test
func publicBroadcastDoesNotSpoolWhenNoLinksAreAvailable() {
let packet = makePacket(type: .message)
let plan = BLEOutboundLinkPlanner.plan(
packet: packet,
dataCount: 32,
peripheralIDs: [],
peripheralWriteLimits: [],
centralIDs: [],
centralNotifyLimits: [],
ingressRecord: nil,
excludedLinks: [],
directedOnlyPeer: nil
)
#expect(plan.directedPeerHint == nil)
#expect(!plan.shouldSpoolDirectedPacket)
}
@Test
func directAnnounceBypassesDuplicateLinkCollapseButRelayedAnnounceDoesNot() {
let peer = PeerID(str: "1122334455667788")
let bindings: [String: PeerID] = ["p1": peer, "p2": peer]
let direct = BLEOutboundLinkPlanner.plan(
packet: makePacket(type: .announce),
dataCount: 32,
peripheralIDs: ["p1", "p2"],
peripheralWriteLimits: [128, 128],
centralIDs: [],
centralNotifyLimits: [],
ingressRecord: nil,
excludedLinks: [],
peripheralPeerBindings: bindings,
preferredPeripheralPerPeer: [peer: "p1"],
directedOnlyPeer: nil
)
// A direct announce is the link-binding packet: it must reach every
// live link, including a peer's duplicate connections.
#expect(direct.selectedLinks.peripheralIDs == Set(["p1", "p2"]))
let relayed = BLEOutboundLinkPlanner.plan(
packet: makePacket(type: .announce, ttl: TransportConfig.messageTTLDefault - 1),
dataCount: 32,
peripheralIDs: ["p1", "p2"],
peripheralWriteLimits: [128, 128],
centralIDs: [],
centralNotifyLimits: [],
ingressRecord: nil,
excludedLinks: [],
peripheralPeerBindings: bindings,
preferredPeripheralPerPeer: [peer: "p1"],
directedOnlyPeer: nil
)
// Relayed announces keep the per-peer collapse (relay hygiene).
#expect(relayed.selectedLinks.peripheralIDs == Set(["p1"]))
}
@Test
func minimumLinkLimitUsesTheSmallestPresentRoleLimit() {
#expect(BLEOutboundLinkPlanner.minimumLinkLimit(peripheralWriteLimits: [80, 120], centralNotifyLimits: []) == 80)
#expect(BLEOutboundLinkPlanner.minimumLinkLimit(peripheralWriteLimits: [], centralNotifyLimits: [60, 90]) == 60)
#expect(BLEOutboundLinkPlanner.minimumLinkLimit(peripheralWriteLimits: [], centralNotifyLimits: []) == nil)
}
private func makePacket(
type: MessageType,
sender: PeerID = PeerID(str: "8877665544332211"),
recipient: PeerID? = nil,
ttl: UInt8 = TransportConfig.messageTTLDefault
) -> BitchatPacket {
BitchatPacket(
type: type.rawValue,
senderID: Data(hexString: sender.id) ?? Data(),
recipientID: recipient.flatMap { Data(hexString: $0.id) },
timestamp: 1234,
payload: Data([0x01, 0x02]),
signature: nil,
ttl: ttl
)
}
}