diff --git a/bitchat/Services/BLEService.swift b/bitchat/Services/BLEService.swift index ad5eb621..4d67a3de 100644 --- a/bitchat/Services/BLEService.swift +++ b/bitchat/Services/BLEService.swift @@ -707,7 +707,7 @@ final class BLEService: NSObject { sendPrivateMessage(content, to: peerID.id, messageID: messageID) - func sendFileBroadcast(_ filePacket: BitchatFilePacket) { + func sendFileBroadcast(_ filePacket: BitchatFilePacket, transferId: String) { messageQueue.async { [weak self] in guard let self = self else { return } guard let payload = filePacket.encode() else { @@ -731,12 +731,12 @@ final class BLEService: NSObject { self.messageDeduplicator.markProcessed(dedupID) SecureLogger.debug("📁 Broadcasting file transfer payload bytes=\(payload.count)", category: .session) - self.broadcastPacket(packet) + self.broadcastPacket(packet, transferId: transferId) self.gossipSyncManager?.onPublicPacketSeen(packet) } } - func sendFilePrivate(_ filePacket: BitchatFilePacket, to peerID: PeerID) { + func sendFilePrivate(_ filePacket: BitchatFilePacket, to peerID: PeerID, transferId: String) { messageQueue.async { [weak self] in guard let self = self else { return } guard let payload = filePacket.encode() else { @@ -764,7 +764,7 @@ final class BLEService: NSObject { } SecureLogger.debug("📁 Sending private file transfer to \(peerID.prefix(8))… bytes=\(payload.count)", category: .session) - self.broadcastPacket(packet) + self.broadcastPacket(packet, transferId: transferId) } } @@ -841,11 +841,11 @@ final class BLEService: NSObject { // MARK: - Packet Broadcasting - private func broadcastPacket(_ packet: BitchatPacket) { + private func broadcastPacket(_ packet: BitchatPacket, transferId: String? = nil) { // Encode once using a small per-type padding policy, then delegate by type let padForBLE = padPolicy(for: packet.type) if packet.type == MessageType.fileTransfer.rawValue { - sendFragmentedPacket(packet, pad: padForBLE, maxChunk: nil, directedOnlyPeer: nil) + sendFragmentedPacket(packet, pad: padForBLE, maxChunk: nil, directedOnlyPeer: nil, transferId: transferId) return } guard let data = packet.toBinaryData(padding: padForBLE) else { @@ -1106,7 +1106,7 @@ final class BLEService: NSObject { // MARK: - Fragmentation (Required for messages > BLE MTU) - private func sendFragmentedPacket(_ packet: BitchatPacket, pad: Bool, maxChunk: Int? = nil, directedOnlyPeer: String? = nil) { + private func sendFragmentedPacket(_ packet: BitchatPacket, pad: Bool, maxChunk: Int? = nil, directedOnlyPeer: String? = nil, transferId: String? = nil) { guard let fullData = packet.toBinaryData(padding: pad) else { return } // Fragment the unpadded frame; each fragment will be encoded independently @@ -1134,9 +1134,9 @@ final class BLEService: NSObject { } let perFragMs = (directedOnlyPeer != nil || packet.recipientID != nil) ? TransportConfig.bleFragmentSpacingDirectedMs : TransportConfig.bleFragmentSpacingMs - let transferId: String? = { + let transferIdentifier: String? = { guard packet.type == MessageType.fileTransfer.rawValue else { return nil } - let id = packet.payload.sha256Hex() + let id = transferId ?? packet.payload.sha256Hex() collectionsQueue.sync(flags: .barrier) { self.activeTransfers[id] = ActiveTransferState(totalFragments: totalFragments, sentFragments: 0, workItems: []) } @@ -1171,12 +1171,12 @@ final class BLEService: NSObject { let workItem = DispatchWorkItem { [weak self] in guard let self = self else { return } - if let transferId = transferId { + if let transferId = transferIdentifier { let isActive = self.collectionsQueue.sync { self.activeTransfers[transferId] != nil } guard isActive else { return } } self.broadcastPacket(fragmentPacket) - if let transferId = transferId { + if let transferId = transferIdentifier { self.markFragmentSent(transferId: transferId) } } @@ -1184,7 +1184,7 @@ final class BLEService: NSObject { scheduledItems.append((item: workItem, index: index)) } - if let transferId = transferId { + if let transferId = transferIdentifier { let workItems = scheduledItems.map { $0.item } collectionsQueue.async(flags: .barrier) { [weak self] in guard let self = self, var state = self.activeTransfers[transferId] else { return } diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index b507f33a..1d268d3f 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -2404,6 +2404,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { let targetPeer = selectedPrivateChatPeer let message = enqueueMediaMessage(content: "[voice] \(url.path)", targetPeer: targetPeer) let messageID = message.id + let transferId = makeTransferID(messageID: messageID) Task.detached(priority: .userInitiated) { [weak self] in guard let self = self else { return } @@ -2424,13 +2425,12 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { content: data ) guard let payload = packet.encode() else { throw MediaSendError.encodingFailed } - let transferId = payload.sha256Hex() await MainActor.run { self.registerTransfer(transferId: transferId, messageID: messageID) if let peerID = targetPeer { - self.meshService.sendFilePrivate(packet, to: peerID) + self.meshService.sendFilePrivate(packet, to: peerID, transferId: transferId) } else { - self.meshService.sendFileBroadcast(packet) + self.meshService.sendFileBroadcast(packet, transferId: transferId) } } } catch { @@ -2468,15 +2468,15 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { content: data ) guard let payload = packet.encode() else { throw MediaSendError.encodingFailed } - let transferId = payload.sha256Hex() await MainActor.run { let message = self.enqueueMediaMessage(content: "[image] \(outputURL.path)", targetPeer: targetPeer) let messageID = message.id + let transferId = self.makeTransferID(messageID: messageID) self.registerTransfer(transferId: transferId, messageID: messageID) if let peerID = targetPeer { - self.meshService.sendFilePrivate(packet, to: peerID) + self.meshService.sendFilePrivate(packet, to: peerID, transferId: transferId) } else { - self.meshService.sendFileBroadcast(packet) + self.meshService.sendFileBroadcast(packet, transferId: transferId) } } } catch { @@ -2518,16 +2518,16 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { try? FileManager.default.removeItem(at: destination) throw MediaSendError.encodingFailed } - let transferId = payload.sha256Hex() await MainActor.run { let message = self.enqueueMediaMessage(content: "[file] \(destination.path)", targetPeer: targetPeer) let messageID = message.id + let transferId = self.makeTransferID(messageID: messageID) self.registerTransfer(transferId: transferId, messageID: messageID) if let peerID = targetPeer { - self.meshService.sendFilePrivate(packet, to: peerID) + self.meshService.sendFilePrivate(packet, to: peerID, transferId: transferId) } else { - self.meshService.sendFileBroadcast(packet) + self.meshService.sendFileBroadcast(packet, transferId: transferId) } } } catch MediaSendError.tooLarge { @@ -2655,6 +2655,10 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { messageIDToTransferId[messageID] = transferId } + private func makeTransferID(messageID: String) -> String { + "\(messageID)-\(UUID().uuidString)" + } + @MainActor private func clearTransferMapping(for messageID: String) { guard let transferId = messageIDToTransferId.removeValue(forKey: messageID) else { return }