From 35cefaf7fbb1661c222f26290fde745f765e35e5 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 29 Jan 2021 11:47:16 +1100 Subject: [PATCH 01/13] Update build number --- Session.xcodeproj/project.pbxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index e34c89a74..221d6ad10 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5336,7 +5336,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 173; + CURRENT_PROJECT_VERSION = 174; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5405,7 +5405,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 173; + CURRENT_PROJECT_VERSION = 174; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5466,7 +5466,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 173; + CURRENT_PROJECT_VERSION = 174; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5536,7 +5536,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 173; + CURRENT_PROJECT_VERSION = 174; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -6551,7 +6551,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 173; + CURRENT_PROJECT_VERSION = 174; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6619,7 +6619,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 173; + CURRENT_PROJECT_VERSION = 174; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", From 7efe56520a3639bf9a40b113a65653d19e6a047e Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 1 Feb 2021 13:13:12 +1100 Subject: [PATCH 02/13] Remove debug code --- Session/Onboarding/SeedVC.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Session/Onboarding/SeedVC.swift b/Session/Onboarding/SeedVC.swift index 4a29d288c..558810625 100644 --- a/Session/Onboarding/SeedVC.swift +++ b/Session/Onboarding/SeedVC.swift @@ -168,8 +168,8 @@ final class SeedVC : BaseVC { self.seedReminderView.subtitle = NSLocalizedString("view_seed_reminder_subtitle_3", comment: "") }, completion: nil) seedReminderView.setProgress(1, animated: true) -// UserDefaults.standard[.hasViewedSeed] = true -// NotificationCenter.default.post(name: .seedViewed, object: nil) + UserDefaults.standard[.hasViewedSeed] = true + NotificationCenter.default.post(name: .seedViewed, object: nil) } @objc private func copyMnemonic() { From a86f1d3bcf2d0ee03a3ecef4575d9a75dcb8bd0a Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 2 Feb 2021 09:21:50 +1100 Subject: [PATCH 03/13] Handle case where admin left the group --- .../MessageReceiver+Handling.swift | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 2308e5ab3..7e8125810 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -412,13 +412,24 @@ extension MessageReceiver { performIfValid(for: message, using: transaction) { groupID, thread, group in let didAdminLeave = group.groupAdminIds.contains(message.sender!) let members: Set = didAdminLeave ? [] : Set(group.groupMemberIds).subtracting([ message.sender! ]) // If the admin leaves the group is disbanded + let userPublicKey = getUserHexEncodedPublicKey() + let isCurrentUserAdmin = group.groupAdminIds.contains(userPublicKey) // Guard against self-sends guard message.sender != getUserHexEncodedPublicKey() else { return SNLog("Ignoring invalid closed group update.") } - // Generate and distribute a new encryption key pair if needed - let isCurrentUserAdmin = group.groupAdminIds.contains(getUserHexEncodedPublicKey()) - if isCurrentUserAdmin { + // If a regular member left: + // • Distribute a new encryption key pair if we're the admin of the group + // If the admin left: + // • Don't distribute a new encryption key pair + // • Unsubscribe from PNs, delete the group public key, etc. as the group will be disbanded + if didAdminLeave { + // Remove the group from the database and unsubscribe from PNs + Storage.shared.removeAllClosedGroupEncryptionKeyPairs(for: groupPublicKey, using: transaction) + Storage.shared.removeClosedGroupPublicKey(groupPublicKey, using: transaction) + let _ = PushNotificationAPI.performOperation(.unsubscribe, for: groupPublicKey, publicKey: userPublicKey) + } else if isCurrentUserAdmin { + // Generate and distribute a new encryption key pair if needed do { try MessageSender.generateAndSendNewEncryptionKeyPair(for: groupPublicKey, to: members, using: transaction) } catch { From b721794a51ea2a4412e137c3523acf760bbf6e86 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 8 Feb 2021 09:37:39 +1100 Subject: [PATCH 04/13] Fix closed group update handling from before it was created --- .../Database/Storage+ClosedGroups.swift | 14 ++++++++++++++ .../MessageReceiver+Handling.swift | 15 ++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/SessionMessagingKit/Database/Storage+ClosedGroups.swift b/SessionMessagingKit/Database/Storage+ClosedGroups.swift index cda71072d..8ec789b85 100644 --- a/SessionMessagingKit/Database/Storage+ClosedGroups.swift +++ b/SessionMessagingKit/Database/Storage+ClosedGroups.swift @@ -9,6 +9,8 @@ extension Storage { } private static let closedGroupPublicKeyCollection = "SNClosedGroupPublicKeyCollection" + + private static let closedGroupFormationTimestampCollection = "SNClosedGroupFormationTimestampCollection" public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String) -> [ECKeyPair] { let collection = Storage.getClosedGroupEncryptionKeyPairCollection(for: groupPublicKey) @@ -45,6 +47,18 @@ extension Storage { (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: groupPublicKey, inCollection: Storage.closedGroupPublicKeyCollection) } + public func getClosedGroupFormationTimestamp(for groupPublicKey: String) -> UInt64? { + var result: UInt64? + Storage.read { transaction in + result = transaction.object(forKey: groupPublicKey, inCollection: Storage.closedGroupFormationTimestampCollection) as? UInt64 + } + return result + } + + public func setClosedGroupFormationTimestamp(to timestamp: UInt64, for groupPublicKey: String, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).setObject(timestamp, forKey: groupPublicKey, inCollection: Storage.closedGroupFormationTimestampCollection) + } + // MARK: - Ratchets diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 7e8125810..cfbb6b3b6 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -150,7 +150,7 @@ extension MessageReceiver { for closedGroup in message.closedGroups { guard !allClosedGroupPublicKeys.contains(closedGroup.publicKey) else { continue } handleNewClosedGroup(groupPublicKey: closedGroup.publicKey, name: closedGroup.name, encryptionKeyPair: closedGroup.encryptionKeyPair, - members: [String](closedGroup.members), admins: [String](closedGroup.admins), using: transaction) + members: [String](closedGroup.members), admins: [String](closedGroup.admins), messageSentTimestamp: message.sentTimestamp!, using: transaction) } let allOpenGroups = Set(storage.getAllUserOpenGroups().keys) for openGroupURL in message.openGroups { @@ -262,10 +262,11 @@ extension MessageReceiver { let groupPublicKey = publicKeyAsData.toHexString() let members = membersAsData.map { $0.toHexString() } let admins = adminsAsData.map { $0.toHexString() } - handleNewClosedGroup(groupPublicKey: groupPublicKey, name: name, encryptionKeyPair: encryptionKeyPair, members: members, admins: admins, using: transaction) + handleNewClosedGroup(groupPublicKey: groupPublicKey, name: name, encryptionKeyPair: encryptionKeyPair, + members: members, admins: admins, messageSentTimestamp: message.sentTimestamp!, using: transaction) } - private static func handleNewClosedGroup(groupPublicKey: String, name: String, encryptionKeyPair: ECKeyPair, members: [String], admins: [String], using transaction: Any) { + private static func handleNewClosedGroup(groupPublicKey: String, name: String, encryptionKeyPair: ECKeyPair, members: [String], admins: [String], messageSentTimestamp: UInt64, using transaction: Any) { let transaction = transaction as! YapDatabaseReadWriteTransaction // Create the group let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) @@ -282,6 +283,8 @@ extension MessageReceiver { Storage.shared.addClosedGroupPublicKey(groupPublicKey, using: transaction) // Store the key pair Storage.shared.addClosedGroupEncryptionKeyPair(encryptionKeyPair, for: groupPublicKey, using: transaction) + // Store the formation timestamp + Storage.shared.setClosedGroupFormationTimestamp(to: messageSentTimestamp, for: groupPublicKey, using: transaction) // Notify the PN server let _ = PushNotificationAPI.performOperation(.subscribe, for: groupPublicKey, publicKey: getUserHexEncodedPublicKey()) // Notify the user @@ -459,8 +462,10 @@ extension MessageReceiver { } let group = thread.groupModel // Check that the message isn't from before the group was created - guard Double(message.sentTimestamp!) > thread.creationDate.timeIntervalSince1970 * 1000 else { - return SNLog("Ignoring closed group update from before thread was created.") + if let formationTimestamp = Storage.shared.getClosedGroupFormationTimestamp(for: groupPublicKey) { + guard message.sentTimestamp! > formationTimestamp else { + return SNLog("Ignoring closed group update from before thread was created.") + } } // Check that the sender is a member of the group guard Set(group.groupMemberIds).contains(message.sender!) else { From 8477f754261ce6c12f49e9973e38fa8b45a1c9d9 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 8 Feb 2021 10:21:02 +1100 Subject: [PATCH 05/13] Fix unnecessary path rebuilding --- SessionSnodeKit/OnionRequestAPI.swift | 4 +++- SessionSnodeKit/SnodeAPI.swift | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/SessionSnodeKit/OnionRequestAPI.swift b/SessionSnodeKit/OnionRequestAPI.swift index 2f9c09816..47705ed84 100644 --- a/SessionSnodeKit/OnionRequestAPI.swift +++ b/SessionSnodeKit/OnionRequestAPI.swift @@ -438,10 +438,12 @@ public enum OnionRequestAPI { OnionRequestAPI.snodeFailureCount[snode] = snodeFailureCount } } else { - handleUnspecificError() + // Do nothing } } else if let message = json?["result"] as? String, message == "Loki Server error" { // Do nothing + } else if statusCode == 0 { // Timeout + // Do nothing } else { handleUnspecificError() } diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index c90685f4e..337197c7f 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -339,7 +339,7 @@ public final class SnodeAPI : NSObject { } } switch statusCode { - case 0, 400, 500, 503: + case 500, 502, 503: // The snode is unreachable handleBadSnode() case 406: From 0cbdc8be965e25bf93114afd3648b47cc5b69128 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 8 Feb 2021 10:37:28 +1100 Subject: [PATCH 06/13] Update version number --- Session.xcodeproj/project.pbxproj | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 221d6ad10..a5804e515 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -5336,7 +5336,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 174; + CURRENT_PROJECT_VERSION = 175; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5357,7 +5357,7 @@ INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.7.7; + MARKETING_VERSION = 1.7.8; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5405,7 +5405,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 174; + CURRENT_PROJECT_VERSION = 175; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5431,7 +5431,7 @@ INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.7.7; + MARKETING_VERSION = 1.7.8; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -5466,7 +5466,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 174; + CURRENT_PROJECT_VERSION = 175; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -5485,7 +5485,7 @@ INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.7.7; + MARKETING_VERSION = 1.7.8; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -5536,7 +5536,7 @@ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 174; + CURRENT_PROJECT_VERSION = 175; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = SUQ8J2PCT7; ENABLE_NS_ASSERTIONS = NO; @@ -5560,7 +5560,7 @@ INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 12.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; - MARKETING_VERSION = 1.7.7; + MARKETING_VERSION = 1.7.8; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; @@ -6551,7 +6551,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 174; + CURRENT_PROJECT_VERSION = 175; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6587,7 +6587,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.7.7; + MARKETING_VERSION = 1.7.8; OTHER_LDFLAGS = "$(inherited)"; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; @@ -6619,7 +6619,7 @@ CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 174; + CURRENT_PROJECT_VERSION = 175; DEVELOPMENT_TEAM = SUQ8J2PCT7; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", @@ -6655,7 +6655,7 @@ "$(SRCROOT)", ); LLVM_LTO = NO; - MARKETING_VERSION = 1.7.7; + MARKETING_VERSION = 1.7.8; OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_NAME = Session; From 1fda8e424931d8a01097f704fced299abd5ea7a1 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 8 Feb 2021 11:33:47 +1100 Subject: [PATCH 07/13] Add encryption key pair request message --- Session/Closed Groups/EditClosedGroupVC.swift | 2 +- .../ClosedGroupControlMessage.swift | 30 ++++++++++++--- .../Protos/Generated/SNProto.swift | 3 ++ .../Protos/Generated/SessionProtos.pb.swift | 6 +++ .../Protos/SessionProtos.proto | 15 ++++---- .../MessageReceiver+Handling.swift | 38 +++++++++++++++++-- .../Sending & Receiving/MessageReceiver.swift | 13 ++++++- .../MessageSender+ClosedGroups.swift | 17 ++++++++- 8 files changed, 104 insertions(+), 20 deletions(-) diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index ec89fa71b..dffd336e6 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -273,7 +273,7 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega if !members.contains(getUserHexEncodedPublicKey()) { try MessageSender.leave(groupPublicKey, using: transaction) } else { - try MessageSender.update(groupPublicKey, with: members, name: name, transaction: transaction) + try MessageSender.v2_update(groupPublicKey, with: members, name: name, transaction: transaction) } } catch { DispatchQueue.main.async { diff --git a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift index 4d4723047..05c259c77 100644 --- a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift @@ -23,11 +23,15 @@ public final class ClosedGroupControlMessage : ControlMessage { case new(publicKey: Data, name: String, encryptionKeyPair: ECKeyPair, members: [Data], admins: [Data]) /// - Note: Deprecated in favor of more explicit group updates. case update(name: String, members: [Data]) - case encryptionKeyPair([KeyPairWrapper]) // The new encryption key pair encrypted for each member individually + /// An encryption key pair encrypted for each member individually. + /// + /// - Note: `publicKey` is only set when an encryption key pair is sent in a one-to-one context (i.e. not in a group). + case encryptionKeyPair(publicKey: Data?, wrappers: [KeyPairWrapper]) case nameChange(name: String) case membersAdded(members: [Data]) case membersRemoved(members: [Data]) case memberLeft + case encryptionKeyPairRequest public var description: String { switch self { @@ -38,6 +42,7 @@ public final class ClosedGroupControlMessage : ControlMessage { case .membersAdded: return "membersAdded" case .membersRemoved: return "membersRemoved" case .memberLeft: return "memberLeft" + case .encryptionKeyPairRequest: return "encryptionKeyPairRequest" } } } @@ -103,6 +108,7 @@ public final class ClosedGroupControlMessage : ControlMessage { case .membersAdded(let members): return !members.isEmpty case .membersRemoved(let members): return !members.isEmpty case .memberLeft: return true + case .encryptionKeyPairRequest: return true } } @@ -123,8 +129,9 @@ public final class ClosedGroupControlMessage : ControlMessage { let members = coder.decodeObject(forKey: "members") as? [Data] else { return nil } self.kind = .update(name: name, members: members) case "encryptionKeyPair": + let publicKey = coder.decodeObject(forKey: "publicKey") as? Data guard let wrappers = coder.decodeObject(forKey: "wrappers") as? [KeyPairWrapper] else { return nil } - self.kind = .encryptionKeyPair(wrappers) + self.kind = .encryptionKeyPair(publicKey: publicKey, wrappers: wrappers) case "nameChange": guard let name = coder.decodeObject(forKey: "name") as? String else { return nil } self.kind = .nameChange(name: name) @@ -136,6 +143,8 @@ public final class ClosedGroupControlMessage : ControlMessage { self.kind = .membersRemoved(members: members) case "memberLeft": self.kind = .memberLeft + case "encryptionKeyPairRequest": + self.kind = .encryptionKeyPairRequest default: return nil } } @@ -155,8 +164,9 @@ public final class ClosedGroupControlMessage : ControlMessage { coder.encode("update", forKey: "kind") coder.encode(name, forKey: "name") coder.encode(members, forKey: "members") - case .encryptionKeyPair(let wrappers): + case .encryptionKeyPair(let publicKey, let wrappers): coder.encode("encryptionKeyPair", forKey: "kind") + coder.encode(publicKey, forKey: "publicKey") coder.encode(wrappers, forKey: "wrappers") case .nameChange(let name): coder.encode("nameChange", forKey: "kind") @@ -169,6 +179,8 @@ public final class ClosedGroupControlMessage : ControlMessage { coder.encode(members, forKey: "members") case .memberLeft: coder.encode("memberLeft", forKey: "kind") + case .encryptionKeyPairRequest: + coder.encode("encryptionKeyPairRequest", forKey: "kind") } } @@ -192,8 +204,9 @@ public final class ClosedGroupControlMessage : ControlMessage { guard let name = closedGroupControlMessageProto.name else { return nil } kind = .update(name: name, members: closedGroupControlMessageProto.members) case .encryptionKeyPair: + let publicKey = closedGroupControlMessageProto.publicKey let wrappers = closedGroupControlMessageProto.wrappers.compactMap { KeyPairWrapper.fromProto($0) } - kind = .encryptionKeyPair(wrappers) + kind = .encryptionKeyPair(publicKey: publicKey, wrappers: wrappers) case .nameChange: guard let name = closedGroupControlMessageProto.name else { return nil } kind = .nameChange(name: name) @@ -203,6 +216,8 @@ public final class ClosedGroupControlMessage : ControlMessage { kind = .membersRemoved(members: closedGroupControlMessageProto.members) case .memberLeft: kind = .memberLeft + case .encryptionKeyPairRequest: + kind = .encryptionKeyPairRequest } return ClosedGroupControlMessage(kind: kind) } @@ -232,8 +247,11 @@ public final class ClosedGroupControlMessage : ControlMessage { closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .update) closedGroupControlMessage.setName(name) closedGroupControlMessage.setMembers(members) - case .encryptionKeyPair(let wrappers): + case .encryptionKeyPair(let publicKey, let wrappers): closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .encryptionKeyPair) + if let publicKey = publicKey { + closedGroupControlMessage.setPublicKey(publicKey) + } closedGroupControlMessage.setWrappers(wrappers.compactMap { $0.toProto() }) case .nameChange(let name): closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .nameChange) @@ -246,6 +264,8 @@ public final class ClosedGroupControlMessage : ControlMessage { closedGroupControlMessage.setMembers(members) case .memberLeft: closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .memberLeft) + case .encryptionKeyPairRequest: + closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .encryptionKeyPairRequest) } let contentProto = SNProtoContent.builder() let dataMessageProto = SNProtoDataMessage.builder() diff --git a/SessionMessagingKit/Protos/Generated/SNProto.swift b/SessionMessagingKit/Protos/Generated/SNProto.swift index 2a6a55a44..5d79bfbbb 100644 --- a/SessionMessagingKit/Protos/Generated/SNProto.swift +++ b/SessionMessagingKit/Protos/Generated/SNProto.swift @@ -2410,6 +2410,7 @@ extension SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper.SNProtoDataM case membersAdded = 5 case membersRemoved = 6 case memberLeft = 7 + case encryptionKeyPairRequest = 8 } private class func SNProtoDataMessageClosedGroupControlMessageTypeWrap(_ value: SessionProtos_DataMessage.ClosedGroupControlMessage.TypeEnum) -> SNProtoDataMessageClosedGroupControlMessageType { @@ -2421,6 +2422,7 @@ extension SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper.SNProtoDataM case .membersAdded: return .membersAdded case .membersRemoved: return .membersRemoved case .memberLeft: return .memberLeft + case .encryptionKeyPairRequest: return .encryptionKeyPairRequest } } @@ -2433,6 +2435,7 @@ extension SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper.SNProtoDataM case .membersAdded: return .membersAdded case .membersRemoved: return .membersRemoved case .memberLeft: return .memberLeft + case .encryptionKeyPairRequest: return .encryptionKeyPairRequest } } diff --git a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift index c0e0553b8..c266767ee 100644 --- a/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift +++ b/SessionMessagingKit/Protos/Generated/SessionProtos.pb.swift @@ -1152,6 +1152,9 @@ struct SessionProtos_DataMessage { case membersRemoved // = 6 case memberLeft // = 7 + /// wrappers + case encryptionKeyPairRequest // = 8 + init() { self = .new } @@ -1165,6 +1168,7 @@ struct SessionProtos_DataMessage { case 5: self = .membersAdded case 6: self = .membersRemoved case 7: self = .memberLeft + case 8: self = .encryptionKeyPairRequest default: return nil } } @@ -1178,6 +1182,7 @@ struct SessionProtos_DataMessage { case .membersAdded: return 5 case .membersRemoved: return 6 case .memberLeft: return 7 + case .encryptionKeyPairRequest: return 8 } } @@ -3131,6 +3136,7 @@ extension SessionProtos_DataMessage.ClosedGroupControlMessage.TypeEnum: SwiftPro 5: .same(proto: "MEMBERS_ADDED"), 6: .same(proto: "MEMBERS_REMOVED"), 7: .same(proto: "MEMBER_LEFT"), + 8: .same(proto: "ENCRYPTION_KEY_PAIR_REQUEST"), ] } diff --git a/SessionMessagingKit/Protos/SessionProtos.proto b/SessionMessagingKit/Protos/SessionProtos.proto index 9662b06bb..fc9e8f2c1 100644 --- a/SessionMessagingKit/Protos/SessionProtos.proto +++ b/SessionMessagingKit/Protos/SessionProtos.proto @@ -169,13 +169,14 @@ message DataMessage { message ClosedGroupControlMessage { enum Type { - NEW = 1; // publicKey, name, encryptionKeyPair, members, admins - UPDATE = 2; // name, members - ENCRYPTION_KEY_PAIR = 3; // wrappers - NAME_CHANGE = 4; // name - MEMBERS_ADDED = 5; // members - MEMBERS_REMOVED = 6; // members - MEMBER_LEFT = 7; + NEW = 1; // publicKey, name, encryptionKeyPair, members, admins + UPDATE = 2; // name, members + ENCRYPTION_KEY_PAIR = 3; // publicKey, wrappers + NAME_CHANGE = 4; // name + MEMBERS_ADDED = 5; // members + MEMBERS_REMOVED = 6; // members + MEMBER_LEFT = 7; + ENCRYPTION_KEY_PAIR_REQUEST = 8; } message KeyPairWrapper { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index cfbb6b3b6..166d1b2da 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -253,6 +253,7 @@ extension MessageReceiver { case .membersAdded: handleClosedGroupMembersAdded(message, using: transaction) case .membersRemoved: handleClosedGroupMembersRemoved(message, using: transaction) case .memberLeft: handleClosedGroupMemberLeft(message, using: transaction) + case .encryptionKeyPairRequest: handleClosedGroupEncryptionKeyPairRequest(message, using: transaction) } } @@ -294,7 +295,8 @@ extension MessageReceiver { private static func handleClosedGroupEncryptionKeyPair(_ message: ClosedGroupControlMessage, using transaction: Any) { // Prepare - guard case let .encryptionKeyPair(wrappers) = message.kind, let groupPublicKey = message.groupPublicKey else { return } + guard case let .encryptionKeyPair(explicitGroupPublicKey, wrappers) = message.kind, + let groupPublicKey = explicitGroupPublicKey?.toHexString() ?? message.groupPublicKey else { return } let transaction = transaction as! YapDatabaseReadWriteTransaction let userPublicKey = getUserHexEncodedPublicKey() guard let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else { @@ -305,8 +307,8 @@ extension MessageReceiver { guard let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else { return SNLog("Ignoring closed group encryption key pair for nonexistent group.") } - guard thread.groupModel.groupAdminIds.contains(message.sender!) else { - return SNLog("Ignoring closed group encryption key pair from non-admin.") + guard thread.groupModel.groupMemberIds.contains(message.sender!) else { + return SNLog("Ignoring closed group encryption key pair from non-member.") } // Find our wrapper and decrypt it if possible guard let wrapper = wrappers.first(where: { $0.publicKey == userPublicKey }), let encryptedKeyPair = wrapper.encryptedKeyPair else { return } @@ -329,7 +331,11 @@ extension MessageReceiver { } catch { return SNLog("Couldn't parse closed group encryption key pair.") } - // Store it + // Store it if needed + let closedGroupEncryptionKeyPairs = Storage.shared.getClosedGroupEncryptionKeyPairs(for: groupPublicKey) + guard !closedGroupEncryptionKeyPairs.contains(keyPair) else { + return SNLog("Ignoring duplicate closed group encryption key pair.") + } Storage.shared.addClosedGroupEncryptionKeyPair(keyPair, for: groupPublicKey, using: transaction) SNLog("Received a new closed group encryption key pair.") } @@ -450,6 +456,30 @@ extension MessageReceiver { } } + private static func handleClosedGroupEncryptionKeyPairRequest(_ message: ClosedGroupControlMessage, using transaction: Any) { + guard case .encryptionKeyPairRequest = message.kind else { return } + let transaction = transaction as! YapDatabaseReadWriteTransaction + guard let groupPublicKey = message.groupPublicKey else { return } + performIfValid(for: message, using: transaction) { groupID, _, group in + let publicKey = message.sender! + // Guard against self-sends + guard publicKey != getUserHexEncodedPublicKey() else { + return SNLog("Ignoring invalid closed group update.") + } + // Get the latest encryption key pair + guard let encryptionKeyPair = Storage.shared.getLatestClosedGroupEncryptionKeyPair(for: groupPublicKey) else { return } + // Send it + guard let proto = try? SNProtoKeyPair.builder(publicKey: encryptionKeyPair.publicKey, + privateKey: encryptionKeyPair.privateKey).build(), let plaintext = try? proto.serializedData() else { return } + let thread = TSContactThread.getOrCreateThread(withContactId: publicKey, transaction: transaction) + guard let ciphertext = try? MessageSender.encryptWithSessionProtocol(plaintext, for: publicKey) else { return } + SNLog("Responding to closed group encryption key pair request from: \(publicKey).") + let wrapper = ClosedGroupControlMessage.KeyPairWrapper(publicKey: publicKey, encryptedKeyPair: ciphertext) + let closedGroupControlMessage = ClosedGroupControlMessage(kind: .encryptionKeyPair(publicKey: Data(hex: groupPublicKey), wrappers: [ wrapper ])) + MessageSender.send(closedGroupControlMessage, in: thread, using: transaction) + } + } + private static func performIfValid(for message: ClosedGroupControlMessage, using transaction: Any, _ update: (Data, TSGroupThread, TSGroupModel) -> Void) { // Prepare let transaction = transaction as! YapDatabaseReadWriteTransaction diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index e6b61cd11..ddcce7150 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -22,7 +22,7 @@ public enum MessageReceiver { public var isRetryable: Bool { switch self { - case .duplicateMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType, .invalidSignature, .noData, .senderBlocked, .selfSend, .decryptionFailed: return false + case .duplicateMessage, .invalidMessage, .unknownMessage, .unknownEnvelopeType, .invalidSignature, .noData, .senderBlocked, .selfSend: return false default: return true } } @@ -88,8 +88,17 @@ public enum MessageReceiver { } } } - try decrypt() groupPublicKey = envelope.source + do { + try decrypt() + } catch { + do { + try MessageSender.requestEncryptionKeyPair(for: groupPublicKey!, using: transaction as! YapDatabaseReadWriteTransaction) + } catch { + // Do nothing + } + throw error // Throw the * decryption * error and not the error generated by requestEncryptionKeyPair (if it generated one) + } default: throw Error.unknownEnvelopeType } } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift index 57386e521..03f6b14fb 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift @@ -69,7 +69,7 @@ extension MessageSender { let ciphertext = try MessageSender.encryptWithSessionProtocol(plaintext, for: publicKey) return ClosedGroupControlMessage.KeyPairWrapper(publicKey: publicKey, encryptedKeyPair: ciphertext) } - let closedGroupControlMessage = ClosedGroupControlMessage(kind: .encryptionKeyPair(wrappers)) + let closedGroupControlMessage = ClosedGroupControlMessage(kind: .encryptionKeyPair(publicKey: nil, wrappers: wrappers)) let _ = MessageSender.sendNonDurably(closedGroupControlMessage, in: thread, using: transaction).done { // FIXME: It'd be great if we could make this a durable operation // Store it * after * having sent out the message to the group SNMessagingKitConfiguration.shared.storage.write { transaction in @@ -234,6 +234,21 @@ extension MessageSender { infoMessage.save(with: transaction) } + public static func requestEncryptionKeyPair(for groupPublicKey: String, using transaction: YapDatabaseReadWriteTransaction) throws { + // Get the group, check preconditions & prepare + let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) + let threadID = TSGroupThread.threadId(fromGroupId: groupID) + guard let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else { + SNLog("Can't request encryption key pair for nonexistent closed group.") + throw Error.noThread + } + let group = thread.groupModel + guard group.groupMemberIds.contains(getUserHexEncodedPublicKey()) else { return } + // Send the request to the group + let closedGroupControlMessage = ClosedGroupControlMessage(kind: .encryptionKeyPairRequest) + MessageSender.send(closedGroupControlMessage, in: thread, using: transaction) + } + // MARK: - Deprecated From b3ccb2ed23775c199f9f8b48ee715b983adab367 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 8 Feb 2021 13:20:20 +1100 Subject: [PATCH 08/13] Remove debug code --- Session/Closed Groups/EditClosedGroupVC.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index dffd336e6..ec89fa71b 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -273,7 +273,7 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega if !members.contains(getUserHexEncodedPublicKey()) { try MessageSender.leave(groupPublicKey, using: transaction) } else { - try MessageSender.v2_update(groupPublicKey, with: members, name: name, transaction: transaction) + try MessageSender.update(groupPublicKey, with: members, name: name, transaction: transaction) } } catch { DispatchQueue.main.async { From e7b90179929ddcd71c196ddb5a63706fdf8f4738 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 9 Feb 2021 13:55:32 +1100 Subject: [PATCH 09/13] Enable V2 closed group control messages --- Session/Closed Groups/EditClosedGroupVC.swift | 4 ++-- Session/Conversations/OWSConversationSettingsViewController.m | 2 +- Session/Home/HomeVC.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Session/Closed Groups/EditClosedGroupVC.swift b/Session/Closed Groups/EditClosedGroupVC.swift index ec89fa71b..31e476c85 100644 --- a/Session/Closed Groups/EditClosedGroupVC.swift +++ b/Session/Closed Groups/EditClosedGroupVC.swift @@ -271,9 +271,9 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega Storage.write(with: { [weak self] transaction in do { if !members.contains(getUserHexEncodedPublicKey()) { - try MessageSender.leave(groupPublicKey, using: transaction) + try MessageSender.v2_leave(groupPublicKey, using: transaction) } else { - try MessageSender.update(groupPublicKey, with: members, name: name, transaction: transaction) + try MessageSender.v2_update(groupPublicKey, with: members, name: name, transaction: transaction) } } catch { DispatchQueue.main.async { diff --git a/Session/Conversations/OWSConversationSettingsViewController.m b/Session/Conversations/OWSConversationSettingsViewController.m index 56aaccb41..21eae4b48 100644 --- a/Session/Conversations/OWSConversationSettingsViewController.m +++ b/Session/Conversations/OWSConversationSettingsViewController.m @@ -939,7 +939,7 @@ static CGRect oldframe; if (gThread.isClosedGroup) { NSString *groupPublicKey = [LKGroupUtilities getDecodedGroupID:gThread.groupModel.groupId]; [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [SNMessageSender leaveClosedGroupWithPublicKey:groupPublicKey using:transaction error:nil]; + [SNMessageSender v2_leaveClosedGroupWithPublicKey:groupPublicKey using:transaction error:nil]; }]; } diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 04994c47e..b197fe40f 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -398,7 +398,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIViewC let groupID = thread.groupModel.groupId let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID) do { - try MessageSender.leave(groupPublicKey, using: transaction) + try MessageSender.v2_leave(groupPublicKey, using: transaction) } catch { // TODO: Handle } From 65f397ccd116d7d72b8abdc515bfb884e48c7e93 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 10 Feb 2021 09:51:28 +1100 Subject: [PATCH 10/13] Debug --- SessionMessagingKit/Jobs/JobQueue.swift | 13 ++++++------ .../Jobs/MessageReceiveJob.swift | 6 ++++-- .../Sending & Receiving/MessageReceiver.swift | 20 ++++++++++++------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/SessionMessagingKit/Jobs/JobQueue.swift b/SessionMessagingKit/Jobs/JobQueue.swift index d6f5b8066..16168f52c 100644 --- a/SessionMessagingKit/Jobs/JobQueue.swift +++ b/SessionMessagingKit/Jobs/JobQueue.swift @@ -76,15 +76,14 @@ public final class JobQueue : NSObject, JobDelegate { private func getRetryInterval(for job: Job) -> TimeInterval { // Arbitrary backoff factor... - // try 1 delay: 0.00s - // try 2 delay: 0.19s + // try 1 delay: 0.5s + // try 2 delay: 1s // ... - // try 5 delay: 1.30s + // try 5 delay: 16s // ... - // try 11 delay: 61.31s - let backoffFactor = 1.9 - let maxBackoff: Double = 60 * 60 * 1000 - return 0.1 * min(maxBackoff, pow(backoffFactor, Double(job.failureCount))) + // try 11 delay: 512s + let maxBackoff: Double = 10 * 60 // 10 minutes + return 0.25 * min(maxBackoff, pow(2, Double(job.failureCount))) } @objc private func retry(_ timer: Timer) { diff --git a/SessionMessagingKit/Jobs/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/MessageReceiveJob.swift index 8e3782a2b..6cfe0dbfe 100644 --- a/SessionMessagingKit/Jobs/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/MessageReceiveJob.swift @@ -57,15 +57,17 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSC let (promise, seal) = Promise.pending() SNMessagingKitConfiguration.shared.storage.write(with: { transaction in // Intentionally capture self do { - let (message, proto) = try MessageReceiver.parse(self.data, openGroupMessageServerID: self.openGroupMessageServerID, using: transaction) + let isRetry = (self.failureCount != 0) + let (message, proto) = try MessageReceiver.parse(self.data, openGroupMessageServerID: self.openGroupMessageServerID, isRetry: isRetry, using: transaction) try MessageReceiver.handle(message, associatedWithProto: proto, openGroupID: self.openGroupID, isBackgroundPoll: self.isBackgroundPoll, using: transaction) self.handleSuccess() seal.fulfill(()) } catch { - SNLog("Couldn't receive message due to error: \(error).") if let error = error as? MessageReceiver.Error, !error.isRetryable { + SNLog("Message receive job permanently failed due to error: \(error).") self.handlePermanentFailure(error: error) } else { + SNLog("Couldn't receive message due to error: \(error).") self.handleFailure(error: error) } seal.fulfill(()) // The promise is just used to keep track of when we're done diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index ddcce7150..a0b350f9b 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -1,6 +1,7 @@ import SessionUtilitiesKit public enum MessageReceiver { + private static var lastEncryptionKeyPairRequest: [String:Date] = [:] public enum Error : LocalizedError { case duplicateMessage @@ -18,7 +19,6 @@ public enum MessageReceiver { // Shared sender keys case invalidGroupPublicKey case noGroupKeyPair - case sharedSecretGenerationFailed public var isRetryable: Bool { switch self { @@ -44,18 +44,20 @@ public enum MessageReceiver { // Shared sender keys case .invalidGroupPublicKey: return "Invalid group public key." case .noGroupKeyPair: return "Missing group key pair." - case .sharedSecretGenerationFailed: return "Couldn't generate a shared secret." } } } - public static func parse(_ data: Data, openGroupMessageServerID: UInt64?, using transaction: Any) throws -> (Message, SNProtoContent) { + public static func parse(_ data: Data, openGroupMessageServerID: UInt64?, isRetry: Bool = false, using transaction: Any) throws -> (Message, SNProtoContent) { let userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey() let isOpenGroupMessage = (openGroupMessageServerID != nil) // Parse the envelope let envelope = try SNProtoEnvelope.parseData(data) let storage = SNMessagingKitConfiguration.shared.storage - guard !Set(storage.getReceivedMessageTimestamps(using: transaction)).contains(envelope.timestamp) else { throw Error.duplicateMessage } + // If the message failed to process the first time around we retry it later (if the error is retryable). In this case the timestamp + // will already be in the database but we don't want to treat the message as a duplicate. The isRetry flag is a simple workaround + // for this issue. + guard !Set(storage.getReceivedMessageTimestamps(using: transaction)).contains(envelope.timestamp) || isRetry else { throw Error.duplicateMessage } storage.addReceivedMessageTimestamp(envelope.timestamp, using: transaction) // Decrypt the contents guard let ciphertext = envelope.content else { throw Error.noData } @@ -93,9 +95,13 @@ public enum MessageReceiver { try decrypt() } catch { do { - try MessageSender.requestEncryptionKeyPair(for: groupPublicKey!, using: transaction as! YapDatabaseReadWriteTransaction) - } catch { - // Do nothing + let now = Date() + // Don't spam encryption key pair requests + let shouldRequestEncryptionKeyPair = given(lastEncryptionKeyPairRequest[groupPublicKey!]) { now.timeIntervalSince($0) > 30 } ?? true + if shouldRequestEncryptionKeyPair { + try MessageSender.requestEncryptionKeyPair(for: groupPublicKey!, using: transaction as! YapDatabaseReadWriteTransaction) + lastEncryptionKeyPairRequest[groupPublicKey!] = now + } } throw error // Throw the * decryption * error and not the error generated by requestEncryptionKeyPair (if it generated one) } From 517360a7739d1f1e0f7a1861f882c6fefd6a820b Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 9 Feb 2021 16:25:45 +1100 Subject: [PATCH 11/13] Fix open group config message syncing --- Session/Open Groups/JoinPublicChatVC.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Session/Open Groups/JoinPublicChatVC.swift b/Session/Open Groups/JoinPublicChatVC.swift index 267a6618e..1e2b94fef 100644 --- a/Session/Open Groups/JoinPublicChatVC.swift +++ b/Session/Open Groups/JoinPublicChatVC.swift @@ -132,10 +132,12 @@ final class JoinPublicChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie } isJoining = true ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] _ in - Storage.shared.write(with: { transaction in + Storage.shared.write { transaction in OpenGroupManager.shared.add(with: urlAsString, using: transaction) .done(on: DispatchQueue.main) { [weak self] _ in self?.presentingViewController!.dismiss(animated: true, completion: nil) + let appDelegate = UIApplication.shared.delegate as! AppDelegate + appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...) } .catch(on: DispatchQueue.main) { [weak self] error in self?.dismiss(animated: true, completion: nil) // Dismiss the loader @@ -148,10 +150,7 @@ final class JoinPublicChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie self?.isJoining = false self?.showError(title: title, message: message) } - }, completion: { - let appDelegate = UIApplication.shared.delegate as! AppDelegate - appDelegate.forceSyncConfigurationNowIfNeeded().retainUntilComplete() // FIXME: It's probably cleaner to do this inside addOpenGroup(...) - }) + } } } From 4803b96eb1208c5ecab011eea2a5449bfc985b20 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 10 Feb 2021 10:19:07 +1100 Subject: [PATCH 12/13] Only handle the first config sync message after restoration --- Podfile.lock | 2 +- .../Sending & Receiving/MessageReceiver+Handling.swift | 3 ++- SessionMessagingKit/Sending & Receiving/MessageSender.swift | 1 + SessionUtilitiesKit/General/SNUserDefaults.swift | 1 + 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index 78a63d0c5..2e3981798 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -219,4 +219,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: bb4f6cffd6e7c08814b945e1787d01d639036b1e -COCOAPODS: 1.10.0.rc.1 +COCOAPODS: 1.10.1 diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index cfbb6b3b6..a0dba8c78 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -144,7 +144,7 @@ extension MessageReceiver { } private static func handleConfigurationMessage(_ message: ConfigurationMessage, using transaction: Any) { - guard message.sender == getUserHexEncodedPublicKey() else { return } + guard message.sender == getUserHexEncodedPublicKey(), !UserDefaults.standard[.hasSyncedConfiguration] else { return } let storage = SNMessagingKitConfiguration.shared.storage let allClosedGroupPublicKeys = storage.getUserClosedGroupPublicKeys() for closedGroup in message.closedGroups { @@ -157,6 +157,7 @@ extension MessageReceiver { guard !allOpenGroups.contains(openGroupURL) else { continue } OpenGroupManager.shared.add(with: openGroupURL, using: transaction).retainUntilComplete() } + UserDefaults.standard[.hasSyncedConfiguration] = true } @discardableResult diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 73f8e476d..9bd97972f 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -364,6 +364,7 @@ public final class MessageSender : NSObject { OWSDisappearingMessagesJob.shared().startAnyExpiration(for: tsMessage, expirationStartedAt: NSDate.millisecondTimestamp(), transaction: transaction) // Sync the message if: // • it's a visible message + // • the destination was a contact // • we didn't sync it already let userPublicKey = getUserHexEncodedPublicKey() if case .contact(let publicKey) = destination, !isSyncMessage, let message = message as? VisibleMessage { diff --git a/SessionUtilitiesKit/General/SNUserDefaults.swift b/SessionUtilitiesKit/General/SNUserDefaults.swift index 0d67755b8..e2a51e84e 100644 --- a/SessionUtilitiesKit/General/SNUserDefaults.swift +++ b/SessionUtilitiesKit/General/SNUserDefaults.swift @@ -5,6 +5,7 @@ public enum SNUserDefaults { public enum Bool : Swift.String { case hasLaunchedOnce case hasSeenGIFMetadataWarning + case hasSyncedConfiguration case hasViewedSeed case isUsingFullAPNs case isMigratingToV2KeyPair From ece564988e819f5e6cfeb4ef36896cdfebe1a372 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 10 Feb 2021 10:56:46 +1100 Subject: [PATCH 13/13] Allow self-sending of closed group control messages --- .../Control Messages/ClosedGroupControlMessage.swift | 7 +------ .../Sending & Receiving/MessageReceiver+Handling.swift | 10 +++------- .../MessageSender+ClosedGroups.swift | 1 - .../Sending & Receiving/MessageSender.swift | 8 ++++++-- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift index 4d4723047..a93518ca3 100644 --- a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift @@ -11,12 +11,7 @@ public final class ClosedGroupControlMessage : ControlMessage { } } - public override var isSelfSendValid: Bool { - switch kind { - case .new: return false - default: return true - } - } + public override var isSelfSendValid: Bool { true } // MARK: Kind public enum Kind : CustomStringConvertible { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index a0dba8c78..9561777dd 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -279,6 +279,9 @@ extension MessageReceiver { } else { thread = TSGroupThread.getOrCreateThread(with: group, transaction: transaction) thread.save(with: transaction) + // Notify the user + let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate) + infoMessage.save(with: transaction) } // Add the group to the user's set of public keys to poll for Storage.shared.addClosedGroupPublicKey(groupPublicKey, using: transaction) @@ -288,9 +291,6 @@ extension MessageReceiver { Storage.shared.setClosedGroupFormationTimestamp(to: messageSentTimestamp, for: groupPublicKey, using: transaction) // Notify the PN server let _ = PushNotificationAPI.performOperation(.subscribe, for: groupPublicKey, publicKey: getUserHexEncodedPublicKey()) - // Notify the user - let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate) - infoMessage.save(with: transaction) } private static func handleClosedGroupEncryptionKeyPair(_ message: ClosedGroupControlMessage, using transaction: Any) { @@ -418,10 +418,6 @@ extension MessageReceiver { let members: Set = didAdminLeave ? [] : Set(group.groupMemberIds).subtracting([ message.sender! ]) // If the admin leaves the group is disbanded let userPublicKey = getUserHexEncodedPublicKey() let isCurrentUserAdmin = group.groupAdminIds.contains(userPublicKey) - // Guard against self-sends - guard message.sender != getUserHexEncodedPublicKey() else { - return SNLog("Ignoring invalid closed group update.") - } // If a regular member left: // • Distribute a new encryption key pair if we're the admin of the group // If the admin left: diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift index 57386e521..19d833a1c 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift @@ -24,7 +24,6 @@ extension MessageSender { // Send a closed group update message to all members individually var promises: [Promise] = [] for member in members { - guard member != userPublicKey else { continue } let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) thread.save(with: transaction) let closedGroupControlMessageKind = ClosedGroupControlMessage.Kind.new(publicKey: Data(hex: groupPublicKey), name: name, diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index 9bd97972f..093885aab 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -137,8 +137,12 @@ public final class MessageSender : NSObject { } // Validate the message guard message.isValid else { handleFailure(with: Error.invalidMessage, using: transaction); return promise } - // Stop here if this is a self-send (unless it's a configuration message or a sync message) - guard !isSelfSend || message is ConfigurationMessage || isSyncMessage else { + // Stop here if this is a self-send, unless it's: + // • a configuration message + // • a sync message + // • a closed group control message of type `new` + let isNewClosedGroupControlMessage = given(message as? ClosedGroupControlMessage) { if case .new = $0.kind { return true } else { return false } } ?? false + guard !isSelfSend || message is ConfigurationMessage || isSyncMessage || isNewClosedGroupControlMessage else { storage.write(with: { transaction in MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction) seal.fulfill(())