PeerID 11/n: Noise types use PeerID + create separate files (#750)

* Noise types use PeerID

* Fix tests

* Extract `NoiseSessionManager` into a separate file

* Extract `NoiseSessionState` into a separate file

* Remove `failed` state from `NoiseSessionState`

* Extract `NoiseSessionError` into a separate file
This commit is contained in:
Islam
2025-10-05 15:51:06 +02:00
committed by GitHub
parent 64f91bb1d6
commit 03c357f048
13 changed files with 329 additions and 311 deletions
+5 -270
View File
@@ -10,32 +10,8 @@ import BitLogger
import Foundation
import CryptoKit
// MARK: - Noise Session State
enum NoiseSessionState: Equatable {
case uninitialized
case handshaking
case established
case failed(Error)
static func == (lhs: NoiseSessionState, rhs: NoiseSessionState) -> Bool {
switch (lhs, rhs) {
case (.uninitialized, .uninitialized),
(.handshaking, .handshaking),
(.established, .established):
return true
case (.failed, .failed):
return true // We don't compare the errors
default:
return false
}
}
}
// MARK: - Noise Session
class NoiseSession {
let peerID: String
let peerID: PeerID
let role: NoiseRole
private let keychain: KeychainManagerProtocol
private var state: NoiseSessionState = .uninitialized
@@ -55,7 +31,7 @@ class NoiseSession {
private let sessionQueue = DispatchQueue(label: "chat.bitchat.noise.session", attributes: .concurrent)
init(
peerID: String,
peerID: PeerID,
role: NoiseRole,
keychain: KeychainManagerProtocol,
localStaticKey: Curve25519.KeyAgreement.PrivateKey,
@@ -141,7 +117,7 @@ class NoiseSession {
handshakeState = nil // Clear handshake state
SecureLogger.debug("NoiseSession[\(peerID)]: Handshake complete (no response needed), transitioning to established")
SecureLogger.info(.handshakeCompleted(peerID: peerID))
SecureLogger.info(.handshakeCompleted(peerID: peerID.id))
return nil
} else {
@@ -167,7 +143,7 @@ class NoiseSession {
handshakeState = nil // Clear handshake state
SecureLogger.debug("NoiseSession[\(peerID)]: Handshake complete after writing response, transitioning to established")
SecureLogger.info(.handshakeCompleted(peerID: peerID))
SecureLogger.info(.handshakeCompleted(peerID: peerID.id))
}
return response
@@ -252,249 +228,8 @@ class NoiseSession {
handshakeHash = nil
if wasEstablished {
SecureLogger.info(.sessionExpired(peerID: peerID))
SecureLogger.info(.sessionExpired(peerID: peerID.id))
}
}
}
}
// MARK: - Session Manager
final class NoiseSessionManager {
private var sessions: [String: NoiseSession] = [:]
private let localStaticKey: Curve25519.KeyAgreement.PrivateKey
private let keychain: KeychainManagerProtocol
private let managerQueue = DispatchQueue(label: "chat.bitchat.noise.manager", attributes: .concurrent)
// Callbacks
var onSessionEstablished: ((String, Curve25519.KeyAgreement.PublicKey) -> Void)?
var onSessionFailed: ((String, Error) -> Void)?
init(localStaticKey: Curve25519.KeyAgreement.PrivateKey, keychain: KeychainManagerProtocol) {
self.localStaticKey = localStaticKey
self.keychain = keychain
}
// MARK: - Session Management
func createSession(for peerID: String, role: NoiseRole) -> NoiseSession {
return managerQueue.sync(flags: .barrier) {
let session = SecureNoiseSession(
peerID: peerID,
role: role,
keychain: keychain,
localStaticKey: localStaticKey
)
sessions[peerID] = session
return session
}
}
func getSession(for peerID: String) -> NoiseSession? {
return managerQueue.sync {
return sessions[peerID]
}
}
func removeSession(for peerID: String) {
managerQueue.sync(flags: .barrier) {
if let session = sessions[peerID] {
if session.isEstablished() {
SecureLogger.info(.sessionExpired(peerID: peerID))
}
// Clear sensitive data before removing
session.reset()
}
_ = sessions.removeValue(forKey: peerID)
}
}
func removeAllSessions() {
managerQueue.sync(flags: .barrier) {
for (_, session) in sessions {
session.reset()
}
sessions.removeAll()
}
}
func getEstablishedSessions() -> [String: NoiseSession] {
return managerQueue.sync {
return sessions.filter { $0.value.isEstablished() }
}
}
// MARK: - Handshake Helpers
func initiateHandshake(with peerID: String) throws -> Data {
return try managerQueue.sync(flags: .barrier) {
// Check if we already have an established session
if let existingSession = sessions[peerID], existingSession.isEstablished() {
// Session already established, don't recreate
throw NoiseSessionError.alreadyEstablished
}
// Remove any existing non-established session
if let existingSession = sessions[peerID], !existingSession.isEstablished() {
_ = sessions.removeValue(forKey: peerID)
}
// Create new initiator session
let session = SecureNoiseSession(
peerID: peerID,
role: .initiator,
keychain: keychain,
localStaticKey: localStaticKey
)
sessions[peerID] = session
do {
let handshakeData = try session.startHandshake()
return handshakeData
} catch {
// Clean up failed session
_ = sessions.removeValue(forKey: peerID)
SecureLogger.error(.handshakeFailed(peerID: peerID, error: error.localizedDescription))
throw error
}
}
}
func handleIncomingHandshake(from peerID: String, message: Data) throws -> Data? {
// Process everything within the synchronized block to prevent race conditions
return try managerQueue.sync(flags: .barrier) {
var shouldCreateNew = false
var existingSession: NoiseSession? = nil
if let existing = sessions[peerID] {
// If we have an established session, the peer must have cleared their session
// for a good reason (e.g., decryption failure, restart, etc.)
// We should accept the new handshake to re-establish encryption
if existing.isEstablished() {
SecureLogger.info("Accepting handshake from \(peerID) despite existing session - peer likely cleared their session", category: .session)
_ = sessions.removeValue(forKey: peerID)
shouldCreateNew = true
} else {
// If we're in the middle of a handshake and receive a new initiation,
// reset and start fresh (the other side may have restarted)
if existing.getState() == .handshaking && message.count == 32 {
_ = sessions.removeValue(forKey: peerID)
shouldCreateNew = true
} else {
existingSession = existing
}
}
} else {
shouldCreateNew = true
}
// Get or create session
let session: NoiseSession
if shouldCreateNew {
let newSession = SecureNoiseSession(
peerID: peerID,
role: .responder,
keychain: keychain,
localStaticKey: localStaticKey
)
sessions[peerID] = newSession
session = newSession
} else {
session = existingSession!
}
// Process the handshake message within the synchronized block
do {
let response = try session.processHandshakeMessage(message)
// Check if session is established after processing
if session.isEstablished() {
if let remoteKey = session.getRemoteStaticPublicKey() {
// Schedule callback outside the synchronized block to prevent deadlock
DispatchQueue.global().async { [weak self] in
self?.onSessionEstablished?(peerID, remoteKey)
}
}
}
return response
} catch {
// Reset the session on handshake failure so next attempt can start fresh
_ = sessions.removeValue(forKey: peerID)
// Schedule callback outside the synchronized block to prevent deadlock
DispatchQueue.global().async { [weak self] in
self?.onSessionFailed?(peerID, error)
}
SecureLogger.error(.handshakeFailed(peerID: peerID, error: error.localizedDescription))
throw error
}
}
}
// MARK: - Encryption/Decryption
func encrypt(_ plaintext: Data, for peerID: String) throws -> Data {
guard let session = getSession(for: peerID) else {
throw NoiseSessionError.sessionNotFound
}
return try session.encrypt(plaintext)
}
func decrypt(_ ciphertext: Data, from peerID: String) throws -> Data {
guard let session = getSession(for: peerID) else {
throw NoiseSessionError.sessionNotFound
}
return try session.decrypt(ciphertext)
}
// MARK: - Key Management
func getRemoteStaticKey(for peerID: String) -> Curve25519.KeyAgreement.PublicKey? {
return getSession(for: peerID)?.getRemoteStaticPublicKey()
}
func getHandshakeHash(for peerID: String) -> Data? {
return getSession(for: peerID)?.getHandshakeHash()
}
// MARK: - Session Rekeying
func getSessionsNeedingRekey() -> [(peerID: String, needsRekey: Bool)] {
return managerQueue.sync {
var needingRekey: [(peerID: String, needsRekey: Bool)] = []
for (peerID, session) in sessions {
if let secureSession = session as? SecureNoiseSession,
secureSession.isEstablished(),
secureSession.needsRenegotiation() {
needingRekey.append((peerID: peerID, needsRekey: true))
}
}
return needingRekey
}
}
func initiateRekey(for peerID: String) throws {
// Remove old session
removeSession(for: peerID)
// Initiate new handshake
_ = try initiateHandshake(with: peerID)
}
}
// MARK: - Errors
enum NoiseSessionError: Error {
case invalidState
case notEstablished
case sessionNotFound
case handshakeFailed(Error)
case alreadyEstablished
}
+15
View File
@@ -0,0 +1,15 @@
//
// NoiseSessionError.swift
// bitchat
//
// This is free and unencumbered software released into the public domain.
// For more information, see <https://unlicense.org>
//
enum NoiseSessionError: Error {
case invalidState
case notEstablished
case sessionNotFound
case handshakeFailed(Error)
case alreadyEstablished
}
+239
View File
@@ -0,0 +1,239 @@
//
// NoiseSessionManager.swift
// bitchat
//
// This is free and unencumbered software released into the public domain.
// For more information, see <https://unlicense.org>
//
import BitLogger
import CryptoKit
import Foundation
final class NoiseSessionManager {
private var sessions: [PeerID: NoiseSession] = [:]
private let localStaticKey: Curve25519.KeyAgreement.PrivateKey
private let keychain: KeychainManagerProtocol
private let managerQueue = DispatchQueue(label: "chat.bitchat.noise.manager", attributes: .concurrent)
// Callbacks
var onSessionEstablished: ((PeerID, Curve25519.KeyAgreement.PublicKey) -> Void)?
var onSessionFailed: ((PeerID, Error) -> Void)?
init(localStaticKey: Curve25519.KeyAgreement.PrivateKey, keychain: KeychainManagerProtocol) {
self.localStaticKey = localStaticKey
self.keychain = keychain
}
// MARK: - Session Management
func createSession(for peerID: PeerID, role: NoiseRole) -> NoiseSession {
return managerQueue.sync(flags: .barrier) {
let session = SecureNoiseSession(
peerID: peerID,
role: role,
keychain: keychain,
localStaticKey: localStaticKey
)
sessions[peerID] = session
return session
}
}
func getSession(for peerID: PeerID) -> NoiseSession? {
return managerQueue.sync {
return sessions[peerID]
}
}
func removeSession(for peerID: PeerID) {
managerQueue.sync(flags: .barrier) {
if let session = sessions[peerID] {
if session.isEstablished() {
SecureLogger.info(.sessionExpired(peerID: peerID.id))
}
// Clear sensitive data before removing
session.reset()
}
_ = sessions.removeValue(forKey: peerID)
}
}
func removeAllSessions() {
managerQueue.sync(flags: .barrier) {
for (_, session) in sessions {
session.reset()
}
sessions.removeAll()
}
}
func getEstablishedSessions() -> [PeerID: NoiseSession] {
return managerQueue.sync {
return sessions.filter { $0.value.isEstablished() }
}
}
// MARK: - Handshake Helpers
func initiateHandshake(with peerID: PeerID) throws -> Data {
return try managerQueue.sync(flags: .barrier) {
// Check if we already have an established session
if let existingSession = sessions[peerID], existingSession.isEstablished() {
// Session already established, don't recreate
throw NoiseSessionError.alreadyEstablished
}
// Remove any existing non-established session
if let existingSession = sessions[peerID], !existingSession.isEstablished() {
_ = sessions.removeValue(forKey: peerID)
}
// Create new initiator session
let session = SecureNoiseSession(
peerID: peerID,
role: .initiator,
keychain: keychain,
localStaticKey: localStaticKey
)
sessions[peerID] = session
do {
let handshakeData = try session.startHandshake()
return handshakeData
} catch {
// Clean up failed session
_ = sessions.removeValue(forKey: peerID)
SecureLogger.error(.handshakeFailed(peerID: peerID.id, error: error.localizedDescription))
throw error
}
}
}
func handleIncomingHandshake(from peerID: PeerID, message: Data) throws -> Data? {
// Process everything within the synchronized block to prevent race conditions
return try managerQueue.sync(flags: .barrier) {
var shouldCreateNew = false
var existingSession: NoiseSession? = nil
if let existing = sessions[peerID] {
// If we have an established session, the peer must have cleared their session
// for a good reason (e.g., decryption failure, restart, etc.)
// We should accept the new handshake to re-establish encryption
if existing.isEstablished() {
SecureLogger.info("Accepting handshake from \(peerID) despite existing session - peer likely cleared their session", category: .session)
_ = sessions.removeValue(forKey: peerID)
shouldCreateNew = true
} else {
// If we're in the middle of a handshake and receive a new initiation,
// reset and start fresh (the other side may have restarted)
if existing.getState() == .handshaking && message.count == 32 {
_ = sessions.removeValue(forKey: peerID)
shouldCreateNew = true
} else {
existingSession = existing
}
}
} else {
shouldCreateNew = true
}
// Get or create session
let session: NoiseSession
if shouldCreateNew {
let newSession = SecureNoiseSession(
peerID: peerID,
role: .responder,
keychain: keychain,
localStaticKey: localStaticKey
)
sessions[peerID] = newSession
session = newSession
} else {
session = existingSession!
}
// Process the handshake message within the synchronized block
do {
let response = try session.processHandshakeMessage(message)
// Check if session is established after processing
if session.isEstablished() {
if let remoteKey = session.getRemoteStaticPublicKey() {
// Schedule callback outside the synchronized block to prevent deadlock
DispatchQueue.global().async { [weak self] in
self?.onSessionEstablished?(peerID, remoteKey)
}
}
}
return response
} catch {
// Reset the session on handshake failure so next attempt can start fresh
_ = sessions.removeValue(forKey: peerID)
// Schedule callback outside the synchronized block to prevent deadlock
DispatchQueue.global().async { [weak self] in
self?.onSessionFailed?(peerID, error)
}
SecureLogger.error(.handshakeFailed(peerID: peerID.id, error: error.localizedDescription))
throw error
}
}
}
// MARK: - Encryption/Decryption
func encrypt(_ plaintext: Data, for peerID: PeerID) throws -> Data {
guard let session = getSession(for: peerID) else {
throw NoiseSessionError.sessionNotFound
}
return try session.encrypt(plaintext)
}
func decrypt(_ ciphertext: Data, from peerID: PeerID) throws -> Data {
guard let session = getSession(for: peerID) else {
throw NoiseSessionError.sessionNotFound
}
return try session.decrypt(ciphertext)
}
// MARK: - Key Management
func getRemoteStaticKey(for peerID: PeerID) -> Curve25519.KeyAgreement.PublicKey? {
return getSession(for: peerID)?.getRemoteStaticPublicKey()
}
func getHandshakeHash(for peerID: PeerID) -> Data? {
return getSession(for: peerID)?.getHandshakeHash()
}
// MARK: - Session Rekeying
func getSessionsNeedingRekey() -> [(peerID: PeerID, needsRekey: Bool)] {
return managerQueue.sync {
var needingRekey: [(peerID: PeerID, needsRekey: Bool)] = []
for (peerID, session) in sessions {
if let secureSession = session as? SecureNoiseSession,
secureSession.isEstablished(),
secureSession.needsRenegotiation() {
needingRekey.append((peerID: peerID, needsRekey: true))
}
}
return needingRekey
}
}
func initiateRekey(for peerID: PeerID) throws {
// Remove old session
removeSession(for: peerID)
// Initiate new handshake
_ = try initiateHandshake(with: peerID)
}
}
+13
View File
@@ -0,0 +1,13 @@
//
// NoiseSessionState.swift
// bitchat
//
// This is free and unencumbered software released into the public domain.
// For more information, see <https://unlicense.org>
//
enum NoiseSessionState: Equatable {
case uninitialized
case handshaking
case established
}
+10 -10
View File
@@ -178,7 +178,7 @@ final class NoiseEncryptionService {
// Callbacks
private var onPeerAuthenticatedHandlers: [((String, String) -> Void)] = [] // Array of handlers for peer authentication
var onHandshakeRequired: ((String) -> Void)? // peerID needs handshake
var onHandshakeRequired: ((PeerID) -> Void)? // peerID needs handshake
// Add a handler for peer authentication
func addOnPeerAuthenticatedHandler(_ handler: @escaping (String, String) -> Void) {
@@ -251,7 +251,7 @@ final class NoiseEncryptionService {
// Set up session callbacks
sessionManager.onSessionEstablished = { [weak self] peerID, remoteStaticKey in
self?.handleSessionEstablished(peerID: PeerID(str: peerID), remoteStaticKey: remoteStaticKey)
self?.handleSessionEstablished(peerID: peerID, remoteStaticKey: remoteStaticKey)
}
// Start session maintenance timer
@@ -277,7 +277,7 @@ final class NoiseEncryptionService {
/// Get peer's public key data
func getPeerPublicKeyData(_ peerID: PeerID) -> Data? {
return sessionManager.getRemoteStaticKey(for: peerID.id)?.rawRepresentation
return sessionManager.getRemoteStaticKey(for: peerID)?.rawRepresentation
}
/// Clear persistent identity (for panic mode)
@@ -422,7 +422,7 @@ final class NoiseEncryptionService {
// Return raw handshake data without wrapper
// The Noise protocol handles its own message format
let handshakeData = try sessionManager.initiateHandshake(with: peerID.id)
let handshakeData = try sessionManager.initiateHandshake(with: peerID)
return handshakeData
}
@@ -449,7 +449,7 @@ final class NoiseEncryptionService {
// For handshakes, we process the raw data directly without NoiseMessage wrapper
// The Noise protocol handles its own message format
let responsePayload = try sessionManager.handleIncomingHandshake(from: peerID.id, message: message)
let responsePayload = try sessionManager.handleIncomingHandshake(from: peerID, message: message)
// Return raw response without wrapper
@@ -458,12 +458,12 @@ final class NoiseEncryptionService {
/// Check if we have an established session with a peer
func hasEstablishedSession(with peerID: PeerID) -> Bool {
return sessionManager.getSession(for: peerID.id)?.isEstablished() ?? false
return sessionManager.getSession(for: peerID)?.isEstablished() ?? false
}
/// Check if we have a session (established or handshaking) with a peer
func hasSession(with peerID: PeerID) -> Bool {
return sessionManager.getSession(for: peerID.id) != nil
return sessionManager.getSession(for: peerID) != nil
}
// MARK: - Encryption/Decryption
@@ -483,11 +483,11 @@ final class NoiseEncryptionService {
// Check if we have an established session
guard hasEstablishedSession(with: peerID) else {
// Signal that handshake is needed
onHandshakeRequired?(peerID.id)
onHandshakeRequired?(peerID)
throw NoiseEncryptionError.handshakeRequired
}
return try sessionManager.encrypt(data, for: peerID.id)
return try sessionManager.encrypt(data, for: peerID)
}
/// Decrypt data from a specific peer
@@ -507,7 +507,7 @@ final class NoiseEncryptionService {
throw NoiseEncryptionError.sessionNotEstablished
}
return try sessionManager.decrypt(data, from: peerID.id)
return try sessionManager.decrypt(data, from: peerID)
}
// MARK: - Peer Management
+2 -2
View File
@@ -4380,10 +4380,10 @@ final class ChatViewModel: ObservableObject, BitchatDelegate {
noiseService.onHandshakeRequired = { [weak self] peerID in
DispatchQueue.main.async {
guard let self = self else { return }
self.peerEncryptionStatus[peerID] = .noiseHandshaking
self.peerEncryptionStatus[peerID.id] = .noiseHandshaking
// Invalidate cache when encryption status changes
self.invalidateEncryptionCache(for: peerID)
self.invalidateEncryptionCache(for: peerID.id)
}
}
}
@@ -301,9 +301,9 @@ final class PrivateChatE2ETests: XCTestCase {
// MARK: - Helper Methods
private func createMockService(peerID: String, nickname: String) -> MockBluetoothMeshService {
private func createMockService(peerID: PeerID, nickname: String) -> MockBluetoothMeshService {
let service = MockBluetoothMeshService()
service.myPeerID = peerID
service.myPeerID = peerID.id
service.mockNickname = nickname
return service
}
@@ -200,9 +200,9 @@ final class PublicChatE2ETests: XCTestCase {
}
// Inject at Bob with TTL=2 so Charlie sees it (TTL->1) and does not relay to David
let msg = TestHelpers.createTestMessage(content: TestConstants.testMessage1, sender: TestConstants.testNickname1, senderPeerID: alice.peerID)
let msg = TestHelpers.createTestMessage(content: TestConstants.testMessage1, sender: TestConstants.testNickname1, senderPeerID: PeerID(str: alice.peerID))
if let payload = msg.toBinaryPayload() {
let pkt = TestHelpers.createTestPacket(senderID: alice.peerID, payload: payload, ttl: 2)
let pkt = TestHelpers.createTestPacket(senderID: PeerID(str: alice.peerID), payload: payload, ttl: 2)
bob.simulateIncomingPacket(pkt)
}
@@ -419,9 +419,9 @@ final class PublicChatE2ETests: XCTestCase {
// MARK: - Helper Methods
private func createMockService(peerID: String, nickname: String) -> MockBluetoothMeshService {
private func createMockService(peerID: PeerID, nickname: String) -> MockBluetoothMeshService {
let service = MockBluetoothMeshService()
service.myPeerID = peerID
service.myPeerID = peerID.id
service.mockNickname = nickname
return service
}
@@ -484,10 +484,13 @@ final class IntegrationTests: XCTestCase {
guard let aliceManager = noiseManagers["Alice"],
let bobManager = noiseManagers["Bob"],
let alicePeerID = nodes["Alice"]?.peerID,
let bobPeerID = nodes["Bob"]?.peerID else {
let aliceStringPeerID = nodes["Alice"]?.peerID,
let bobStringPeerID = nodes["Bob"]?.peerID else {
return XCTFail("Missing managers or peer IDs")
}
let alicePeerID = PeerID(str: aliceStringPeerID)
let bobPeerID = PeerID(str: bobStringPeerID)
// Baseline: encrypt from Alice, decrypt at Bob
let plaintext1 = Data("hello-secure".utf8)
@@ -587,9 +590,9 @@ final class IntegrationTests: XCTestCase {
// MARK: - Helper Methods
private func createNode(_ name: String, peerID: String) {
private func createNode(_ name: String, peerID: PeerID) {
let node = MockBluetoothMeshService()
node.myPeerID = peerID
node.myPeerID = peerID.id
node.mockNickname = name
nodes[name] = node
@@ -666,9 +669,9 @@ final class IntegrationTests: XCTestCase {
let peer1ID = nodes[node1]?.peerID,
let peer2ID = nodes[node2]?.peerID else { return }
let msg1 = try manager1.initiateHandshake(with: peer2ID)
let msg2 = try manager2.handleIncomingHandshake(from: peer1ID, message: msg1)!
let msg3 = try manager1.handleIncomingHandshake(from: peer2ID, message: msg2)!
_ = try manager2.handleIncomingHandshake(from: peer1ID, message: msg3)
let msg1 = try manager1.initiateHandshake(with: PeerID(str: peer2ID))
let msg2 = try manager2.handleIncomingHandshake(from: PeerID(str: peer1ID), message: msg1)!
let msg3 = try manager1.handleIncomingHandshake(from: PeerID(str: peer2ID), message: msg2)!
_ = try manager2.handleIncomingHandshake(from: PeerID(str: peer1ID), message: msg3)
}
}
+2 -2
View File
@@ -342,8 +342,8 @@ final class MockBLEService: NSObject {
// MARK: - Compatibility methods for old tests
func sendPrivateMessage(_ content: String, to recipientPeerID: String, recipientNickname: String, messageID: String? = nil) {
sendPrivateMessage(content, to: recipientPeerID, recipientNickname: recipientNickname, messageID: messageID ?? UUID().uuidString)
func sendPrivateMessage(_ content: String, to recipientPeerID: PeerID, recipientNickname: String, messageID: String? = nil) {
sendPrivateMessage(content, to: recipientPeerID.id, recipientNickname: recipientNickname, messageID: messageID ?? UUID().uuidString)
}
}
@@ -54,7 +54,7 @@ final class BinaryProtocolTests: XCTestCase {
// Verify recipient
XCTAssertNotNil(decodedPacket.recipientID)
let decodedRecipientID = decodedPacket.recipientID?.trimmingNullBytes()
XCTAssertEqual(String(data: decodedRecipientID!, encoding: .utf8), recipientID)
XCTAssertTrue(String(data: decodedRecipientID!, encoding: .utf8) == recipientID)
}
func testPacketWithSignature() throws {
@@ -251,7 +251,7 @@ final class BinaryProtocolTests: XCTestCase {
originalSender: TestConstants.testNickname3,
isPrivate: false,
recipientNickname: nil,
senderPeerID: TestConstants.testPeerID1,
senderPeerID: TestConstants.testPeerID1.id,
mentions: nil
)
@@ -7,16 +7,17 @@
//
import Foundation
@testable import bitchat
struct TestConstants {
static let defaultTimeout: TimeInterval = 5.0
static let shortTimeout: TimeInterval = 1.0
static let longTimeout: TimeInterval = 10.0
static let testPeerID1 = "PEER1234"
static let testPeerID2 = "PEER5678"
static let testPeerID3 = "PEER9012"
static let testPeerID4 = "PEER3456"
static let testPeerID1: PeerID = "PEER1234"
static let testPeerID2: PeerID = "PEER5678"
static let testPeerID3: PeerID = "PEER9012"
static let testPeerID4: PeerID = "PEER3456"
static let testNickname1 = "Alice"
static let testNickname2 = "Bob"
+19 -7
View File
@@ -30,7 +30,7 @@ final class TestHelpers {
static func createTestMessage(
content: String = TestConstants.testMessage1,
sender: String = TestConstants.testNickname1,
senderPeerID: String = TestConstants.testPeerID1,
senderPeerID: PeerID = TestConstants.testPeerID1,
isPrivate: Bool = false,
recipientNickname: String? = nil,
mentions: [String]? = nil
@@ -44,23 +44,23 @@ final class TestHelpers {
originalSender: nil,
isPrivate: isPrivate,
recipientNickname: recipientNickname,
senderPeerID: senderPeerID,
senderPeerID: senderPeerID.id,
mentions: mentions
)
}
static func createTestPacket(
type: UInt8 = 0x01,
senderID: String = TestConstants.testPeerID1,
recipientID: String? = nil,
senderID: PeerID = TestConstants.testPeerID1,
recipientID: PeerID? = nil,
payload: Data = "test payload".data(using: .utf8)!,
signature: Data? = nil,
ttl: UInt8 = 3
) -> BitchatPacket {
return BitchatPacket(
type: type,
senderID: senderID.data(using: .utf8)!,
recipientID: recipientID?.data(using: .utf8),
senderID: senderID.id.data(using: .utf8)!,
recipientID: recipientID?.id.data(using: .utf8),
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: payload,
signature: signature,
@@ -119,4 +119,16 @@ enum TestError: Error {
case timeout
case unexpectedValue
case testFailure(String)
}
}
// MARK: - PeerID String Helpers
/// Raw String can be passed as PeerID
extension PeerID: @retroactive ExpressibleByStringLiteral {
public init(stringLiteral value: String) {
self.init(str: value)
}
}
/// Interpolated String can be passed as PeerID
extension PeerID: @retroactive ExpressibleByStringInterpolation {}