mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 01:45:20 +00:00
Fix courier handoff verification and directed sends
This commit is contained in:
@@ -62,6 +62,13 @@ struct BLEAnnounceHandlerEnvironment {
|
||||
/// Orchestrates inbound announce packets: preflight validation, signature
|
||||
/// trust, registry/topology updates, identity persistence, UI notification,
|
||||
/// gossip tracking, and the reciprocal announce response.
|
||||
struct BLEAnnounceHandlingResult {
|
||||
let peerID: PeerID
|
||||
let announcement: AnnouncementPacket
|
||||
let isDirectAnnounce: Bool
|
||||
let isVerified: Bool
|
||||
}
|
||||
|
||||
final class BLEAnnounceHandler {
|
||||
private let environment: BLEAnnounceHandlerEnvironment
|
||||
|
||||
@@ -69,7 +76,8 @@ final class BLEAnnounceHandler {
|
||||
self.environment = environment
|
||||
}
|
||||
|
||||
func handle(_ packet: BitchatPacket, from peerID: PeerID) {
|
||||
@discardableResult
|
||||
func handle(_ packet: BitchatPacket, from peerID: PeerID) -> BLEAnnounceHandlingResult? {
|
||||
let env = environment
|
||||
let now = env.now()
|
||||
let preflight = BLEAnnouncePreflightPolicy.evaluate(
|
||||
@@ -85,15 +93,15 @@ final class BLEAnnounceHandler {
|
||||
announcement = acceptance.announcement
|
||||
case .reject(.malformed):
|
||||
SecureLogger.error("❌ Failed to decode announce packet from \(peerID.id.prefix(8))…", category: .session)
|
||||
return
|
||||
return nil
|
||||
case .reject(.senderMismatch(let derivedFromKey)):
|
||||
SecureLogger.warning("⚠️ Announce sender mismatch: derived \(derivedFromKey.id.prefix(8))… vs packet \(peerID.id.prefix(8))…", category: .security)
|
||||
return
|
||||
return nil
|
||||
case .reject(.selfAnnounce):
|
||||
return
|
||||
return nil
|
||||
case .reject(.stale(let ageSeconds)):
|
||||
SecureLogger.debug("⏰ Ignoring stale announce from \(peerID.id.prefix(8))… (age: \(ageSeconds)s)", category: .session)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// Suppress announce logs to reduce noise
|
||||
@@ -210,5 +218,12 @@ final class BLEAnnounceHandler {
|
||||
let delay = Double.random(in: 0.3...0.6)
|
||||
env.scheduleAfterglow(delay)
|
||||
}
|
||||
|
||||
return BLEAnnounceHandlingResult(
|
||||
peerID: peerID,
|
||||
announcement: announcement,
|
||||
isDirectAnnounce: isDirectAnnounce,
|
||||
isVerified: verifiedAnnounce
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,13 +19,35 @@ enum BLEFanoutSelector {
|
||||
packetType: UInt8,
|
||||
messageID: String
|
||||
) -> BLEFanoutSelection {
|
||||
let rawAllowed = allowedLinks(
|
||||
peripheralIDs: peripheralIDs,
|
||||
centralIDs: centralIDs,
|
||||
ingressLink: ingressLink,
|
||||
excludedLinks: excludedLinks
|
||||
)
|
||||
|
||||
if let directedPeerHint,
|
||||
let directedSelection = directLinks(
|
||||
to: directedPeerHint,
|
||||
links: rawAllowed,
|
||||
peripheralPeerBindings: peripheralPeerBindings,
|
||||
centralPeerBindings: centralPeerBindings
|
||||
) {
|
||||
return directedSelection
|
||||
}
|
||||
if let directedPeerHint,
|
||||
hasBoundLink(
|
||||
to: directedPeerHint,
|
||||
peripheralIDs: peripheralIDs,
|
||||
centralIDs: centralIDs,
|
||||
peripheralPeerBindings: peripheralPeerBindings,
|
||||
centralPeerBindings: centralPeerBindings
|
||||
) {
|
||||
return BLEFanoutSelection(peripheralIDs: [], centralIDs: [])
|
||||
}
|
||||
|
||||
let allowed = collapseDuplicateLinksPerPeer(
|
||||
allowedLinks(
|
||||
peripheralIDs: peripheralIDs,
|
||||
centralIDs: centralIDs,
|
||||
ingressLink: ingressLink,
|
||||
excludedLinks: excludedLinks
|
||||
),
|
||||
rawAllowed,
|
||||
peripheralPeerBindings: peripheralPeerBindings,
|
||||
centralPeerBindings: centralPeerBindings
|
||||
)
|
||||
@@ -71,6 +93,42 @@ enum BLEFanoutSelector {
|
||||
return (allowedPeripheralIDs, allowedCentralIDs)
|
||||
}
|
||||
|
||||
private static func directLinks(
|
||||
to peerID: PeerID,
|
||||
links: (peripheralIDs: [String], centralIDs: [String]),
|
||||
peripheralPeerBindings: [String: PeerID],
|
||||
centralPeerBindings: [String: PeerID]
|
||||
) -> BLEFanoutSelection? {
|
||||
let directLinks = collapseDuplicateLinksPerPeer(
|
||||
(
|
||||
peripheralIDs: links.peripheralIDs.filter { peripheralPeerBindings[$0] == peerID },
|
||||
centralIDs: links.centralIDs.filter { centralPeerBindings[$0] == peerID }
|
||||
),
|
||||
peripheralPeerBindings: peripheralPeerBindings,
|
||||
centralPeerBindings: centralPeerBindings
|
||||
)
|
||||
|
||||
guard !directLinks.peripheralIDs.isEmpty || !directLinks.centralIDs.isEmpty else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return BLEFanoutSelection(
|
||||
peripheralIDs: Set(directLinks.peripheralIDs),
|
||||
centralIDs: Set(directLinks.centralIDs)
|
||||
)
|
||||
}
|
||||
|
||||
private static func hasBoundLink(
|
||||
to peerID: PeerID,
|
||||
peripheralIDs: [String],
|
||||
centralIDs: [String],
|
||||
peripheralPeerBindings: [String: PeerID],
|
||||
centralPeerBindings: [String: PeerID]
|
||||
) -> Bool {
|
||||
peripheralIDs.contains { peripheralPeerBindings[$0] == peerID }
|
||||
|| centralIDs.contains { centralPeerBindings[$0] == peerID }
|
||||
}
|
||||
|
||||
// Dual-role pairs hold two live links (we-as-central writing to their
|
||||
// peripheral, and they-as-central subscribed to ours). Sending the same
|
||||
// packet down both doubles airtime for nothing — the receiver's assembler
|
||||
|
||||
@@ -1065,6 +1065,9 @@ final class BLEService: NSObject {
|
||||
|
||||
// Directed send helper (unicast to a specific peerID) without altering packet contents
|
||||
private func sendPacketDirected(_ packet: BitchatPacket, to peerID: PeerID) {
|
||||
#if DEBUG
|
||||
_test_onOutboundPacket?(packet)
|
||||
#endif
|
||||
guard let data = packet.toBinaryData(padding: false) else { return }
|
||||
sendOnAllLinks(packet: packet, data: data, pad: false, directedOnlyPeer: peerID)
|
||||
}
|
||||
@@ -2491,33 +2494,39 @@ extension BLEService {
|
||||
|
||||
/// Seal `content` to the recipient's static key (one-way Noise X) and hand
|
||||
/// the envelope to the given couriers for physical delivery. Returns false
|
||||
/// when no courier is connected; sealing and sending happen asynchronously.
|
||||
/// when no courier is connected, the payload cannot be built, or sealing
|
||||
/// fails; link writes are queued asynchronously after the envelope is ready.
|
||||
func sendCourierMessage(_ content: String, messageID: String, recipientNoiseKey: Data, via couriers: [PeerID]) -> Bool {
|
||||
let connected = couriers.filter { isPeerConnected($0) }
|
||||
guard !connected.isEmpty,
|
||||
let typedPayload = BLENoisePayloadFactory.privateMessage(content: content, messageID: messageID) else {
|
||||
return false
|
||||
}
|
||||
|
||||
let payload: Data
|
||||
do {
|
||||
let now = Date()
|
||||
let sealed = try noiseService.sealCourierPayload(typedPayload, recipientStaticKey: recipientNoiseKey)
|
||||
let envelope = CourierEnvelope(
|
||||
recipientTag: CourierEnvelope.recipientTag(
|
||||
noiseStaticKey: recipientNoiseKey,
|
||||
epochDay: CourierEnvelope.epochDay(for: now)
|
||||
),
|
||||
expiry: UInt64((now.timeIntervalSince1970 + CourierEnvelope.maxLifetimeSeconds) * 1000),
|
||||
ciphertext: sealed
|
||||
)
|
||||
guard let encoded = envelope.encode() else { return false }
|
||||
payload = encoded
|
||||
} catch {
|
||||
SecureLogger.error("Failed to seal courier envelope: \(error)", category: .encryption)
|
||||
return false
|
||||
}
|
||||
|
||||
messageQueue.async { [weak self] in
|
||||
guard let self else { return }
|
||||
do {
|
||||
let now = Date()
|
||||
let sealed = try self.noiseService.sealCourierPayload(typedPayload, recipientStaticKey: recipientNoiseKey)
|
||||
let envelope = CourierEnvelope(
|
||||
recipientTag: CourierEnvelope.recipientTag(
|
||||
noiseStaticKey: recipientNoiseKey,
|
||||
epochDay: CourierEnvelope.epochDay(for: now)
|
||||
),
|
||||
expiry: UInt64((now.timeIntervalSince1970 + CourierEnvelope.maxLifetimeSeconds) * 1000),
|
||||
ciphertext: sealed
|
||||
)
|
||||
guard let payload = envelope.encode() else { return }
|
||||
for courier in connected {
|
||||
SecureLogger.debug("📦 Depositing courier envelope with \(courier.id.prefix(8))… id=\(messageID.prefix(8))…", category: .session)
|
||||
self.broadcastPacket(self.makeCourierPacket(payload, to: courier))
|
||||
}
|
||||
} catch {
|
||||
SecureLogger.error("Failed to seal courier envelope: \(error)", category: .encryption)
|
||||
for courier in connected {
|
||||
SecureLogger.debug("📦 Depositing courier envelope with \(courier.id.prefix(8))… id=\(messageID.prefix(8))…", category: .session)
|
||||
self.sendPacketDirected(self.makeCourierPacket(payload, to: courier), to: courier)
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -2605,7 +2614,7 @@ extension BLEService {
|
||||
SecureLogger.debug("📦 Handing over \(envelopes.count) courier envelope(s) to \(peerID.id.prefix(8))…", category: .session)
|
||||
for envelope in envelopes {
|
||||
guard let payload = envelope.encode() else { continue }
|
||||
broadcastPacket(makeCourierPacket(payload, to: peerID))
|
||||
sendPacketDirected(makeCourierPacket(payload, to: peerID), to: peerID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3159,21 +3168,14 @@ extension BLEService {
|
||||
}
|
||||
|
||||
private func handleAnnounce(_ packet: BitchatPacket, from peerID: PeerID) {
|
||||
announceHandler.handle(packet, from: peerID)
|
||||
let result = announceHandler.handle(packet, from: peerID)
|
||||
|
||||
// Courier handover: an announce is the moment we learn a peer's Noise
|
||||
// static key, so check whether we're carrying mail addressed to them.
|
||||
// Re-run the preflight so handover only follows announces that pass
|
||||
// the key<->peerID binding check (mirrors the handler's own gate).
|
||||
guard !courierStore.isEmpty else { return }
|
||||
if case .accept(let acceptance) = BLEAnnouncePreflightPolicy.evaluate(
|
||||
packet: packet,
|
||||
from: peerID,
|
||||
localPeerID: myPeerID,
|
||||
now: Date()
|
||||
) {
|
||||
deliverCourierMail(to: peerID, noiseKey: acceptance.announcement.noisePublicKey)
|
||||
}
|
||||
guard !courierStore.isEmpty,
|
||||
let result,
|
||||
result.isVerified else { return }
|
||||
deliverCourierMail(to: result.peerID, noiseKey: result.announcement.noisePublicKey)
|
||||
}
|
||||
|
||||
/// Builds the announce handler environment. All queue hops stay here so
|
||||
|
||||
@@ -33,6 +33,11 @@ struct CourierEndToEndTests {
|
||||
lock.lock(); defer { lock.unlock() }
|
||||
return packets.first { $0.type == type.rawValue }
|
||||
}
|
||||
|
||||
func count(ofType type: MessageType) -> Int {
|
||||
lock.lock(); defer { lock.unlock() }
|
||||
return packets.filter { $0.type == type.rawValue }.count
|
||||
}
|
||||
}
|
||||
|
||||
private final class NoiseCaptureDelegate: BitchatDelegate {
|
||||
@@ -139,7 +144,7 @@ struct CourierEndToEndTests {
|
||||
)
|
||||
#expect(announced)
|
||||
let announcePacket = try #require(bobOut.first(ofType: .announce))
|
||||
carol._test_handlePacket(announcePacket, fromPeerID: bob.myPeerID)
|
||||
carol._test_handlePacket(announcePacket, fromPeerID: bob.myPeerID, preseedPeer: false)
|
||||
|
||||
let handedOver = await TestHelpers.waitUntil(
|
||||
{ carolOut.first(ofType: .courierEnvelope) != nil },
|
||||
@@ -167,6 +172,90 @@ struct CourierEndToEndTests {
|
||||
#expect(message.content == "the camp moved north")
|
||||
}
|
||||
|
||||
@Test func unverifiedAnnounceDoesNotTriggerCourierHandover() async throws {
|
||||
let alice = makeService()
|
||||
let carol = makeService()
|
||||
let bob = makeService()
|
||||
carol.courierDepositPolicy = { _ in true }
|
||||
|
||||
let aliceOut = PacketTap()
|
||||
alice._test_onOutboundPacket = aliceOut.record
|
||||
let carolOut = PacketTap()
|
||||
carol._test_onOutboundPacket = carolOut.record
|
||||
let bobOut = PacketTap()
|
||||
bob._test_onOutboundPacket = bobOut.record
|
||||
|
||||
preseedConnectedPeer(carol, in: alice)
|
||||
|
||||
#expect(alice.sendCourierMessage(
|
||||
"hold until verified",
|
||||
messageID: "courier-msg-unverified-announce",
|
||||
recipientNoiseKey: bob.noiseStaticPublicKeyData(),
|
||||
via: [carol.myPeerID]
|
||||
))
|
||||
let deposited = await TestHelpers.waitUntil(
|
||||
{ aliceOut.first(ofType: .courierEnvelope) != nil },
|
||||
timeout: TestConstants.defaultTimeout
|
||||
)
|
||||
#expect(deposited)
|
||||
let depositPacket = try #require(aliceOut.first(ofType: .courierEnvelope))
|
||||
|
||||
carol._test_handlePacket(depositPacket, fromPeerID: alice.myPeerID)
|
||||
let carried = await TestHelpers.waitUntil(
|
||||
{ !carol.courierStore.isEmpty },
|
||||
timeout: TestConstants.defaultTimeout
|
||||
)
|
||||
#expect(carried)
|
||||
|
||||
let forgedAnnounce = try makeUnsignedAnnounce(from: bob)
|
||||
carol._test_handlePacket(forgedAnnounce, fromPeerID: bob.myPeerID, preseedPeer: false)
|
||||
|
||||
let leakedOnUnverifiedAnnounce = await TestHelpers.waitUntil(
|
||||
{ carolOut.count(ofType: .courierEnvelope) > 0 },
|
||||
timeout: TestConstants.shortTimeout
|
||||
)
|
||||
#expect(!leakedOnUnverifiedAnnounce)
|
||||
#expect(!carol.courierStore.isEmpty)
|
||||
|
||||
bob.sendBroadcastAnnounce()
|
||||
let announced = await TestHelpers.waitUntil(
|
||||
{ bobOut.first(ofType: .announce) != nil },
|
||||
timeout: TestConstants.defaultTimeout
|
||||
)
|
||||
#expect(announced)
|
||||
let verifiedAnnounce = try #require(bobOut.first(ofType: .announce))
|
||||
carol._test_handlePacket(verifiedAnnounce, fromPeerID: bob.myPeerID, preseedPeer: false)
|
||||
|
||||
let handedOver = await TestHelpers.waitUntil(
|
||||
{ carolOut.count(ofType: .courierEnvelope) == 1 },
|
||||
timeout: TestConstants.defaultTimeout
|
||||
)
|
||||
#expect(handedOver)
|
||||
#expect(carol.courierStore.isEmpty)
|
||||
}
|
||||
|
||||
@Test func sendCourierMessageRejectsInvalidRecipientKeyBeforeQueueing() async throws {
|
||||
let alice = makeService()
|
||||
let carol = makeService()
|
||||
preseedConnectedPeer(carol, in: alice)
|
||||
|
||||
let aliceOut = PacketTap()
|
||||
alice._test_onOutboundPacket = aliceOut.record
|
||||
|
||||
#expect(!alice.sendCourierMessage(
|
||||
"this cannot be sealed",
|
||||
messageID: "courier-msg-invalid-key",
|
||||
recipientNoiseKey: Data(repeating: 0x01, count: 8),
|
||||
via: [carol.myPeerID]
|
||||
))
|
||||
|
||||
let queuedPacket = await TestHelpers.waitUntil(
|
||||
{ aliceOut.first(ofType: .courierEnvelope) != nil },
|
||||
timeout: TestConstants.shortTimeout
|
||||
)
|
||||
#expect(!queuedPacket)
|
||||
}
|
||||
|
||||
@Test func depositFromUntrustedPeerIsRejected() async throws {
|
||||
let carol = makeService()
|
||||
carol.courierDepositPolicy = { _ in false } // depositor is not a mutual favorite
|
||||
@@ -202,6 +291,26 @@ struct CourierEndToEndTests {
|
||||
)
|
||||
#expect(!stored)
|
||||
}
|
||||
|
||||
private func makeUnsignedAnnounce(from service: BLEService) throws -> BitchatPacket {
|
||||
let announcement = AnnouncementPacket(
|
||||
nickname: "Unsigned",
|
||||
noisePublicKey: service.noiseStaticPublicKeyData(),
|
||||
signingPublicKey: service.noiseSigningPublicKeyData(),
|
||||
directNeighbors: nil
|
||||
)
|
||||
let payload = try #require(announcement.encode())
|
||||
|
||||
return BitchatPacket(
|
||||
type: MessageType.announce.rawValue,
|
||||
senderID: Data(hexString: service.myPeerID.id) ?? Data(),
|
||||
recipientID: nil,
|
||||
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
|
||||
payload: payload,
|
||||
signature: nil,
|
||||
ttl: TransportConfig.messageTTLDefault
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Router courier selection
|
||||
|
||||
@@ -98,8 +98,12 @@ struct BLEAnnounceHandlerTests {
|
||||
recorder.upsertResult = BLEPeerAnnounceUpdate(isNewPeer: true, wasDisconnected: false, previousNickname: nil)
|
||||
let handler = makeHandler(recorder: recorder, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
let result = handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(result?.peerID == peerID)
|
||||
#expect(result?.announcement.noisePublicKey == noiseKey)
|
||||
#expect(result?.isDirectAnnounce == true)
|
||||
#expect(result?.isVerified == true)
|
||||
#expect(recorder.verifySignatureCalls.count == 1)
|
||||
#expect(recorder.verifySignatureCalls.first?.signingPublicKey == Data(repeating: 0x99, count: 32))
|
||||
#expect(recorder.barrierCount == 1)
|
||||
@@ -161,8 +165,11 @@ struct BLEAnnounceHandlerTests {
|
||||
let recorder = Recorder()
|
||||
let handler = makeHandler(recorder: recorder, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
let result = handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(result?.peerID == peerID)
|
||||
#expect(result?.announcement.noisePublicKey == noiseKey)
|
||||
#expect(result?.isVerified == false)
|
||||
#expect(recorder.verifySignatureCalls.isEmpty)
|
||||
#expect(recorder.barrierCount == 1)
|
||||
#expect(recorder.upsertCalls.isEmpty)
|
||||
@@ -197,8 +204,9 @@ struct BLEAnnounceHandlerTests {
|
||||
recorder.signatureValid = false
|
||||
let handler = makeHandler(recorder: recorder, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
let result = handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(result?.isVerified == false)
|
||||
#expect(recorder.verifySignatureCalls.count == 1)
|
||||
#expect(recorder.upsertCalls.isEmpty)
|
||||
#expect(recorder.uiEventDeliveries.count == 1)
|
||||
@@ -222,8 +230,9 @@ struct BLEAnnounceHandlerTests {
|
||||
let recorder = Recorder()
|
||||
let handler = makeHandler(recorder: recorder, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
let result = handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(result == nil)
|
||||
expectNoSideEffects(recorder)
|
||||
}
|
||||
|
||||
@@ -242,8 +251,9 @@ struct BLEAnnounceHandlerTests {
|
||||
let recorder = Recorder()
|
||||
let handler = makeHandler(recorder: recorder, localPeerID: peerID, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
let result = handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(result == nil)
|
||||
expectNoSideEffects(recorder)
|
||||
}
|
||||
|
||||
@@ -263,8 +273,9 @@ struct BLEAnnounceHandlerTests {
|
||||
let recorder = Recorder()
|
||||
let handler = makeHandler(recorder: recorder, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
let result = handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(result == nil)
|
||||
expectNoSideEffects(recorder)
|
||||
}
|
||||
|
||||
@@ -311,8 +322,10 @@ struct BLEAnnounceHandlerTests {
|
||||
recorder.upsertResult = BLEPeerAnnounceUpdate(isNewPeer: true, wasDisconnected: false, previousNickname: nil)
|
||||
let handler = makeHandler(recorder: recorder, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
let result = handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(result?.isDirectAnnounce == false)
|
||||
#expect(result?.isVerified == true)
|
||||
#expect(recorder.upsertCalls.count == 1)
|
||||
#expect(recorder.upsertCalls.first?.isConnected == false)
|
||||
#expect(recorder.uiEventDeliveries.count == 1)
|
||||
@@ -384,8 +397,9 @@ struct BLEAnnounceHandlerTests {
|
||||
recorder.existingNoisePublicKey = Data(repeating: 0xAA, count: 32)
|
||||
let handler = makeHandler(recorder: recorder, now: now)
|
||||
|
||||
handler.handle(packet, from: peerID)
|
||||
let result = handler.handle(packet, from: peerID)
|
||||
|
||||
#expect(result?.isVerified == false)
|
||||
#expect(recorder.upsertCalls.isEmpty)
|
||||
#expect(recorder.uiEventDeliveries.count == 1)
|
||||
#expect(recorder.uiEventDeliveries.first?.notifyPeerConnected == false)
|
||||
|
||||
@@ -19,6 +19,79 @@ struct BLEFanoutSelectorTests {
|
||||
#expect(selection.centralIDs == Set(["c2"]))
|
||||
}
|
||||
|
||||
@Test
|
||||
func directedSendUsesOnlyBoundPeripheralLinkWhenAvailable() {
|
||||
let target = PeerID(str: "1122334455667788")
|
||||
let bystander = PeerID(str: "8877665544332211")
|
||||
let selection = BLEFanoutSelector.selectLinks(
|
||||
peripheralIDs: ["target-p", "bystander-p"],
|
||||
centralIDs: ["target-c", "bystander-c"],
|
||||
ingressLink: nil,
|
||||
peripheralPeerBindings: [
|
||||
"target-p": target,
|
||||
"bystander-p": bystander
|
||||
],
|
||||
centralPeerBindings: [
|
||||
"target-c": target,
|
||||
"bystander-c": bystander
|
||||
],
|
||||
directedPeerHint: target,
|
||||
packetType: MessageType.courierEnvelope.rawValue,
|
||||
messageID: "message-1"
|
||||
)
|
||||
|
||||
#expect(selection.peripheralIDs == Set(["target-p"]))
|
||||
#expect(selection.centralIDs.isEmpty)
|
||||
}
|
||||
|
||||
@Test
|
||||
func directedSendUsesBoundCentralLinkWhenNoPeripheralLinkExists() {
|
||||
let target = PeerID(str: "1122334455667788")
|
||||
let bystander = PeerID(str: "8877665544332211")
|
||||
let selection = BLEFanoutSelector.selectLinks(
|
||||
peripheralIDs: ["bystander-p"],
|
||||
centralIDs: ["target-c", "bystander-c"],
|
||||
ingressLink: nil,
|
||||
peripheralPeerBindings: [
|
||||
"bystander-p": bystander
|
||||
],
|
||||
centralPeerBindings: [
|
||||
"target-c": target,
|
||||
"bystander-c": bystander
|
||||
],
|
||||
directedPeerHint: target,
|
||||
packetType: MessageType.courierEnvelope.rawValue,
|
||||
messageID: "message-1"
|
||||
)
|
||||
|
||||
#expect(selection.peripheralIDs.isEmpty)
|
||||
#expect(selection.centralIDs == Set(["target-c"]))
|
||||
}
|
||||
|
||||
@Test
|
||||
func directedSendToKnownPeerDoesNotFallBackWhenOnlyDirectLinkIsExcluded() {
|
||||
let target = PeerID(str: "1122334455667788")
|
||||
let bystander = PeerID(str: "8877665544332211")
|
||||
let selection = BLEFanoutSelector.selectLinks(
|
||||
peripheralIDs: ["bystander-p"],
|
||||
centralIDs: ["target-c", "bystander-c"],
|
||||
ingressLink: .central("target-c"),
|
||||
peripheralPeerBindings: [
|
||||
"bystander-p": bystander
|
||||
],
|
||||
centralPeerBindings: [
|
||||
"target-c": target,
|
||||
"bystander-c": bystander
|
||||
],
|
||||
directedPeerHint: target,
|
||||
packetType: MessageType.courierEnvelope.rawValue,
|
||||
messageID: "message-1"
|
||||
)
|
||||
|
||||
#expect(selection.peripheralIDs.isEmpty)
|
||||
#expect(selection.centralIDs.isEmpty)
|
||||
}
|
||||
|
||||
@Test
|
||||
func directedSendExcludesAllLinksToIngressPeer() {
|
||||
let selection = BLEFanoutSelector.selectLinks(
|
||||
|
||||
Reference in New Issue
Block a user