From 09b4b7d388961d406bcd79b3969bf7c2ccb7ac19 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 24 Mar 2021 13:28:30 +1100 Subject: [PATCH] Update MessageSender for V2 open groups --- SessionMessagingKit/Jobs/MessageSendJob.swift | 8 ++ .../Messages/Message+Destination.swift | 9 +- .../Sending & Receiving/MessageSender.swift | 91 +++++++++++++------ 3 files changed, 78 insertions(+), 30 deletions(-) diff --git a/SessionMessagingKit/Jobs/MessageSendJob.swift b/SessionMessagingKit/Jobs/MessageSendJob.swift index 879d6c9c1..6fc0ae4b4 100644 --- a/SessionMessagingKit/Jobs/MessageSendJob.swift +++ b/SessionMessagingKit/Jobs/MessageSendJob.swift @@ -41,6 +41,13 @@ public final class MessageSendJob : NSObject, Job, NSCoding { // NSObject/NSCodi guard components.count == 2, let channel = UInt64(components[0]) else { return nil } let server = components[1] destination = .openGroup(channel: channel, server: server) + } else if rawDestination.removePrefix("openGroupV2(") { + guard rawDestination.removeSuffix(")") else { return nil } + let components = rawDestination.split(separator: ",").map { String($0).trimmingCharacters(in: .whitespacesAndNewlines) } + guard components.count == 2 else { return nil } + let room = components[0] + let server = components[1] + destination = .openGroupV2(room: room, server: server) } else { return nil } @@ -54,6 +61,7 @@ public final class MessageSendJob : NSObject, Job, NSCoding { // NSObject/NSCodi case .contact(let publicKey): coder.encode("contact(\(publicKey))", forKey: "destination") case .closedGroup(let groupPublicKey): coder.encode("closedGroup(\(groupPublicKey))", forKey: "destination") case .openGroup(let channel, let server): coder.encode("openGroup(\(channel), \(server))", forKey: "destination") + case .openGroupV2(let room, let server): coder.encode("openGroupV2(\(room), \(server))", forKey: "destination") } coder.encode(id, forKey: "id") coder.encode(failureCount, forKey: "failureCount") diff --git a/SessionMessagingKit/Messages/Message+Destination.swift b/SessionMessagingKit/Messages/Message+Destination.swift index 009e00f2b..8587d5619 100644 --- a/SessionMessagingKit/Messages/Message+Destination.swift +++ b/SessionMessagingKit/Messages/Message+Destination.swift @@ -5,6 +5,7 @@ public extension Message { case contact(publicKey: String) case closedGroup(groupPublicKey: String) case openGroup(channel: UInt64, server: String) + case openGroupV2(room: String, server: String) static func from(_ thread: TSThread) -> Message.Destination { if let thread = thread as? TSContactThread { @@ -14,8 +15,12 @@ public extension Message { let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID) return .closedGroup(groupPublicKey: groupPublicKey) } else if let thread = thread as? TSGroupThread, thread.isOpenGroup { - let openGroup = Storage.shared.getOpenGroup(for: thread.uniqueId!)! - return .openGroup(channel: openGroup.channel, server: openGroup.server) + if let openGroupV2 = Storage.shared.getV2OpenGroup(for: thread.uniqueId!) { + return .openGroupV2(room: openGroupV2.room, server: openGroupV2.server) + } else { + let openGroup = Storage.shared.getOpenGroup(for: thread.uniqueId!)! + return .openGroup(channel: openGroup.channel, server: openGroup.server) + } } else { preconditionFailure("TODO: Handle legacy closed groups.") } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 3a9467874..9afc3afa6 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -102,7 +102,7 @@ public final class MessageSender : NSObject { public static func send(_ message: Message, to destination: Message.Destination, using transaction: Any) -> Promise { switch destination { case .contact(_), .closedGroup(_): return sendToSnodeDestination(destination, message: message, using: transaction) - case .openGroup(_, _): return sendToOpenGroupDestination(destination, message: message, using: transaction) + case .openGroup(_, _), .openGroupV2(_, _): return sendToOpenGroupDestination(destination, message: message, using: transaction) } } @@ -124,7 +124,7 @@ public final class MessageSender : NSObject { switch destination { case .contact(let publicKey): message.recipient = publicKey case .closedGroup(let groupPublicKey): message.recipient = groupPublicKey - case .openGroup(_, _): preconditionFailure() + case .openGroup(_, _), .openGroupV2(_, _): preconditionFailure() } let isSelfSend = (message.recipient == userPublicKey) // Set the failure handler (need it here already for precondition failure handling) @@ -174,7 +174,7 @@ public final class MessageSender : NSObject { case .closedGroup(let groupPublicKey): guard let encryptionKeyPair = Storage.shared.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey) else { throw Error.noKeyPair } ciphertext = try encryptWithSessionProtocol(plaintext, for: encryptionKeyPair.hexEncodedPublicKey) - case .openGroup(_, _): preconditionFailure() + case .openGroup(_, _), .openGroupV2(_, _): preconditionFailure() } } catch { SNLog("Couldn't encrypt message for destination: \(destination) due to error: \(error).") @@ -191,7 +191,7 @@ public final class MessageSender : NSObject { case .closedGroup(let groupPublicKey): kind = .closedGroupMessage senderPublicKey = groupPublicKey - case .openGroup(_, _): preconditionFailure() + case .openGroup(_, _), .openGroupV2(_, _): preconditionFailure() } let wrappedMessage: Data do { @@ -275,6 +275,7 @@ public final class MessageSender : NSObject { case .contact(_): preconditionFailure() case .closedGroup(_): preconditionFailure() case .openGroup(let channel, let server): message.recipient = "\(server).\(channel)" + case .openGroupV2(let room, let server): message.recipient = "\(server).\(room)" } // Set the failure handler (need it here already for precondition failure handling) func handleFailure(with error: Swift.Error, using transaction: YapDatabaseReadWriteTransaction) { @@ -291,32 +292,66 @@ public final class MessageSender : NSObject { #endif } guard message.isValid else { handleFailure(with: Error.invalidMessage, using: transaction); return promise } - // The back-end doesn't accept messages without a body so we use this as a workaround - if message.text?.isEmpty != false { - message.text = String(message.sentTimestamp!) - } - // Convert the message to an open group message - let (channel, server) = { () -> (UInt64, String) in - switch destination { - case .openGroup(let channel, let server): return (channel, server) - default: preconditionFailure() + // There's quite a bit of overlap between the two clauses of this if statement for now, but that'll be fixed + // when we remove support for V1 open groups + if case .openGroup(let channel, let server) = destination { + // The back-end doesn't accept messages without a body so we use this as a workaround + if message.text?.isEmpty != false { + message.text = String(message.sentTimestamp!) } - }() - guard let openGroupMessage = OpenGroupMessage.from(message, for: server, using: transaction) else { handleFailure(with: Error.invalidMessage, using: transaction); return promise } - // Send the result - OpenGroupAPI.sendMessage(openGroupMessage, to: channel, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in - message.openGroupServerMessageID = openGroupMessage.serverID - storage.write(with: { transaction in - MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction) - seal.fulfill(()) - }, completion: { }) - }.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in - storage.write(with: { transaction in - handleFailure(with: error, using: transaction as! YapDatabaseReadWriteTransaction) - }, completion: { }) + // Convert the message to an open group message + guard let openGroupMessage = OpenGroupMessage.from(message, for: server, using: transaction) else { handleFailure(with: Error.invalidMessage, using: transaction); return promise } + // Send the result + OpenGroupAPI.sendMessage(openGroupMessage, to: channel, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in + message.openGroupServerMessageID = openGroupMessage.serverID + storage.write(with: { transaction in + MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction) + seal.fulfill(()) + }, completion: { }) + }.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in + storage.write(with: { transaction in + handleFailure(with: error, using: transaction as! YapDatabaseReadWriteTransaction) + }, completion: { }) + } + // Return + return promise + } else if case .openGroupV2(let room, let server) = destination { + // Attach the user's profile + guard let name = storage.getUser()?.name else { handleFailure(with: Error.noUsername, using: transaction); return promise } + if let profileKey = storage.getUser()?.profilePictureEncryptionKey?.keyData, let profilePictureURL = storage.getUser()?.profilePictureURL { + message.profile = VisibleMessage.Profile(displayName: name, profileKey: profileKey, profilePictureURL: profilePictureURL) + } else { + message.profile = VisibleMessage.Profile(displayName: name) + } + // Convert it to protobuf + guard let proto = message.toProto(using: transaction) else { handleFailure(with: Error.protoConversionFailed, using: transaction); return promise } + // Serialize the protobuf + let plaintext: Data + do { + plaintext = (try proto.serializedData() as NSData).paddedMessageBody() + } catch { + SNLog("Couldn't serialize proto due to error: \(error).") + handleFailure(with: error, using: transaction) + return promise + } + // Send the result + let openGroupMessage = OpenGroupMessageV2(serverID: nil, base64EncodedData: plaintext.base64EncodedString(), base64EncodedSignature: nil) + OpenGroupAPIV2.send(openGroupMessage, to: room, on: server).done(on: DispatchQueue.global(qos: .userInitiated)) { openGroupMessage in + message.openGroupServerMessageID = given(openGroupMessage.serverID) { UInt64($0) } + storage.write(with: { transaction in + MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction) + seal.fulfill(()) + }, completion: { }) + }.catch(on: DispatchQueue.global(qos: .userInitiated)) { error in + storage.write(with: { transaction in + handleFailure(with: error, using: transaction as! YapDatabaseReadWriteTransaction) + }, completion: { }) + } + // Return + return promise + } else { + preconditionFailure() } - // Return - return promise } // MARK: Success & Failure Handling