Remove noise service exposure; single-owner selection state

Transport callers no longer reach the raw NoiseEncryptionService:
getNoiseService() is deleted in favor of narrow purpose-named Transport
methods (session public key, identity fingerprint, static/signing keys,
sign/verify, callback installation). VerificationService now reaches
crypto through the transport, so it can no longer pin a stale service
across a panic reset. myPeerID/myNickname become private(set); the
existing setNickname mutator is the sole nickname path.

ConversationStore is now the sole owner of private-chat selection:
PrivateChatManager.selectedPeer is a published read-only mirror, and
startChat/endChat mutate through the store intent. The bridge method
and its five call sites are deleted, removing a latent bug where a
stale manager selection pushed back into the store could resurrect a
just-removed conversation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jack
2026-06-11 15:29:07 +02:00
co-authored by Claude Fable 5
parent ed86ed1065
commit 8a867a17a1
16 changed files with 205 additions and 99 deletions
@@ -66,7 +66,6 @@ private final class MockChatPeerIdentityContext: ChatPeerIdentityContext {
private(set) var consolidatedPeers: [(peerID: PeerID, peerNickname: String)] = []
private(set) var syncedReadReceiptPeers: [PeerID] = []
private(set) var begunChatSessions: [PeerID] = []
private(set) var selectionStoreSyncCount = 0
private(set) var markedReadPeers: [PeerID] = []
@discardableResult
@@ -83,7 +82,6 @@ private final class MockChatPeerIdentityContext: ChatPeerIdentityContext {
begunChatSessions.append(peerID)
}
func synchronizeConversationSelectionStore() { selectionStoreSyncCount += 1 }
func markPrivateMessagesAsRead(from peerID: PeerID) { markedReadPeers.append(peerID) }
// Unified peer service
@@ -283,7 +281,6 @@ struct ChatPeerIdentityCoordinatorContextTests {
#expect(context.storedFingerprints.map(\.fingerprint) == ["fp-alice"])
#expect(context.selectedPrivateChatFingerprint == "fp-alice")
#expect(context.begunChatSessions == [peerID])
#expect(context.selectionStoreSyncCount == 1)
#expect(context.markedReadPeers == [peerID])
// Established session: no second handshake.
+1 -7
View File
@@ -34,9 +34,7 @@ final class MockBLEService: NSObject {
weak var delegate: BitchatDelegate?
var myPeerID = PeerID(str: "MOCK1234")
var myNickname: String = "MockUser"
private let mockKeychain = MockKeychain()
// Test-specific properties
var sentMessages: [(message: BitchatMessage, packet: BitchatPacket)] = []
var sentPackets: [BitchatPacket] = []
@@ -240,10 +238,6 @@ final class MockBLEService: NSObject {
delegate?.didUpdatePeerList([])
}
func getNoiseService() -> NoiseEncryptionService {
return NoiseEncryptionService(keychain: mockKeychain)
}
func getFingerprint(for peerID: String) -> String? {
return nil
}
+28 -2
View File
@@ -109,8 +109,34 @@ final class MockTransport: Transport {
triggeredHandshakes.append(peerID)
}
func getNoiseService() -> NoiseEncryptionService {
NoiseEncryptionService(keychain: mockKeychain)
// Noise identity wrappers backed by a mock-keychain encryption service
// (mirrors the previous `getNoiseService()` placeholder behavior: a real
// identity, but no peer sessions). Exposed so tests can assert against
// the same identity the wrappers use.
private(set) lazy var mockNoiseService = NoiseEncryptionService(keychain: mockKeychain)
func noiseSessionPublicKeyData(for peerID: PeerID) -> Data? {
mockNoiseService.getPeerPublicKeyData(peerID)
}
func noiseIdentityFingerprint() -> String {
mockNoiseService.getIdentityFingerprint()
}
func noiseStaticPublicKeyData() -> Data {
mockNoiseService.getStaticPublicKeyData()
}
func noiseSigningPublicKeyData() -> Data {
mockNoiseService.getSigningPublicKeyData()
}
func noiseSignData(_ data: Data) -> Data? {
mockNoiseService.signData(data)
}
func noiseVerifySignature(_ signature: Data, for data: Data, publicKey: Data) -> Bool {
mockNoiseService.verifySignature(signature, for: data, publicKey: publicKey)
}
// MARK: - Messaging
-2
View File
@@ -21,7 +21,6 @@ private final class DefaultTransportProbe: Transport {
let subject = CurrentValueSubject<[TransportPeerSnapshot], Never>([])
let myPeerID = PeerID(str: "0011223344556677")
var myNickname = "Tester"
private let keychain = MockKeychain()
private(set) var sentMessages: [(content: String, mentions: [String])] = []
var peerSnapshotPublisher: AnyPublisher<[TransportPeerSnapshot], Never> {
@@ -40,7 +39,6 @@ private final class DefaultTransportProbe: Transport {
func getFingerprint(for peerID: PeerID) -> String? { nil }
func getNoiseSessionState(for peerID: PeerID) -> LazyHandshakeState { .none }
func triggerHandshake(with peerID: PeerID) {}
func getNoiseService() -> NoiseEncryptionService { NoiseEncryptionService(keychain: keychain) }
func sendMessage(_ content: String, mentions: [String]) { sentMessages.append((content, mentions)) }
func sendPrivateMessage(_ content: String, to peerID: PeerID, recipientNickname: String, messageID: String) {}
func sendReadReceipt(_ receipt: ReadReceipt, to peerID: PeerID) {}
@@ -98,10 +98,13 @@ final class VerificationServiceTests: XCTestCase {
}
private func makeService() -> (VerificationService, NoiseEncryptionService) {
let noise = NoiseEncryptionService(keychain: MockKeychain())
// The service consumes Noise identity operations through the
// Transport's narrow noise* wrappers; the mock transport's backing
// encryption service is returned for direct assertions.
let transport = MockTransport()
let service = VerificationService()
service.configure(with: noise)
return (service, noise)
service.configure(with: transport)
return (service, transport.mockNoiseService)
}
private func makeSignedQR(