mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 07:05:20 +00:00
* Refactor BLE transport event handling * Make image output paths unique * Keep queued Nostr read receipts alive * Refine BLE ingress fanout * Rediscover BLE service after invalidation * Extract BLE notification retry buffer * Extract BLE inbound write buffer * Extract BLE fragment assembly buffer * Tidy secure log handling from device run * Allow self-authored RSR ingress replies * Harden read receipt queue test timing --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com>
109 lines
3.4 KiB
Swift
109 lines
3.4 KiB
Swift
import BitFoundation
|
|
import Foundation
|
|
|
|
enum BLEIngressLinkID: Hashable, Equatable {
|
|
case peripheral(String)
|
|
case central(String)
|
|
}
|
|
|
|
struct BLEIngressPacketContext: Equatable {
|
|
let receivedFromPeerID: PeerID
|
|
let validationPeerID: PeerID
|
|
}
|
|
|
|
struct BLEIngressLinkRecord: Equatable {
|
|
let link: BLEIngressLinkID
|
|
let peerID: PeerID
|
|
let timestamp: Date
|
|
}
|
|
|
|
enum BLEIngressRejection: Error, Equatable {
|
|
case selfLoopback(packetType: UInt8)
|
|
case directSenderMismatch(boundPeerID: PeerID, claimedSenderID: PeerID)
|
|
}
|
|
|
|
struct BLEIngressLinkRegistry {
|
|
private var ingressByMessageID: [String: BLEIngressLinkRecord] = [:]
|
|
|
|
var isEmpty: Bool {
|
|
ingressByMessageID.isEmpty
|
|
}
|
|
|
|
mutating func removeAll() {
|
|
ingressByMessageID.removeAll()
|
|
}
|
|
|
|
func record(for packet: BitchatPacket) -> BLEIngressLinkRecord? {
|
|
ingressByMessageID[Self.messageID(for: packet)]
|
|
}
|
|
|
|
func link(for packet: BitchatPacket) -> BLEIngressLinkID? {
|
|
record(for: packet)?.link
|
|
}
|
|
|
|
func peerID(for packet: BitchatPacket) -> PeerID? {
|
|
record(for: packet)?.peerID
|
|
}
|
|
|
|
mutating func recordIfNew(
|
|
_ packet: BitchatPacket,
|
|
link: BLEIngressLinkID,
|
|
peerID: PeerID,
|
|
now: Date = Date(),
|
|
lifetime: TimeInterval
|
|
) -> Bool {
|
|
let messageID = Self.messageID(for: packet)
|
|
if let existing = ingressByMessageID[messageID],
|
|
now.timeIntervalSince(existing.timestamp) <= lifetime {
|
|
return false
|
|
}
|
|
|
|
ingressByMessageID[messageID] = BLEIngressLinkRecord(link: link, peerID: peerID, timestamp: now)
|
|
return true
|
|
}
|
|
|
|
mutating func prune(before cutoff: Date) {
|
|
ingressByMessageID = ingressByMessageID.filter { $0.value.timestamp >= cutoff }
|
|
}
|
|
|
|
static func packetContext(
|
|
for packet: BitchatPacket,
|
|
claimedSenderID: PeerID,
|
|
boundPeerID: PeerID?,
|
|
localPeerID: PeerID,
|
|
directAnnounceTTL: UInt8
|
|
) -> Result<BLEIngressPacketContext, BLEIngressRejection> {
|
|
if claimedSenderID == localPeerID,
|
|
!isSelfAuthoredSyncResponse(packet) {
|
|
return .failure(.selfLoopback(packetType: packet.type))
|
|
}
|
|
|
|
if let boundPeerID,
|
|
boundPeerID != claimedSenderID,
|
|
requiresDirectSenderBinding(packet, directAnnounceTTL: directAnnounceTTL) {
|
|
return .failure(.directSenderMismatch(boundPeerID: boundPeerID, claimedSenderID: claimedSenderID))
|
|
}
|
|
|
|
let receivedFromPeerID = boundPeerID ?? claimedSenderID
|
|
let validationPeerID = packet.isRSR ? receivedFromPeerID : claimedSenderID
|
|
return .success(BLEIngressPacketContext(
|
|
receivedFromPeerID: receivedFromPeerID,
|
|
validationPeerID: validationPeerID
|
|
))
|
|
}
|
|
|
|
static func messageID(for packet: BitchatPacket) -> String {
|
|
let senderID = packet.senderID.hexEncodedString()
|
|
let digestPrefix = packet.payload.sha256Hash().prefix(4).hexEncodedString()
|
|
return "\(senderID)-\(packet.timestamp)-\(packet.type)-\(digestPrefix)"
|
|
}
|
|
|
|
private static func requiresDirectSenderBinding(_ packet: BitchatPacket, directAnnounceTTL: UInt8) -> Bool {
|
|
packet.type == MessageType.announce.rawValue && packet.ttl == directAnnounceTTL
|
|
}
|
|
|
|
private static func isSelfAuthoredSyncResponse(_ packet: BitchatPacket) -> Bool {
|
|
packet.isRSR && packet.ttl == 0
|
|
}
|
|
}
|