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.
This commit is contained in:
jack
2026-07-10 14:04:09 +02:00
committed by GitHub
parent b205a55523
commit 820c933958
91 changed files with 8381 additions and 1083 deletions
@@ -35,19 +35,15 @@ struct FragmentationTests {
// Use a small fragment size to ensure multiple pieces
let fragments = fragmentPacket(original, fragmentSize: 400)
// Shuffle fragments to simulate out-of-order arrival
let shuffled = fragments.shuffled()
// Reverse deterministically to simulate out-of-order arrival without
// making a failure depend on a random permutation.
let outOfOrder = fragments.reversed()
// Send fragments sequentially with small delays (no fire-and-forget Tasks)
for (i, fragment) in shuffled.enumerated() {
if i > 0 {
try await Task.sleep(for: .milliseconds(5))
}
for fragment in outOfOrder {
ble._test_handlePacket(fragment, fromPeerID: remoteShortID, signingPublicKey: signingKey)
}
// Wait for delegate callback with proper timeout
try await capture.waitForPublicMessages(count: 1, timeout: .seconds(5))
await ble._test_drainFragmentPipeline()
#expect(capture.publicMessages.count == 1)
#expect(capture.publicMessages.first?.content.count == 3_000)
@@ -73,16 +69,11 @@ struct FragmentationTests {
frags.insert(dup, at: 1)
}
// Send fragments sequentially with small delays (no fire-and-forget Tasks)
for (i, fragment) in frags.enumerated() {
if i > 0 {
try await Task.sleep(for: .milliseconds(5))
}
for fragment in frags {
ble._test_handlePacket(fragment, fromPeerID: remoteShortID, signingPublicKey: signingKey)
}
// Wait for delegate callback with proper timeout
try await capture.waitForPublicMessages(count: 1, timeout: .seconds(5))
await ble._test_drainFragmentPipeline()
#expect(capture.publicMessages.count == 1)
#expect(capture.publicMessages.first?.content.count == 2048)
@@ -126,14 +117,11 @@ struct FragmentationTests {
let fragments = fragmentPacket(packet, fragmentSize: 4096, pad: false)
#expect(!fragments.isEmpty)
for (i, fragment) in fragments.enumerated() {
if i > 0 {
try await Task.sleep(for: .milliseconds(5))
}
for fragment in fragments {
ble._test_handlePacket(fragment, fromPeerID: remoteID, signingPublicKey: signingKey)
}
try await capture.waitForReceivedMessages(count: 1, timeout: .seconds(5))
await ble._test_drainFragmentPipeline()
let message = try #require(capture.receivedMessages.first, "Expected file transfer message")
#expect(message.content.hasPrefix("[file]"))
@@ -173,15 +161,11 @@ struct FragmentationTests {
corrupted[0] = p
}
for (i, fragment) in corrupted.enumerated() {
if i > 0 {
try await Task.sleep(for: .milliseconds(5))
}
for fragment in corrupted {
ble._test_handlePacket(fragment, fromPeerID: remoteShortID)
}
// Allow async processing
try await sleep(0.5)
await ble._test_drainFragmentPipeline()
// Should not deliver since one fragment is invalid and reassembly can't complete
#expect(capture.publicMessages.isEmpty)
@@ -207,10 +191,6 @@ extension FragmentationTests {
private let lock = NSLock()
private var _publicMessages: [(peerID: PeerID, nickname: String, content: String)] = []
private var _receivedMessages: [BitchatMessage] = []
private var publicMessageContinuation: CheckedContinuation<Void, Never>?
private var receivedMessageContinuation: CheckedContinuation<Void, Never>?
private var expectedPublicMessageCount: Int = 0
private var expectedReceivedMessageCount: Int = 0
private func withLock<T>(_ body: () -> T) -> T {
lock.lock()
@@ -227,139 +207,11 @@ extension FragmentationTests {
}
func didReceiveMessage(_ message: BitchatMessage) {
lock.lock()
_receivedMessages.append(message)
let count = _receivedMessages.count
let expected = expectedReceivedMessageCount
let continuation = receivedMessageContinuation
lock.unlock()
if count >= expected, let cont = continuation {
lock.lock()
receivedMessageContinuation = nil
lock.unlock()
cont.resume()
}
withLock { _receivedMessages.append(message) }
}
func didReceivePublicMessage(from peerID: PeerID, nickname: String, content: String, timestamp: Date, messageID: String?) {
lock.lock()
_publicMessages.append((peerID, nickname, content))
let count = _publicMessages.count
let expected = expectedPublicMessageCount
let continuation = publicMessageContinuation
lock.unlock()
if count >= expected, let cont = continuation {
lock.lock()
publicMessageContinuation = nil
lock.unlock()
cont.resume()
}
}
/// Waits for the specified number of public messages to be received
func waitForPublicMessages(count: Int, timeout: Duration = .seconds(2)) async throws {
let isAlreadySatisfied = withLock { () -> Bool in
if _publicMessages.count >= count {
return true
}
expectedPublicMessageCount = count
return false
}
if isAlreadySatisfied {
return
}
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
// withCheckedContinuation itself is not cancellable, so hook
// group.cancelAll() to resume the parked continuation
// otherwise a timeout leaves the group awaiting this child
// forever and the test run hangs instead of failing.
await withTaskCancellationHandler {
await withCheckedContinuation { continuation in
let shouldResumeImmediately = self.withLock {
// Recheck count after acquiring lock to avoid race condition
// where message arrives between initial check and continuation install
if self._publicMessages.count >= count {
return true
}
self.publicMessageContinuation = continuation
return false
}
if shouldResumeImmediately {
continuation.resume()
}
}
} onCancel: {
let continuation = self.withLock {
let parked = self.publicMessageContinuation
self.publicMessageContinuation = nil
return parked
}
continuation?.resume()
}
}
group.addTask {
try await Task.sleep(for: timeout)
throw CancellationError()
}
try await group.next()
group.cancelAll()
}
}
/// Waits for the specified number of received messages
func waitForReceivedMessages(count: Int, timeout: Duration = .seconds(2)) async throws {
let isAlreadySatisfied = withLock { () -> Bool in
if _receivedMessages.count >= count {
return true
}
expectedReceivedMessageCount = count
return false
}
if isAlreadySatisfied {
return
}
try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
// withCheckedContinuation itself is not cancellable, so hook
// group.cancelAll() to resume the parked continuation
// otherwise a timeout leaves the group awaiting this child
// forever and the test run hangs instead of failing.
await withTaskCancellationHandler {
await withCheckedContinuation { continuation in
let shouldResumeImmediately = self.withLock {
// Recheck count after acquiring lock to avoid race condition
// where message arrives between initial check and continuation install
if self._receivedMessages.count >= count {
return true
}
self.receivedMessageContinuation = continuation
return false
}
if shouldResumeImmediately {
continuation.resume()
}
}
} onCancel: {
let continuation = self.withLock {
let parked = self.receivedMessageContinuation
self.receivedMessageContinuation = nil
return parked
}
continuation?.resume()
}
}
group.addTask {
try await Task.sleep(for: timeout)
throw CancellationError()
}
try await group.next()
group.cancelAll()
}
withLock { _publicMessages.append((peerID, nickname, content)) }
}
func didConnectToPeer(_ peerID: PeerID) {}