mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 23:45:20 +00:00
Merge upstream/main: Adopt channel terminology while preserving bug fixes
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user