mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 18:05:19 +00:00
[codex] Refactor BLE outbound scheduling and Noise queues (#1306)
* 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 * Extract BLE outbound fragment scheduler * Harden app CI media tests * Redact BLE message content from logs * Extract BLE Noise session queues * Fix BLE read receipt UI updates * Allow self-authored RSR ingress replies * Harden read receipt queue test timing * Extract BLE outbound policy and incoming file storage * Avoid duplicate BLE link snapshots during send * Canonicalize Nostr relay URLs --------- Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import bitchat
|
||||
|
||||
struct BLENoisePayloadFactoryTests {
|
||||
@Test
|
||||
func privateMessagePayloadPrefixesTLVWithNoiseType() throws {
|
||||
let payload = try #require(BLENoisePayloadFactory.privateMessage(content: "secret", messageID: "pm-1"))
|
||||
|
||||
#expect(payload.first == NoisePayloadType.privateMessage.rawValue)
|
||||
|
||||
let packet = try #require(PrivateMessagePacket.decode(from: Data(payload.dropFirst())))
|
||||
#expect(packet.messageID == "pm-1")
|
||||
#expect(packet.content == "secret")
|
||||
}
|
||||
|
||||
@Test
|
||||
func receiptPayloadsUseMessageIDBytes() {
|
||||
let read = BLENoisePayloadFactory.readReceipt(originalMessageID: "read-id")
|
||||
let delivered = BLENoisePayloadFactory.delivered(messageID: "delivered-id")
|
||||
|
||||
#expect(read.first == NoisePayloadType.readReceipt.rawValue)
|
||||
#expect(String(data: Data(read.dropFirst()), encoding: .utf8) == "read-id")
|
||||
#expect(delivered.first == NoisePayloadType.delivered.rawValue)
|
||||
#expect(String(data: Data(delivered.dropFirst()), encoding: .utf8) == "delivered-id")
|
||||
}
|
||||
|
||||
@Test
|
||||
func typedPayloadKeepsOpaqueDataUnchanged() {
|
||||
let payload = BLENoisePayloadFactory.typedPayload(.verifyChallenge, payload: Data([0xCA, 0xFE]))
|
||||
|
||||
#expect(payload == Data([NoisePayloadType.verifyChallenge.rawValue, 0xCA, 0xFE]))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import BitFoundation
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import bitchat
|
||||
|
||||
struct BLENoiseSessionQueuesTests {
|
||||
@Test
|
||||
func privateMessagesDrainInPeerOrderAndClearOnlyThatPeer() {
|
||||
let firstPeer = PeerID(str: "aaaaaaaaaaaaaaaa")
|
||||
let secondPeer = PeerID(str: "bbbbbbbbbbbbbbbb")
|
||||
var queues = BLENoiseSessionQueues()
|
||||
|
||||
queues.appendPrivateMessage(content: "first", messageID: "m1", for: firstPeer)
|
||||
queues.appendPrivateMessage(content: "second", messageID: "m2", for: firstPeer)
|
||||
queues.appendPrivateMessage(content: "other", messageID: "m3", for: secondPeer)
|
||||
|
||||
let drained = queues.takePrivateMessages(for: firstPeer)
|
||||
|
||||
#expect(drained == [
|
||||
BLEPendingPrivateMessage(content: "first", messageID: "m1"),
|
||||
BLEPendingPrivateMessage(content: "second", messageID: "m2")
|
||||
])
|
||||
#expect(queues.takePrivateMessages(for: firstPeer).isEmpty)
|
||||
#expect(queues.takePrivateMessages(for: secondPeer) == [
|
||||
BLEPendingPrivateMessage(content: "other", messageID: "m3")
|
||||
])
|
||||
}
|
||||
|
||||
@Test
|
||||
func prependPrivateMessagesRestoresFailedMessagesAheadOfNewerOnes() {
|
||||
let peerID = PeerID(str: "aaaaaaaaaaaaaaaa")
|
||||
var queues = BLENoiseSessionQueues()
|
||||
|
||||
queues.appendPrivateMessage(content: "new", messageID: "m2", for: peerID)
|
||||
queues.prependPrivateMessages([
|
||||
BLEPendingPrivateMessage(content: "retry", messageID: "m1")
|
||||
], for: peerID)
|
||||
|
||||
#expect(queues.takePrivateMessages(for: peerID).map(\.messageID) == ["m1", "m2"])
|
||||
}
|
||||
|
||||
@Test
|
||||
func typedPayloadsDrainIndependentlyFromPrivateMessages() {
|
||||
let peerID = PeerID(str: "aaaaaaaaaaaaaaaa")
|
||||
var queues = BLENoiseSessionQueues()
|
||||
|
||||
queues.appendPrivateMessage(content: "queued", messageID: "m1", for: peerID)
|
||||
queues.appendTypedPayload(Data([0x01]), for: peerID)
|
||||
queues.appendTypedPayload(Data([0x02]), for: peerID)
|
||||
|
||||
#expect(queues.takeTypedPayloads(for: peerID) == [Data([0x01]), Data([0x02])])
|
||||
#expect(queues.takeTypedPayloads(for: peerID).isEmpty)
|
||||
#expect(queues.takePrivateMessages(for: peerID).map(\.messageID) == ["m1"])
|
||||
}
|
||||
|
||||
@Test
|
||||
func removeAllClearsBothQueueTypes() {
|
||||
let peerID = PeerID(str: "aaaaaaaaaaaaaaaa")
|
||||
var queues = BLENoiseSessionQueues()
|
||||
|
||||
queues.appendPrivateMessage(content: "queued", messageID: "m1", for: peerID)
|
||||
queues.appendTypedPayload(Data([0x01]), for: peerID)
|
||||
queues.removeAll()
|
||||
|
||||
#expect(queues.isEmpty)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
import BitFoundation
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import bitchat
|
||||
|
||||
struct BLEOutboundFragmentTransferSchedulerTests {
|
||||
@Test
|
||||
func submitStartsPublicMessageWithoutTransferReservation() {
|
||||
var scheduler = BLEOutboundFragmentTransferScheduler()
|
||||
let request = makeRequest(type: MessageType.message.rawValue, transferId: nil)
|
||||
|
||||
let result = scheduler.submit(request, maxConcurrentTransfers: 1)
|
||||
|
||||
if case let .start(_, reservedTransferId) = result {
|
||||
#expect(reservedTransferId == nil)
|
||||
#expect(scheduler.activeCount == 0)
|
||||
#expect(scheduler.pendingCount == 0)
|
||||
} else {
|
||||
Issue.record("Expected non-file fragments to start without reserving a transfer slot")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func submitQueuesFileTransferWhenSlotsAreFull() {
|
||||
var scheduler = BLEOutboundFragmentTransferScheduler()
|
||||
let first = makeRequest(type: MessageType.fileTransfer.rawValue, transferId: "first")
|
||||
let second = makeRequest(type: MessageType.fileTransfer.rawValue, transferId: "second")
|
||||
|
||||
guard case let .start(_, firstReservation?) = scheduler.submit(first, maxConcurrentTransfers: 1) else {
|
||||
Issue.record("Expected first file transfer to reserve a slot")
|
||||
return
|
||||
}
|
||||
#expect(firstReservation == "first")
|
||||
|
||||
let result = scheduler.submit(second, maxConcurrentTransfers: 1)
|
||||
|
||||
if case let .queued(_, transferId, position) = result {
|
||||
#expect(transferId == "second")
|
||||
#expect(position == .back)
|
||||
#expect(scheduler.activeCount == 1)
|
||||
#expect(scheduler.pendingCount == 1)
|
||||
} else {
|
||||
Issue.record("Expected second file transfer to queue while slots are full")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func submitQueuesDuplicateActiveTransferAtFront() {
|
||||
var scheduler = BLEOutboundFragmentTransferScheduler()
|
||||
let request = makeRequest(type: MessageType.fileTransfer.rawValue, transferId: "same")
|
||||
|
||||
_ = scheduler.submit(request, maxConcurrentTransfers: 2)
|
||||
let result = scheduler.submit(request, maxConcurrentTransfers: 2)
|
||||
|
||||
if case let .queued(_, transferId, position) = result {
|
||||
#expect(transferId == "same")
|
||||
#expect(position == .front)
|
||||
#expect(scheduler.activeCount == 1)
|
||||
#expect(scheduler.pendingCount == 1)
|
||||
} else {
|
||||
Issue.record("Expected duplicate active transfer to queue at the front")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func cancelActiveTransferReturnsScheduledWorkItems() {
|
||||
var scheduler = BLEOutboundFragmentTransferScheduler()
|
||||
let request = makeRequest(type: MessageType.fileTransfer.rawValue, transferId: "active")
|
||||
_ = scheduler.submit(request, maxConcurrentTransfers: 1)
|
||||
let workItem = DispatchWorkItem {}
|
||||
|
||||
let didActivate = scheduler.activateReservedTransfer(id: "active", totalFragments: 2, workItems: [workItem])
|
||||
#expect(didActivate)
|
||||
|
||||
if case let .active(transferId, workItems) = scheduler.cancelTransfer("active") {
|
||||
#expect(transferId == "active")
|
||||
#expect(workItems.count == 1)
|
||||
#expect(scheduler.activeCount == 0)
|
||||
} else {
|
||||
Issue.record("Expected active transfer cancellation to return its work items")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func completedTransferFreesSlotForPendingTransfer() {
|
||||
var scheduler = BLEOutboundFragmentTransferScheduler()
|
||||
let first = makeRequest(type: MessageType.fileTransfer.rawValue, transferId: "first")
|
||||
let second = makeRequest(type: MessageType.fileTransfer.rawValue, transferId: "second")
|
||||
|
||||
_ = scheduler.submit(first, maxConcurrentTransfers: 1)
|
||||
let didActivate = scheduler.activateReservedTransfer(id: "first", totalFragments: 2, workItems: [])
|
||||
#expect(didActivate)
|
||||
_ = scheduler.submit(second, maxConcurrentTransfers: 1)
|
||||
|
||||
#expect(scheduler.markFragmentSent(transferId: "first") == .progress(sentFragments: 1, totalFragments: 2))
|
||||
#expect(scheduler.markFragmentSent(transferId: "first") == .complete(sentFragments: 2, totalFragments: 2))
|
||||
|
||||
let starts = scheduler.reservePendingStarts(maxConcurrentTransfers: 1)
|
||||
#expect(starts.count == 1)
|
||||
|
||||
if case let .start(_, reservedTransferId?) = starts.first {
|
||||
#expect(reservedTransferId == "second")
|
||||
#expect(scheduler.activeCount == 1)
|
||||
#expect(scheduler.pendingCount == 0)
|
||||
} else {
|
||||
Issue.record("Expected pending transfer to reserve the freed slot")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func removeAllReturnsActiveWorkItemsAndDropsPendingTransfers() {
|
||||
var scheduler = BLEOutboundFragmentTransferScheduler()
|
||||
let active = makeRequest(type: MessageType.fileTransfer.rawValue, transferId: "active")
|
||||
let pending = makeRequest(type: MessageType.fileTransfer.rawValue, transferId: "pending")
|
||||
let workItem = DispatchWorkItem {}
|
||||
|
||||
_ = scheduler.submit(active, maxConcurrentTransfers: 1)
|
||||
let didActivate = scheduler.activateReservedTransfer(id: "active", totalFragments: 1, workItems: [workItem])
|
||||
#expect(didActivate)
|
||||
_ = scheduler.submit(pending, maxConcurrentTransfers: 1)
|
||||
|
||||
let removed = scheduler.removeAll()
|
||||
|
||||
#expect(removed.count == 1)
|
||||
#expect(removed.first?.id == "active")
|
||||
#expect(removed.first?.workItems.count == 1)
|
||||
#expect(scheduler.activeCount == 0)
|
||||
#expect(scheduler.pendingCount == 0)
|
||||
}
|
||||
|
||||
private func makeRequest(type: UInt8, transferId: String?) -> BLEOutboundFragmentTransferRequest {
|
||||
BLEOutboundFragmentTransferRequest(
|
||||
packet: BitchatPacket(
|
||||
type: type,
|
||||
senderID: Data([0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77]),
|
||||
recipientID: nil,
|
||||
timestamp: 0x0102030405,
|
||||
payload: Data((transferId ?? "payload").utf8),
|
||||
signature: nil,
|
||||
ttl: 3
|
||||
),
|
||||
pad: false,
|
||||
maxChunk: nil,
|
||||
directedPeer: nil,
|
||||
transferId: transferId
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -208,7 +208,7 @@ final class NostrRelayManagerTests: XCTestCase {
|
||||
let relayTwo = "wss://relay-two.example"
|
||||
let context = makeContext(permission: .denied)
|
||||
|
||||
context.manager.ensureConnections(to: [relayOne, relayOne, relayTwo])
|
||||
context.manager.ensureConnections(to: [relayOne, "wss://relay-one.example:443/", "WSS://RELAY-TWO.EXAMPLE:443"])
|
||||
|
||||
let connected = await waitUntil {
|
||||
Set(context.manager.getRelayStatuses().map(\.url)) == Set([relayOne, relayTwo]) &&
|
||||
|
||||
Reference in New Issue
Block a user