Merge branch 'multi-device' into ui

pull/347/head
nielsandriesse 4 years ago
commit 1924e01edc

@ -218,4 +218,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 2fca3f32c171e1324c9e3809b96a32d4a929d05c PODFILE CHECKSUM: 2fca3f32c171e1324c9e3809b96a32d4a929d05c
COCOAPODS: 1.10.0.rc.1 COCOAPODS: 1.10.1

@ -5267,7 +5267,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 173; CURRENT_PROJECT_VERSION = 175;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5288,7 +5288,7 @@
INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist; INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; 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; MTL_ENABLE_DEBUG_INFO = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -5336,7 +5336,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 173; CURRENT_PROJECT_VERSION = 175;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
@ -5362,7 +5362,7 @@
INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist; INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; 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_ENABLE_DEBUG_INFO = NO;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.ShareExtension";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
@ -5397,7 +5397,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 173; CURRENT_PROJECT_VERSION = 175;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = "$(inherited)"; FRAMEWORK_SEARCH_PATHS = "$(inherited)";
@ -5416,7 +5416,7 @@
INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist; INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; 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_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -5467,7 +5467,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 173; CURRENT_PROJECT_VERSION = 175;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
ENABLE_NS_ASSERTIONS = NO; ENABLE_NS_ASSERTIONS = NO;
@ -5491,7 +5491,7 @@
INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist; INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.0; IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; 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_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger.NotificationServiceExtension";
@ -6352,7 +6352,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 173; CURRENT_PROJECT_VERSION = 175;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -6388,7 +6388,7 @@
"$(SRCROOT)", "$(SRCROOT)",
); );
LLVM_LTO = NO; LLVM_LTO = NO;
MARKETING_VERSION = 1.7.7; MARKETING_VERSION = 1.7.8;
OTHER_LDFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)";
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\""; OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
@ -6420,7 +6420,7 @@
CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements; CODE_SIGN_ENTITLEMENTS = Session/Meta/Signal.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 173; CURRENT_PROJECT_VERSION = 175;
DEVELOPMENT_TEAM = SUQ8J2PCT7; DEVELOPMENT_TEAM = SUQ8J2PCT7;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@ -6456,7 +6456,7 @@
"$(SRCROOT)", "$(SRCROOT)",
); );
LLVM_LTO = NO; LLVM_LTO = NO;
MARKETING_VERSION = 1.7.7; MARKETING_VERSION = 1.7.8;
OTHER_LDFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)";
PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger"; PRODUCT_BUNDLE_IDENTIFIER = "com.loki-project.loki-messenger";
PRODUCT_NAME = Session; PRODUCT_NAME = Session;

@ -271,9 +271,9 @@ final class EditClosedGroupVC : BaseVC, UITableViewDataSource, UITableViewDelega
Storage.write(with: { [weak self] transaction in Storage.write(with: { [weak self] transaction in
do { do {
if !members.contains(getUserHexEncodedPublicKey()) { if !members.contains(getUserHexEncodedPublicKey()) {
try MessageSender.leave(groupPublicKey, using: transaction) try MessageSender.v2_leave(groupPublicKey, using: transaction)
} else { } else {
try MessageSender.update(groupPublicKey, with: members, name: name, transaction: transaction) try MessageSender.v2_update(groupPublicKey, with: members, name: name, transaction: transaction)
} }
} catch { } catch {
DispatchQueue.main.async { DispatchQueue.main.async {

@ -939,7 +939,7 @@ static CGRect oldframe;
if (gThread.isClosedGroup) { if (gThread.isClosedGroup) {
NSString *groupPublicKey = [LKGroupUtilities getDecodedGroupID:gThread.groupModel.groupId]; NSString *groupPublicKey = [LKGroupUtilities getDecodedGroupID:gThread.groupModel.groupId];
[LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[SNMessageSender leaveClosedGroupWithPublicKey:groupPublicKey using:transaction error:nil]; [SNMessageSender v2_leaveClosedGroupWithPublicKey:groupPublicKey using:transaction error:nil];
}]; }];
} }

@ -398,7 +398,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIViewC
let groupID = thread.groupModel.groupId let groupID = thread.groupModel.groupId
let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID) let groupPublicKey = LKGroupUtilities.getDecodedGroupID(groupID)
do { do {
try MessageSender.leave(groupPublicKey, using: transaction) try MessageSender.v2_leave(groupPublicKey, using: transaction)
} catch { } catch {
// TODO: Handle // TODO: Handle
} }

@ -168,8 +168,8 @@ final class SeedVC : BaseVC {
self.seedReminderView.subtitle = NSLocalizedString("view_seed_reminder_subtitle_3", comment: "") self.seedReminderView.subtitle = NSLocalizedString("view_seed_reminder_subtitle_3", comment: "")
}, completion: nil) }, completion: nil)
seedReminderView.setProgress(1, animated: true) seedReminderView.setProgress(1, animated: true)
// UserDefaults.standard[.hasViewedSeed] = true UserDefaults.standard[.hasViewedSeed] = true
// NotificationCenter.default.post(name: .seedViewed, object: nil) NotificationCenter.default.post(name: .seedViewed, object: nil)
} }
@objc private func copyMnemonic() { @objc private func copyMnemonic() {

@ -132,10 +132,12 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
} }
isJoining = true isJoining = true
ModalActivityIndicatorViewController.present(fromViewController: navigationController!, canCancel: false) { [weak self] _ in 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) OpenGroupManager.shared.add(with: urlAsString, using: transaction)
.done(on: DispatchQueue.main) { [weak self] _ in .done(on: DispatchQueue.main) { [weak self] _ in
self?.presentingViewController!.dismiss(animated: true, completion: nil) 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 .catch(on: DispatchQueue.main) { [weak self] error in
self?.dismiss(animated: true, completion: nil) // Dismiss the loader self?.dismiss(animated: true, completion: nil) // Dismiss the loader
@ -148,10 +150,7 @@ final class JoinOpenGroupVC : BaseVC, UIPageViewControllerDataSource, UIPageView
self?.isJoining = false self?.isJoining = false
self?.showError(title: title, message: message) 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(...)
})
} }
} }

@ -7,6 +7,8 @@ extension Storage {
private static let closedGroupPublicKeyCollection = "SNClosedGroupPublicKeyCollection" private static let closedGroupPublicKeyCollection = "SNClosedGroupPublicKeyCollection"
private static let closedGroupFormationTimestampCollection = "SNClosedGroupFormationTimestampCollection"
public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String) -> [ECKeyPair] { public func getClosedGroupEncryptionKeyPairs(for groupPublicKey: String) -> [ECKeyPair] {
let collection = Storage.getClosedGroupEncryptionKeyPairCollection(for: groupPublicKey) let collection = Storage.getClosedGroupEncryptionKeyPairCollection(for: groupPublicKey)
var timestampsAndKeyPairs: [(timestamp: Double, keyPair: ECKeyPair)] = [] var timestampsAndKeyPairs: [(timestamp: Double, keyPair: ECKeyPair)] = []
@ -42,6 +44,18 @@ extension Storage {
(transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: groupPublicKey, inCollection: Storage.closedGroupPublicKeyCollection) (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)
}
public func getUserClosedGroupPublicKeys() -> Set<String> { public func getUserClosedGroupPublicKeys() -> Set<String> {
var result: Set<String> = [] var result: Set<String> = []
Storage.read { transaction in Storage.read { transaction in

@ -76,15 +76,14 @@ public final class JobQueue : NSObject, JobDelegate {
private func getRetryInterval(for job: Job) -> TimeInterval { private func getRetryInterval(for job: Job) -> TimeInterval {
// Arbitrary backoff factor... // Arbitrary backoff factor...
// try 1 delay: 0.00s // try 1 delay: 0.5s
// try 2 delay: 0.19s // try 2 delay: 1s
// ... // ...
// try 5 delay: 1.30s // try 5 delay: 16s
// ... // ...
// try 11 delay: 61.31s // try 11 delay: 512s
let backoffFactor = 1.9 let maxBackoff: Double = 10 * 60 // 10 minutes
let maxBackoff: Double = 60 * 60 * 1000 return 0.25 * min(maxBackoff, pow(2, Double(job.failureCount)))
return 0.1 * min(maxBackoff, pow(backoffFactor, Double(job.failureCount)))
} }
@objc private func retry(_ timer: Timer) { @objc private func retry(_ timer: Timer) {

@ -57,15 +57,17 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSC
let (promise, seal) = Promise<Void>.pending() let (promise, seal) = Promise<Void>.pending()
SNMessagingKitConfiguration.shared.storage.write(with: { transaction in // Intentionally capture self SNMessagingKitConfiguration.shared.storage.write(with: { transaction in // Intentionally capture self
do { 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) try MessageReceiver.handle(message, associatedWithProto: proto, openGroupID: self.openGroupID, isBackgroundPoll: self.isBackgroundPoll, using: transaction)
self.handleSuccess() self.handleSuccess()
seal.fulfill(()) seal.fulfill(())
} catch { } catch {
SNLog("Couldn't receive message due to error: \(error).")
if let error = error as? MessageReceiver.Error, !error.isRetryable { if let error = error as? MessageReceiver.Error, !error.isRetryable {
SNLog("Message receive job permanently failed due to error: \(error).")
self.handlePermanentFailure(error: error) self.handlePermanentFailure(error: error)
} else { } else {
SNLog("Couldn't receive message due to error: \(error).")
self.handleFailure(error: error) self.handleFailure(error: error)
} }
seal.fulfill(()) // The promise is just used to keep track of when we're done seal.fulfill(()) // The promise is just used to keep track of when we're done

@ -10,23 +10,22 @@ public final class ClosedGroupControlMessage : ControlMessage {
} }
} }
public override var isSelfSendValid: Bool { public override var isSelfSendValid: Bool { true }
switch kind {
case .new: return false
default: return true
}
}
// MARK: Kind // MARK: Kind
public enum Kind : CustomStringConvertible { public enum Kind : CustomStringConvertible {
case new(publicKey: Data, name: String, encryptionKeyPair: ECKeyPair, members: [Data], admins: [Data]) case new(publicKey: Data, name: String, encryptionKeyPair: ECKeyPair, members: [Data], admins: [Data])
/// - Note: Deprecated in favor of more explicit group updates. /// - Note: Deprecated in favor of more explicit group updates.
case update(name: String, members: [Data]) 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 nameChange(name: String)
case membersAdded(members: [Data]) case membersAdded(members: [Data])
case membersRemoved(members: [Data]) case membersRemoved(members: [Data])
case memberLeft case memberLeft
case encryptionKeyPairRequest
public var description: String { public var description: String {
switch self { switch self {
@ -37,6 +36,7 @@ public final class ClosedGroupControlMessage : ControlMessage {
case .membersAdded: return "membersAdded" case .membersAdded: return "membersAdded"
case .membersRemoved: return "membersRemoved" case .membersRemoved: return "membersRemoved"
case .memberLeft: return "memberLeft" case .memberLeft: return "memberLeft"
case .encryptionKeyPairRequest: return "encryptionKeyPairRequest"
} }
} }
} }
@ -102,6 +102,7 @@ public final class ClosedGroupControlMessage : ControlMessage {
case .membersAdded(let members): return !members.isEmpty case .membersAdded(let members): return !members.isEmpty
case .membersRemoved(let members): return !members.isEmpty case .membersRemoved(let members): return !members.isEmpty
case .memberLeft: return true case .memberLeft: return true
case .encryptionKeyPairRequest: return true
} }
} }
@ -122,8 +123,9 @@ public final class ClosedGroupControlMessage : ControlMessage {
let members = coder.decodeObject(forKey: "members") as? [Data] else { return nil } let members = coder.decodeObject(forKey: "members") as? [Data] else { return nil }
self.kind = .update(name: name, members: members) self.kind = .update(name: name, members: members)
case "encryptionKeyPair": case "encryptionKeyPair":
let publicKey = coder.decodeObject(forKey: "publicKey") as? Data
guard let wrappers = coder.decodeObject(forKey: "wrappers") as? [KeyPairWrapper] else { return nil } 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": case "nameChange":
guard let name = coder.decodeObject(forKey: "name") as? String else { return nil } guard let name = coder.decodeObject(forKey: "name") as? String else { return nil }
self.kind = .nameChange(name: name) self.kind = .nameChange(name: name)
@ -135,6 +137,8 @@ public final class ClosedGroupControlMessage : ControlMessage {
self.kind = .membersRemoved(members: members) self.kind = .membersRemoved(members: members)
case "memberLeft": case "memberLeft":
self.kind = .memberLeft self.kind = .memberLeft
case "encryptionKeyPairRequest":
self.kind = .encryptionKeyPairRequest
default: return nil default: return nil
} }
} }
@ -154,8 +158,9 @@ public final class ClosedGroupControlMessage : ControlMessage {
coder.encode("update", forKey: "kind") coder.encode("update", forKey: "kind")
coder.encode(name, forKey: "name") coder.encode(name, forKey: "name")
coder.encode(members, forKey: "members") coder.encode(members, forKey: "members")
case .encryptionKeyPair(let wrappers): case .encryptionKeyPair(let publicKey, let wrappers):
coder.encode("encryptionKeyPair", forKey: "kind") coder.encode("encryptionKeyPair", forKey: "kind")
coder.encode(publicKey, forKey: "publicKey")
coder.encode(wrappers, forKey: "wrappers") coder.encode(wrappers, forKey: "wrappers")
case .nameChange(let name): case .nameChange(let name):
coder.encode("nameChange", forKey: "kind") coder.encode("nameChange", forKey: "kind")
@ -168,6 +173,8 @@ public final class ClosedGroupControlMessage : ControlMessage {
coder.encode(members, forKey: "members") coder.encode(members, forKey: "members")
case .memberLeft: case .memberLeft:
coder.encode("memberLeft", forKey: "kind") coder.encode("memberLeft", forKey: "kind")
case .encryptionKeyPairRequest:
coder.encode("encryptionKeyPairRequest", forKey: "kind")
} }
} }
@ -191,8 +198,9 @@ public final class ClosedGroupControlMessage : ControlMessage {
guard let name = closedGroupControlMessageProto.name else { return nil } guard let name = closedGroupControlMessageProto.name else { return nil }
kind = .update(name: name, members: closedGroupControlMessageProto.members) kind = .update(name: name, members: closedGroupControlMessageProto.members)
case .encryptionKeyPair: case .encryptionKeyPair:
let publicKey = closedGroupControlMessageProto.publicKey
let wrappers = closedGroupControlMessageProto.wrappers.compactMap { KeyPairWrapper.fromProto($0) } let wrappers = closedGroupControlMessageProto.wrappers.compactMap { KeyPairWrapper.fromProto($0) }
kind = .encryptionKeyPair(wrappers) kind = .encryptionKeyPair(publicKey: publicKey, wrappers: wrappers)
case .nameChange: case .nameChange:
guard let name = closedGroupControlMessageProto.name else { return nil } guard let name = closedGroupControlMessageProto.name else { return nil }
kind = .nameChange(name: name) kind = .nameChange(name: name)
@ -202,6 +210,8 @@ public final class ClosedGroupControlMessage : ControlMessage {
kind = .membersRemoved(members: closedGroupControlMessageProto.members) kind = .membersRemoved(members: closedGroupControlMessageProto.members)
case .memberLeft: case .memberLeft:
kind = .memberLeft kind = .memberLeft
case .encryptionKeyPairRequest:
kind = .encryptionKeyPairRequest
} }
return ClosedGroupControlMessage(kind: kind) return ClosedGroupControlMessage(kind: kind)
} }
@ -231,8 +241,11 @@ public final class ClosedGroupControlMessage : ControlMessage {
closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .update) closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .update)
closedGroupControlMessage.setName(name) closedGroupControlMessage.setName(name)
closedGroupControlMessage.setMembers(members) closedGroupControlMessage.setMembers(members)
case .encryptionKeyPair(let wrappers): case .encryptionKeyPair(let publicKey, let wrappers):
closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .encryptionKeyPair) closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .encryptionKeyPair)
if let publicKey = publicKey {
closedGroupControlMessage.setPublicKey(publicKey)
}
closedGroupControlMessage.setWrappers(wrappers.compactMap { $0.toProto() }) closedGroupControlMessage.setWrappers(wrappers.compactMap { $0.toProto() })
case .nameChange(let name): case .nameChange(let name):
closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .nameChange) closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .nameChange)
@ -245,6 +258,8 @@ public final class ClosedGroupControlMessage : ControlMessage {
closedGroupControlMessage.setMembers(members) closedGroupControlMessage.setMembers(members)
case .memberLeft: case .memberLeft:
closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .memberLeft) closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .memberLeft)
case .encryptionKeyPairRequest:
closedGroupControlMessage = SNProtoDataMessageClosedGroupControlMessage.builder(type: .encryptionKeyPairRequest)
} }
let contentProto = SNProtoContent.builder() let contentProto = SNProtoContent.builder()
let dataMessageProto = SNProtoDataMessage.builder() let dataMessageProto = SNProtoDataMessage.builder()

@ -2410,6 +2410,7 @@ extension SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper.SNProtoDataM
case membersAdded = 5 case membersAdded = 5
case membersRemoved = 6 case membersRemoved = 6
case memberLeft = 7 case memberLeft = 7
case encryptionKeyPairRequest = 8
} }
private class func SNProtoDataMessageClosedGroupControlMessageTypeWrap(_ value: SessionProtos_DataMessage.ClosedGroupControlMessage.TypeEnum) -> SNProtoDataMessageClosedGroupControlMessageType { private class func SNProtoDataMessageClosedGroupControlMessageTypeWrap(_ value: SessionProtos_DataMessage.ClosedGroupControlMessage.TypeEnum) -> SNProtoDataMessageClosedGroupControlMessageType {
@ -2421,6 +2422,7 @@ extension SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper.SNProtoDataM
case .membersAdded: return .membersAdded case .membersAdded: return .membersAdded
case .membersRemoved: return .membersRemoved case .membersRemoved: return .membersRemoved
case .memberLeft: return .memberLeft case .memberLeft: return .memberLeft
case .encryptionKeyPairRequest: return .encryptionKeyPairRequest
} }
} }
@ -2433,6 +2435,7 @@ extension SNProtoDataMessageClosedGroupControlMessageKeyPairWrapper.SNProtoDataM
case .membersAdded: return .membersAdded case .membersAdded: return .membersAdded
case .membersRemoved: return .membersRemoved case .membersRemoved: return .membersRemoved
case .memberLeft: return .memberLeft case .memberLeft: return .memberLeft
case .encryptionKeyPairRequest: return .encryptionKeyPairRequest
} }
} }

@ -1152,6 +1152,9 @@ struct SessionProtos_DataMessage {
case membersRemoved // = 6 case membersRemoved // = 6
case memberLeft // = 7 case memberLeft // = 7
/// wrappers
case encryptionKeyPairRequest // = 8
init() { init() {
self = .new self = .new
} }
@ -1165,6 +1168,7 @@ struct SessionProtos_DataMessage {
case 5: self = .membersAdded case 5: self = .membersAdded
case 6: self = .membersRemoved case 6: self = .membersRemoved
case 7: self = .memberLeft case 7: self = .memberLeft
case 8: self = .encryptionKeyPairRequest
default: return nil default: return nil
} }
} }
@ -1178,6 +1182,7 @@ struct SessionProtos_DataMessage {
case .membersAdded: return 5 case .membersAdded: return 5
case .membersRemoved: return 6 case .membersRemoved: return 6
case .memberLeft: return 7 case .memberLeft: return 7
case .encryptionKeyPairRequest: return 8
} }
} }
@ -3131,6 +3136,7 @@ extension SessionProtos_DataMessage.ClosedGroupControlMessage.TypeEnum: SwiftPro
5: .same(proto: "MEMBERS_ADDED"), 5: .same(proto: "MEMBERS_ADDED"),
6: .same(proto: "MEMBERS_REMOVED"), 6: .same(proto: "MEMBERS_REMOVED"),
7: .same(proto: "MEMBER_LEFT"), 7: .same(proto: "MEMBER_LEFT"),
8: .same(proto: "ENCRYPTION_KEY_PAIR_REQUEST"),
] ]
} }

@ -169,13 +169,14 @@ message DataMessage {
message ClosedGroupControlMessage { message ClosedGroupControlMessage {
enum Type { enum Type {
NEW = 1; // publicKey, name, encryptionKeyPair, members, admins NEW = 1; // publicKey, name, encryptionKeyPair, members, admins
UPDATE = 2; // name, members UPDATE = 2; // name, members
ENCRYPTION_KEY_PAIR = 3; // wrappers ENCRYPTION_KEY_PAIR = 3; // publicKey, wrappers
NAME_CHANGE = 4; // name NAME_CHANGE = 4; // name
MEMBERS_ADDED = 5; // members MEMBERS_ADDED = 5; // members
MEMBERS_REMOVED = 6; // members MEMBERS_REMOVED = 6; // members
MEMBER_LEFT = 7; MEMBER_LEFT = 7;
ENCRYPTION_KEY_PAIR_REQUEST = 8;
} }
message KeyPairWrapper { message KeyPairWrapper {

@ -143,19 +143,20 @@ extension MessageReceiver {
} }
private static func handleConfigurationMessage(_ message: ConfigurationMessage, using transaction: Any) { 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 storage = SNMessagingKitConfiguration.shared.storage
let allClosedGroupPublicKeys = storage.getUserClosedGroupPublicKeys() let allClosedGroupPublicKeys = storage.getUserClosedGroupPublicKeys()
for closedGroup in message.closedGroups { for closedGroup in message.closedGroups {
guard !allClosedGroupPublicKeys.contains(closedGroup.publicKey) else { continue } guard !allClosedGroupPublicKeys.contains(closedGroup.publicKey) else { continue }
handleNewClosedGroup(groupPublicKey: closedGroup.publicKey, name: closedGroup.name, encryptionKeyPair: closedGroup.encryptionKeyPair, 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) let allOpenGroups = Set(storage.getAllUserOpenGroups().keys)
for openGroupURL in message.openGroups { for openGroupURL in message.openGroups {
guard !allOpenGroups.contains(openGroupURL) else { continue } guard !allOpenGroups.contains(openGroupURL) else { continue }
OpenGroupManager.shared.add(with: openGroupURL, using: transaction).retainUntilComplete() OpenGroupManager.shared.add(with: openGroupURL, using: transaction).retainUntilComplete()
} }
UserDefaults.standard[.hasSyncedConfiguration] = true
} }
@discardableResult @discardableResult
@ -252,6 +253,7 @@ extension MessageReceiver {
case .membersAdded: handleClosedGroupMembersAdded(message, using: transaction) case .membersAdded: handleClosedGroupMembersAdded(message, using: transaction)
case .membersRemoved: handleClosedGroupMembersRemoved(message, using: transaction) case .membersRemoved: handleClosedGroupMembersRemoved(message, using: transaction)
case .memberLeft: handleClosedGroupMemberLeft(message, using: transaction) case .memberLeft: handleClosedGroupMemberLeft(message, using: transaction)
case .encryptionKeyPairRequest: handleClosedGroupEncryptionKeyPairRequest(message, using: transaction)
} }
} }
@ -261,10 +263,11 @@ extension MessageReceiver {
let groupPublicKey = publicKeyAsData.toHexString() let groupPublicKey = publicKeyAsData.toHexString()
let members = membersAsData.map { $0.toHexString() } let members = membersAsData.map { $0.toHexString() }
let admins = adminsAsData.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 let transaction = transaction as! YapDatabaseReadWriteTransaction
// Create the group // Create the group
let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey)
@ -276,21 +279,24 @@ extension MessageReceiver {
} else { } else {
thread = TSGroupThread.getOrCreateThread(with: group, transaction: transaction) thread = TSGroupThread.getOrCreateThread(with: group, transaction: transaction)
thread.save(with: 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 // Add the group to the user's set of public keys to poll for
Storage.shared.addClosedGroupPublicKey(groupPublicKey, using: transaction) Storage.shared.addClosedGroupPublicKey(groupPublicKey, using: transaction)
// Store the key pair // Store the key pair
Storage.shared.addClosedGroupEncryptionKeyPair(encryptionKeyPair, for: groupPublicKey, using: transaction) 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 // Notify the PN server
let _ = PushNotificationAPI.performOperation(.subscribe, for: groupPublicKey, publicKey: getUserHexEncodedPublicKey()) 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) { private static func handleClosedGroupEncryptionKeyPair(_ message: ClosedGroupControlMessage, using transaction: Any) {
// Prepare // 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 transaction = transaction as! YapDatabaseReadWriteTransaction
let userPublicKey = getUserHexEncodedPublicKey() let userPublicKey = getUserHexEncodedPublicKey()
guard let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else { guard let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else {
@ -301,8 +307,8 @@ extension MessageReceiver {
guard let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else { guard let thread = TSGroupThread.fetch(uniqueId: threadID, transaction: transaction) else {
return SNLog("Ignoring closed group encryption key pair for nonexistent group.") return SNLog("Ignoring closed group encryption key pair for nonexistent group.")
} }
guard thread.groupModel.groupAdminIds.contains(message.sender!) else { guard thread.groupModel.groupMemberIds.contains(message.sender!) else {
return SNLog("Ignoring closed group encryption key pair from non-admin.") return SNLog("Ignoring closed group encryption key pair from non-member.")
} }
// Find our wrapper and decrypt it if possible // Find our wrapper and decrypt it if possible
guard let wrapper = wrappers.first(where: { $0.publicKey == userPublicKey }), let encryptedKeyPair = wrapper.encryptedKeyPair else { return } guard let wrapper = wrappers.first(where: { $0.publicKey == userPublicKey }), let encryptedKeyPair = wrapper.encryptedKeyPair else { return }
@ -325,7 +331,11 @@ extension MessageReceiver {
} catch { } catch {
return SNLog("Couldn't parse closed group encryption key pair.") 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) Storage.shared.addClosedGroupEncryptionKeyPair(keyPair, for: groupPublicKey, using: transaction)
SNLog("Received a new closed group encryption key pair.") SNLog("Received a new closed group encryption key pair.")
} }
@ -411,13 +421,20 @@ extension MessageReceiver {
performIfValid(for: message, using: transaction) { groupID, thread, group in performIfValid(for: message, using: transaction) { groupID, thread, group in
let didAdminLeave = group.groupAdminIds.contains(message.sender!) let didAdminLeave = group.groupAdminIds.contains(message.sender!)
let members: Set<String> = didAdminLeave ? [] : Set(group.groupMemberIds).subtracting([ message.sender! ]) // If the admin leaves the group is disbanded let members: Set<String> = didAdminLeave ? [] : Set(group.groupMemberIds).subtracting([ message.sender! ]) // If the admin leaves the group is disbanded
// Guard against self-sends let userPublicKey = getUserHexEncodedPublicKey()
guard message.sender != getUserHexEncodedPublicKey() else { let isCurrentUserAdmin = group.groupAdminIds.contains(userPublicKey)
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
// Generate and distribute a new encryption key pair if needed // If the admin left:
let isCurrentUserAdmin = group.groupAdminIds.contains(getUserHexEncodedPublicKey()) // Don't distribute a new encryption key pair
if isCurrentUserAdmin { // 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 { do {
try MessageSender.generateAndSendNewEncryptionKeyPair(for: groupPublicKey, to: members, using: transaction) try MessageSender.generateAndSendNewEncryptionKeyPair(for: groupPublicKey, to: members, using: transaction)
} catch { } catch {
@ -435,6 +452,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) { private static func performIfValid(for message: ClosedGroupControlMessage, using transaction: Any, _ update: (Data, TSGroupThread, TSGroupModel) -> Void) {
// Prepare // Prepare
let transaction = transaction as! YapDatabaseReadWriteTransaction let transaction = transaction as! YapDatabaseReadWriteTransaction
@ -447,8 +488,10 @@ extension MessageReceiver {
} }
let group = thread.groupModel let group = thread.groupModel
// Check that the message isn't from before the group was created // Check that the message isn't from before the group was created
guard Double(message.sentTimestamp!) > thread.creationDate.timeIntervalSince1970 * 1000 else { if let formationTimestamp = Storage.shared.getClosedGroupFormationTimestamp(for: groupPublicKey) {
return SNLog("Ignoring closed group update from before thread was created.") 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 // Check that the sender is a member of the group
guard Set(group.groupMemberIds).contains(message.sender!) else { guard Set(group.groupMemberIds).contains(message.sender!) else {

@ -1,6 +1,7 @@
import SessionUtilitiesKit import SessionUtilitiesKit
public enum MessageReceiver { public enum MessageReceiver {
private static var lastEncryptionKeyPairRequest: [String:Date] = [:]
public enum Error : LocalizedError { public enum Error : LocalizedError {
case duplicateMessage case duplicateMessage
@ -18,11 +19,10 @@ public enum MessageReceiver {
// Shared sender keys // Shared sender keys
case invalidGroupPublicKey case invalidGroupPublicKey
case noGroupKeyPair case noGroupKeyPair
case sharedSecretGenerationFailed
public var isRetryable: Bool { public var isRetryable: Bool {
switch self { 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 default: return true
} }
} }
@ -44,18 +44,20 @@ public enum MessageReceiver {
// Shared sender keys // Shared sender keys
case .invalidGroupPublicKey: return "Invalid group public key." case .invalidGroupPublicKey: return "Invalid group public key."
case .noGroupKeyPair: return "Missing group key pair." 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 userPublicKey = SNMessagingKitConfiguration.shared.storage.getUserPublicKey()
let isOpenGroupMessage = (openGroupMessageServerID != nil) let isOpenGroupMessage = (openGroupMessageServerID != nil)
// Parse the envelope // Parse the envelope
let envelope = try SNProtoEnvelope.parseData(data) let envelope = try SNProtoEnvelope.parseData(data)
let storage = SNMessagingKitConfiguration.shared.storage 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) storage.addReceivedMessageTimestamp(envelope.timestamp, using: transaction)
// Decrypt the contents // Decrypt the contents
guard let ciphertext = envelope.content else { throw Error.noData } guard let ciphertext = envelope.content else { throw Error.noData }
@ -88,8 +90,21 @@ public enum MessageReceiver {
} }
} }
} }
try decrypt()
groupPublicKey = envelope.source groupPublicKey = envelope.source
do {
try decrypt()
} catch {
do {
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)
}
default: throw Error.unknownEnvelopeType default: throw Error.unknownEnvelopeType
} }
} }

@ -23,7 +23,6 @@ extension MessageSender {
// Send a closed group update message to all members individually // Send a closed group update message to all members individually
var promises: [Promise<Void>] = [] var promises: [Promise<Void>] = []
for member in members { for member in members {
guard member != userPublicKey else { continue }
let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction)
thread.save(with: transaction) thread.save(with: transaction)
let closedGroupControlMessageKind = ClosedGroupControlMessage.Kind.new(publicKey: Data(hex: groupPublicKey), name: name, let closedGroupControlMessageKind = ClosedGroupControlMessage.Kind.new(publicKey: Data(hex: groupPublicKey), name: name,
@ -68,7 +67,7 @@ extension MessageSender {
let ciphertext = try MessageSender.encryptWithSessionProtocol(plaintext, for: publicKey) let ciphertext = try MessageSender.encryptWithSessionProtocol(plaintext, for: publicKey)
return ClosedGroupControlMessage.KeyPairWrapper(publicKey: publicKey, encryptedKeyPair: ciphertext) 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 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 // Store it * after * having sent out the message to the group
SNMessagingKitConfiguration.shared.storage.write { transaction in SNMessagingKitConfiguration.shared.storage.write { transaction in
@ -233,6 +232,21 @@ extension MessageSender {
infoMessage.save(with: transaction) 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 // MARK: - Deprecated

@ -137,8 +137,12 @@ public final class MessageSender : NSObject {
} }
// Validate the message // Validate the message
guard message.isValid else { handleFailure(with: Error.invalidMessage, using: transaction); return promise } 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) // Stop here if this is a self-send, unless it's:
guard !isSelfSend || message is ConfigurationMessage || isSyncMessage else { // 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 storage.write(with: { transaction in
MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction) MessageSender.handleSuccessfulMessageSend(message, to: destination, using: transaction)
seal.fulfill(()) seal.fulfill(())
@ -364,6 +368,7 @@ public final class MessageSender : NSObject {
OWSDisappearingMessagesJob.shared().startAnyExpiration(for: tsMessage, expirationStartedAt: NSDate.millisecondTimestamp(), transaction: transaction) OWSDisappearingMessagesJob.shared().startAnyExpiration(for: tsMessage, expirationStartedAt: NSDate.millisecondTimestamp(), transaction: transaction)
// Sync the message if: // Sync the message if:
// it's a visible message // it's a visible message
// the destination was a contact
// we didn't sync it already // we didn't sync it already
let userPublicKey = getUserHexEncodedPublicKey() let userPublicKey = getUserHexEncodedPublicKey()
if case .contact(let publicKey) = destination, !isSyncMessage, let message = message as? VisibleMessage { if case .contact(let publicKey) = destination, !isSyncMessage, let message = message as? VisibleMessage {

@ -438,10 +438,12 @@ public enum OnionRequestAPI {
OnionRequestAPI.snodeFailureCount[snode] = snodeFailureCount OnionRequestAPI.snodeFailureCount[snode] = snodeFailureCount
} }
} else { } else {
handleUnspecificError() // Do nothing
} }
} else if let message = json?["result"] as? String, message == "Loki Server error" { } else if let message = json?["result"] as? String, message == "Loki Server error" {
// Do nothing // Do nothing
} else if statusCode == 0 { // Timeout
// Do nothing
} else { } else {
handleUnspecificError() handleUnspecificError()
} }

@ -339,7 +339,7 @@ public final class SnodeAPI : NSObject {
} }
} }
switch statusCode { switch statusCode {
case 0, 400, 500, 503: case 500, 502, 503:
// The snode is unreachable // The snode is unreachable
handleBadSnode() handleBadSnode()
case 406: case 406:

@ -5,6 +5,7 @@ public enum SNUserDefaults {
public enum Bool : Swift.String { public enum Bool : Swift.String {
case hasLaunchedOnce case hasLaunchedOnce
case hasSeenGIFMetadataWarning case hasSeenGIFMetadataWarning
case hasSyncedConfiguration
case hasViewedSeed case hasViewedSeed
case isUsingFullAPNs case isUsingFullAPNs
case isMigratingToV2KeyPair case isMigratingToV2KeyPair

Loading…
Cancel
Save