Merge upstream/main: Adopt channel terminology while preserving bug fixes

This commit is contained in:
Nelson Campos
2025-07-07 23:27:12 -03:00
15 changed files with 1959 additions and 1183 deletions
+66 -49
View File
@@ -249,6 +249,11 @@ class BluetoothMeshService: NSObject {
return String(fingerprint)
}
// Public method to get peer's public key data
func getPeerPublicKey(_ peerID: String) -> Data? {
return encryptionService.getPeerIdentityKey(peerID)
}
override init() {
// Generate ephemeral peer ID for each session to prevent tracking
// Use random bytes instead of UUID for better anonymity
@@ -490,7 +495,9 @@ class BluetoothMeshService: NSObject {
self.characteristic = characteristic
}
func sendMessage(_ content: String, mentions: [String] = [], room: String? = nil, to recipientID: String? = nil, messageID: String? = nil, timestamp: Date? = nil) {
func sendMessage(_ content: String, mentions: [String] = [], channel: String? = nil, to recipientID: String? = nil, messageID: String? = nil, timestamp: Date? = nil) {
// Defensive check for empty content
guard !content.isEmpty else { return }
messageQueue.async { [weak self] in
guard let self = self else { return }
@@ -508,7 +515,7 @@ class BluetoothMeshService: NSObject {
recipientNickname: nil,
senderPeerID: self.myPeerID,
mentions: mentions.isEmpty ? nil : mentions,
room: room
channel: channel
)
if let messageData = message.toBinaryPayload() {
@@ -570,6 +577,9 @@ class BluetoothMeshService: NSObject {
func sendPrivateMessage(_ content: String, to recipientPeerID: String, recipientNickname: String, messageID: String? = nil) {
// Defensive checks
guard !content.isEmpty, !recipientPeerID.isEmpty, !recipientNickname.isEmpty else { return }
messageQueue.async { [weak self] in
guard let self = self else { return }
@@ -628,7 +638,11 @@ class BluetoothMeshService: NSObject {
// Check if recipient is offline and cache if they're a favorite
if !self.activePeers.contains(recipientPeerID) {
self.activePeersLock.lock()
let isRecipientOffline = !self.activePeers.contains(recipientPeerID)
self.activePeersLock.unlock()
if isRecipientOffline {
if let publicKeyData = self.encryptionService.getPeerIdentityKey(recipientPeerID) {
let fingerprint = self.getPublicKeyFingerprint(publicKeyData)
if self.delegate?.isFavorite(fingerprint: fingerprint) ?? false {
@@ -673,17 +687,17 @@ class BluetoothMeshService: NSObject {
}
}
func sendRoomLeaveNotification(_ room: String) {
func sendChannelLeaveNotification(_ channel: String) {
messageQueue.async { [weak self] in
guard let self = self else { return }
// Create a leave packet with room hashtag as payload
// Create a leave packet with channel hashtag as payload
let packet = BitchatPacket(
type: MessageType.leave.rawValue,
senderID: Data(self.myPeerID.utf8),
recipientID: SpecialRecipients.broadcast, // Broadcast to all
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: Data(room.utf8), // Room hashtag as payload
payload: Data(channel.utf8), // Channel hashtag as payload
signature: nil,
ttl: 3 // Short TTL for leave notifications
)
@@ -758,53 +772,53 @@ class BluetoothMeshService: NSObject {
}
}
func announcePasswordProtectedRoom(_ room: String, isProtected: Bool = true, creatorID: String? = nil, keyCommitment: String? = nil) {
func announcePasswordProtectedChannel(_ channel: String, isProtected: Bool = true, creatorID: String? = nil, keyCommitment: String? = nil) {
messageQueue.async { [weak self] in
guard let self = self else { return }
// Payload format: room|isProtected|creatorID|keyCommitment
// Payload format: channel|isProtected|creatorID|keyCommitment
let protectedFlag = isProtected ? "1" : "0"
let creator = creatorID ?? self.myPeerID
let commitment = keyCommitment ?? ""
let payload = "\(room)|\(protectedFlag)|\(creator)|\(commitment)"
let payload = "\(channel)|\(protectedFlag)|\(creator)|\(commitment)"
let packet = BitchatPacket(
type: MessageType.roomAnnounce.rawValue,
type: MessageType.channelAnnounce.rawValue,
senderID: Data(self.myPeerID.utf8),
recipientID: SpecialRecipients.broadcast,
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: Data(payload.utf8),
signature: nil,
ttl: 5 // Allow wider propagation for room announcements
ttl: 5 // Allow wider propagation for channel announcements
)
self.broadcastPacket(packet)
}
}
func sendRoomRetentionAnnouncement(_ room: String, enabled: Bool) {
func sendChannelRetentionAnnouncement(_ channel: String, enabled: Bool) {
messageQueue.async { [weak self] in
guard let self = self else { return }
// Payload format: room|enabled|creatorID
// Payload format: channel|enabled|creatorID
let enabledFlag = enabled ? "1" : "0"
let payload = "\(room)|\(enabledFlag)|\(self.myPeerID)"
let payload = "\(channel)|\(enabledFlag)|\(self.myPeerID)"
let packet = BitchatPacket(
type: MessageType.roomRetention.rawValue,
type: MessageType.channelRetention.rawValue,
senderID: Data(self.myPeerID.utf8),
recipientID: SpecialRecipients.broadcast,
timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
payload: Data(payload.utf8),
signature: nil,
ttl: 5 // Allow wider propagation for room announcements
ttl: 5 // Allow wider propagation for channel announcements
)
self.broadcastPacket(packet)
}
}
func sendEncryptedRoomMessage(_ content: String, mentions: [String], room: String, roomKey: SymmetricKey, messageID: String? = nil, timestamp: Date? = nil) {
func sendEncryptedChannelMessage(_ content: String, mentions: [String], channel: String, channelKey: SymmetricKey, messageID: String? = nil, timestamp: Date? = nil) {
messageQueue.async { [weak self] in
guard let self = self else { return }
@@ -817,8 +831,11 @@ class BluetoothMeshService: NSObject {
// Debug logging removed
do {
let sealedBox = try AES.GCM.seal(contentData, using: roomKey)
let encryptedData = sealedBox.combined!
let sealedBox = try AES.GCM.seal(contentData, using: channelKey)
guard let encryptedData = sealedBox.combined else {
// Encryption failed to produce combined data
return
}
// Create message with encrypted content
let message = BitchatMessage(
@@ -832,7 +849,7 @@ class BluetoothMeshService: NSObject {
recipientNickname: nil,
senderPeerID: self.myPeerID,
mentions: mentions.isEmpty ? nil : mentions,
room: room,
channel: channel,
encryptedContent: encryptedData,
isEncrypted: true
)
@@ -1267,7 +1284,7 @@ class BluetoothMeshService: NSObject {
MessageRetryService.shared.addMessageForRetry(
content: message.content,
mentions: message.mentions,
room: message.room,
channel: message.channel,
isPrivate: message.isPrivate,
recipientPeerID: nil,
recipientNickname: message.recipientNickname,
@@ -1328,24 +1345,24 @@ class BluetoothMeshService: NSObject {
// This is our own message that failed to send
if packet.type == MessageType.message.rawValue,
let message = BitchatMessage.fromBinaryPayload(packet.payload) {
// For encrypted room messages, we need to preserve the room key
var roomKeyData: Data? = nil
if let room = message.room, message.isEncrypted {
// This is an encrypted room message
// For encrypted channel messages, we need to preserve the channel key
var channelKeyData: Data? = nil
if let channel = message.channel, message.isEncrypted {
// This is an encrypted channel message
if let viewModel = delegate as? ChatViewModel,
let roomKey = viewModel.roomKeys[room] {
roomKeyData = roomKey.withUnsafeBytes { Data($0) }
let channelKey = viewModel.channelKeys[channel] {
channelKeyData = channelKey.withUnsafeBytes { Data($0) }
}
}
MessageRetryService.shared.addMessageForRetry(
content: message.content,
mentions: message.mentions,
room: message.room,
channel: message.channel,
isPrivate: message.isPrivate,
recipientPeerID: nil,
recipientNickname: message.recipientNickname,
roomKey: roomKeyData,
channelKey: channelKeyData,
originalMessageID: message.id,
originalTimestamp: message.timestamp
)
@@ -1463,11 +1480,11 @@ class BluetoothMeshService: NSObject {
peerNicknames[senderID] = message.sender
peerNicknamesLock.unlock()
// Handle encrypted room messages
// Handle encrypted channel messages
var finalContent = message.content
if message.isEncrypted, let room = message.room, let encryptedData = message.encryptedContent {
if message.isEncrypted, let channel = message.channel, let encryptedData = message.encryptedContent {
// Try to decrypt the content
if let decryptedContent = self.delegate?.decryptRoomMessage(encryptedData, room: room) {
if let decryptedContent = self.delegate?.decryptChannelMessage(encryptedData, channel: channel) {
finalContent = decryptedContent
} else {
// Unable to decrypt - show placeholder
@@ -1486,7 +1503,7 @@ class BluetoothMeshService: NSObject {
recipientNickname: nil,
senderPeerID: senderID,
mentions: message.mentions,
room: message.room,
channel: message.channel,
encryptedContent: message.encryptedContent,
isEncrypted: message.isEncrypted
)
@@ -1500,10 +1517,10 @@ class BluetoothMeshService: NSObject {
self.delegate?.didReceiveMessage(messageWithPeerID)
}
// Generate and send ACK for room messages if we're mentioned or it's a small room
// Generate and send ACK for channel messages if we're mentioned or it's a small channel
let viewModel = self.delegate as? ChatViewModel
let myNickname = viewModel?.nickname ?? self.myPeerID
if let _ = message.room,
if let _ = message.channel,
let mentions = message.mentions,
(mentions.contains(myNickname) || self.activePeers.count < 10) {
if let ack = DeliveryTracker.shared.generateAck(
@@ -1608,7 +1625,7 @@ class BluetoothMeshService: NSObject {
recipientNickname: message.recipientNickname,
senderPeerID: senderID,
mentions: message.mentions,
room: message.room,
channel: message.channel,
deliveryStatus: nil // Will be set to .delivered in ChatViewModel
)
@@ -1898,13 +1915,13 @@ class BluetoothMeshService: NSObject {
case .leave:
if let senderID = String(data: packet.senderID.trimmingNullBytes(), encoding: .utf8) {
// Check if payload contains a room hashtag
if let room = String(data: packet.payload, encoding: .utf8),
room.hasPrefix("#") {
// Room leave notification
// Check if payload contains a channel hashtag
if let channel = String(data: packet.payload, encoding: .utf8),
channel.hasPrefix("#") {
// Channel leave notification
DispatchQueue.main.async {
self.delegate?.didReceiveRoomLeave(room, from: senderID)
self.delegate?.didReceiveChannelLeave(channel, from: senderID)
}
// Relay if TTL > 0
@@ -1955,19 +1972,19 @@ class BluetoothMeshService: NSObject {
self.broadcastPacket(relayPacket)
}
case .roomAnnounce:
case .channelAnnounce:
if let payloadStr = String(data: packet.payload, encoding: .utf8) {
// Parse payload: room|isProtected|creatorID|keyCommitment
// Parse payload: channel|isProtected|creatorID|keyCommitment
let components = payloadStr.split(separator: "|").map(String.init)
if components.count >= 3 {
let room = components[0]
let channel = components[0]
let isProtected = components[1] == "1"
let creatorID = components[2]
let keyCommitment = components.count >= 4 ? components[3] : nil
DispatchQueue.main.async {
self.delegate?.didReceivePasswordProtectedRoomAnnouncement(room, isProtected: isProtected, creatorID: creatorID, keyCommitment: keyCommitment)
self.delegate?.didReceivePasswordProtectedChannelAnnouncement(channel, isProtected: isProtected, creatorID: creatorID, keyCommitment: keyCommitment)
}
// Relay announcement
@@ -2008,18 +2025,18 @@ class BluetoothMeshService: NSObject {
self.broadcastPacket(relayPacket)
}
case .roomRetention:
case .channelRetention:
if let payloadStr = String(data: packet.payload, encoding: .utf8) {
// Parse payload: room|enabled|creatorID
// Parse payload: channel|enabled|creatorID
let components = payloadStr.split(separator: "|").map(String.init)
if components.count >= 3 {
let room = components[0]
let channel = components[0]
let enabled = components[1] == "1"
let creatorID = components[2]
DispatchQueue.main.async {
self.delegate?.didReceiveRoomRetentionAnnouncement(room, enabled: enabled, creatorID: creatorID)
self.delegate?.didReceiveChannelRetentionAnnouncement(channel, enabled: enabled, creatorID: creatorID)
}
// Relay announcement