mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 02:25:20 +00:00
Extract BLE and chat architecture policies (#1324)
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
import BitFoundation
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import bitchat
|
||||
|
||||
struct BLEAnnounceHandlingPolicyTests {
|
||||
@Test
|
||||
func preflightAcceptsMatchingFreshAnnounce() throws {
|
||||
let now = Date(timeIntervalSince1970: 1_000)
|
||||
let noiseKey = Data(repeating: 0x11, count: 32)
|
||||
let peerID = PeerID(publicKey: noiseKey)
|
||||
let packet = try makeAnnouncePacket(noisePublicKey: noiseKey, peerID: peerID, timestamp: timestamp(now))
|
||||
|
||||
let decision = BLEAnnouncePreflightPolicy.evaluate(
|
||||
packet: packet,
|
||||
from: peerID,
|
||||
localPeerID: PeerID(str: "0102030405060708"),
|
||||
now: now
|
||||
)
|
||||
|
||||
guard case .accept(let accepted) = decision else {
|
||||
Issue.record("Expected announce preflight acceptance")
|
||||
return
|
||||
}
|
||||
#expect(accepted.announcement.nickname == "Alice")
|
||||
#expect(accepted.derivedPeerID == peerID)
|
||||
}
|
||||
|
||||
@Test
|
||||
func preflightRejectsMalformedPayload() {
|
||||
let now = Date(timeIntervalSince1970: 1_000)
|
||||
let peerID = PeerID(str: "1122334455667788")
|
||||
let packet = BitchatPacket(
|
||||
type: MessageType.announce.rawValue,
|
||||
senderID: Data(hexString: peerID.id) ?? Data(),
|
||||
recipientID: nil,
|
||||
timestamp: timestamp(now),
|
||||
payload: Data([0x01, 0x20]),
|
||||
signature: nil,
|
||||
ttl: TransportConfig.messageTTLDefault
|
||||
)
|
||||
|
||||
let decision = BLEAnnouncePreflightPolicy.evaluate(
|
||||
packet: packet,
|
||||
from: peerID,
|
||||
localPeerID: PeerID(str: "0102030405060708"),
|
||||
now: now
|
||||
)
|
||||
|
||||
guard case .reject(.malformed) = decision else {
|
||||
Issue.record("Expected malformed announce rejection")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func preflightRejectsSenderMismatchWithDerivedPeerID() throws {
|
||||
let now = Date(timeIntervalSince1970: 1_000)
|
||||
let noiseKey = Data(repeating: 0x22, count: 32)
|
||||
let derivedPeerID = PeerID(publicKey: noiseKey)
|
||||
let claimedPeerID = PeerID(str: "1122334455667788")
|
||||
let packet = try makeAnnouncePacket(noisePublicKey: noiseKey, peerID: claimedPeerID, timestamp: timestamp(now))
|
||||
|
||||
let decision = BLEAnnouncePreflightPolicy.evaluate(
|
||||
packet: packet,
|
||||
from: claimedPeerID,
|
||||
localPeerID: PeerID(str: "0102030405060708"),
|
||||
now: now
|
||||
)
|
||||
|
||||
guard case .reject(.senderMismatch(let rejectedDerivedPeerID)) = decision else {
|
||||
Issue.record("Expected sender mismatch rejection")
|
||||
return
|
||||
}
|
||||
#expect(rejectedDerivedPeerID == derivedPeerID)
|
||||
}
|
||||
|
||||
@Test
|
||||
func preflightRejectsSelfAnnounceAfterSenderMatches() throws {
|
||||
let now = Date(timeIntervalSince1970: 1_000)
|
||||
let noiseKey = Data(repeating: 0x33, count: 32)
|
||||
let peerID = PeerID(publicKey: noiseKey)
|
||||
let packet = try makeAnnouncePacket(noisePublicKey: noiseKey, peerID: peerID, timestamp: timestamp(now))
|
||||
|
||||
let decision = BLEAnnouncePreflightPolicy.evaluate(
|
||||
packet: packet,
|
||||
from: peerID,
|
||||
localPeerID: peerID,
|
||||
now: now
|
||||
)
|
||||
|
||||
guard case .reject(.selfAnnounce) = decision else {
|
||||
Issue.record("Expected self announce rejection")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func preflightRejectsStaleAnnounceWithAge() throws {
|
||||
let now = Date(timeIntervalSince1970: 1_000)
|
||||
let noiseKey = Data(repeating: 0x44, count: 32)
|
||||
let peerID = PeerID(publicKey: noiseKey)
|
||||
let oldTimestamp = UInt64((now.timeIntervalSince1970 - 901) * 1000)
|
||||
let packet = try makeAnnouncePacket(noisePublicKey: noiseKey, peerID: peerID, timestamp: oldTimestamp)
|
||||
|
||||
let decision = BLEAnnouncePreflightPolicy.evaluate(
|
||||
packet: packet,
|
||||
from: peerID,
|
||||
localPeerID: PeerID(str: "0102030405060708"),
|
||||
now: now
|
||||
)
|
||||
|
||||
guard case .reject(.stale(let ageSeconds)) = decision else {
|
||||
Issue.record("Expected stale announce rejection")
|
||||
return
|
||||
}
|
||||
#expect(abs(ageSeconds - 901) < 0.001)
|
||||
}
|
||||
|
||||
@Test
|
||||
func trustPolicyRequiresSignature() {
|
||||
let decision = BLEAnnounceTrustPolicy.evaluate(
|
||||
hasSignature: false,
|
||||
signatureValid: false,
|
||||
existingNoisePublicKey: nil,
|
||||
announcedNoisePublicKey: Data(repeating: 0x11, count: 32)
|
||||
)
|
||||
|
||||
#expect(decision == .reject(.missingSignature))
|
||||
#expect(!decision.isVerified)
|
||||
}
|
||||
|
||||
@Test
|
||||
func trustPolicyRejectsInvalidSignature() {
|
||||
let decision = BLEAnnounceTrustPolicy.evaluate(
|
||||
hasSignature: true,
|
||||
signatureValid: false,
|
||||
existingNoisePublicKey: nil,
|
||||
announcedNoisePublicKey: Data(repeating: 0x11, count: 32)
|
||||
)
|
||||
|
||||
#expect(decision == .reject(.invalidSignature))
|
||||
}
|
||||
|
||||
@Test
|
||||
func trustPolicyRejectsExistingKeyMismatch() {
|
||||
let decision = BLEAnnounceTrustPolicy.evaluate(
|
||||
hasSignature: true,
|
||||
signatureValid: true,
|
||||
existingNoisePublicKey: Data(repeating: 0xAA, count: 32),
|
||||
announcedNoisePublicKey: Data(repeating: 0xBB, count: 32)
|
||||
)
|
||||
|
||||
#expect(decision == .reject(.keyMismatch))
|
||||
}
|
||||
|
||||
@Test
|
||||
func trustPolicyAcceptsValidSignatureWithMatchingExistingKey() {
|
||||
let noiseKey = Data(repeating: 0xCC, count: 32)
|
||||
|
||||
let decision = BLEAnnounceTrustPolicy.evaluate(
|
||||
hasSignature: true,
|
||||
signatureValid: true,
|
||||
existingNoisePublicKey: noiseKey,
|
||||
announcedNoisePublicKey: noiseKey
|
||||
)
|
||||
|
||||
#expect(decision == .verified)
|
||||
#expect(decision.isVerified)
|
||||
}
|
||||
|
||||
@Test
|
||||
func responsePolicyConnectsOnlyForDirectNewOrReconnectedPeers() {
|
||||
let directNew = BLEAnnounceResponsePolicy.plan(
|
||||
isDirectAnnounce: true,
|
||||
isNewPeer: true,
|
||||
isReconnectedPeer: false,
|
||||
shouldSendAnnounceBack: false
|
||||
)
|
||||
#expect(directNew.shouldNotifyPeerConnected)
|
||||
#expect(directNew.shouldScheduleInitialSync)
|
||||
#expect(directNew.shouldScheduleAfterglow)
|
||||
|
||||
let relayedNew = BLEAnnounceResponsePolicy.plan(
|
||||
isDirectAnnounce: false,
|
||||
isNewPeer: true,
|
||||
isReconnectedPeer: false,
|
||||
shouldSendAnnounceBack: true
|
||||
)
|
||||
#expect(!relayedNew.shouldNotifyPeerConnected)
|
||||
#expect(!relayedNew.shouldScheduleInitialSync)
|
||||
#expect(relayedNew.shouldSendAnnounceBack)
|
||||
#expect(relayedNew.shouldScheduleAfterglow)
|
||||
|
||||
let directExisting = BLEAnnounceResponsePolicy.plan(
|
||||
isDirectAnnounce: true,
|
||||
isNewPeer: false,
|
||||
isReconnectedPeer: false,
|
||||
shouldSendAnnounceBack: false
|
||||
)
|
||||
#expect(!directExisting.shouldNotifyPeerConnected)
|
||||
#expect(!directExisting.shouldScheduleInitialSync)
|
||||
#expect(!directExisting.shouldScheduleAfterglow)
|
||||
}
|
||||
|
||||
private func makeAnnouncePacket(
|
||||
noisePublicKey: Data,
|
||||
peerID: PeerID,
|
||||
timestamp: UInt64
|
||||
) throws -> BitchatPacket {
|
||||
let announcement = AnnouncementPacket(
|
||||
nickname: "Alice",
|
||||
noisePublicKey: noisePublicKey,
|
||||
signingPublicKey: Data(repeating: 0x99, count: 32),
|
||||
directNeighbors: nil
|
||||
)
|
||||
let payload = try #require(announcement.encode())
|
||||
|
||||
return BitchatPacket(
|
||||
type: MessageType.announce.rawValue,
|
||||
senderID: Data(hexString: peerID.id) ?? Data(),
|
||||
recipientID: nil,
|
||||
timestamp: timestamp,
|
||||
payload: payload,
|
||||
signature: nil,
|
||||
ttl: TransportConfig.messageTTLDefault
|
||||
)
|
||||
}
|
||||
|
||||
private func timestamp(_ date: Date) -> UInt64 {
|
||||
UInt64(date.timeIntervalSince1970 * 1000)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user