diff --git a/Session/AppDelegate+SharedSenderKeys.swift b/Session/AppDelegate+SharedSenderKeys.swift new file mode 100644 index 000000000..23bd59871 --- /dev/null +++ b/Session/AppDelegate+SharedSenderKeys.swift @@ -0,0 +1,7 @@ + +extension AppDelegate : SharedSenderKeysDelegate { + + public func requestSenderKey(for groupPublicKey: String, senderPublicKey: String, using transaction: Any) { + ClosedGroupsProtocol.requestSenderKey(for: groupPublicKey, senderPublicKey: senderPublicKey, using: transaction as! YapDatabaseReadWriteTransaction) + } +} diff --git a/Session/Configuration.swift b/Session/Configuration.swift new file mode 100644 index 000000000..4ff7101f6 --- /dev/null +++ b/Session/Configuration.swift @@ -0,0 +1,11 @@ +import SessionProtocolKit +import SessionSnodeKit + +@objc(SNConfiguration) +final class Configuration : NSObject { + + @objc func performMainSetup() { + SessionProtocolKit.configure(storage: Storage.shared, sharedSenderKeysDelegate: UIApplication.shared.delegate as! AppDelegate) + SessionSnodeKit.configure(storage: Storage.shared) + } +} diff --git a/Session/Storage+SessionProtocolKit.swift b/Session/Storage+SessionProtocolKit.swift new file mode 100644 index 000000000..7700de54f --- /dev/null +++ b/Session/Storage+SessionProtocolKit.swift @@ -0,0 +1,24 @@ + +extension Storage : SessionProtocolKitStorageProtocol { + + private func getClosedGroupRatchetCollection(_ collection: ClosedGroupRatchetCollectionType, for groupPublicKey: String) -> String { + switch collection { + case .old: return "LokiOldClosedGroupRatchetCollection.\(groupPublicKey)" + case .current: return "LokiClosedGroupRatchetCollection.\(groupPublicKey)" + } + } + + public func getClosedGroupRatchet(for groupPublicKey: String, senderPublicKey: String, from collection: ClosedGroupRatchetCollectionType = .current) -> ClosedGroupRatchet? { + let collection = getClosedGroupRatchetCollection(collection, for: groupPublicKey) + var result: ClosedGroupRatchet? + Storage.read { transaction in + result = transaction.object(forKey: senderPublicKey, inCollection: collection) as? ClosedGroupRatchet + } + return result + } + + public func setClosedGroupRatchet(for groupPublicKey: String, senderPublicKey: String, ratchet: ClosedGroupRatchet, in collection: ClosedGroupRatchetCollectionType = .current, using transaction: Any) { + let collection = getClosedGroupRatchetCollection(collection, for: groupPublicKey) + (transaction as! YapDatabaseReadWriteTransaction).setObject(ratchet, forKey: senderPublicKey, inCollection: collection) + } +} diff --git a/Session/Storage+SessionSnodeKit.swift b/Session/Storage+SessionSnodeKit.swift new file mode 100644 index 000000000..356cd72c9 --- /dev/null +++ b/Session/Storage+SessionSnodeKit.swift @@ -0,0 +1,153 @@ + +extension Storage : SessionSnodeKitStorageProtocol { + + // MARK: Onion Request Paths + internal static let onionRequestPathCollection = "LokiOnionRequestPathCollection" + + public func getOnionRequestPaths() -> [OnionRequestAPI.Path] { + let collection = Storage.onionRequestPathCollection + var result: [OnionRequestAPI.Path] = [] + Storage.read { transaction in + if + let path0Snode0 = transaction.object(forKey: "0-0", inCollection: collection) as? Snode, + let path0Snode1 = transaction.object(forKey: "0-1", inCollection: collection) as? Snode, + let path0Snode2 = transaction.object(forKey: "0-2", inCollection: collection) as? Snode { + result.append([ path0Snode0, path0Snode1, path0Snode2 ]) + if + let path1Snode0 = transaction.object(forKey: "1-0", inCollection: collection) as? Snode, + let path1Snode1 = transaction.object(forKey: "1-1", inCollection: collection) as? Snode, + let path1Snode2 = transaction.object(forKey: "1-2", inCollection: collection) as? Snode { + result.append([ path1Snode0, path1Snode1, path1Snode2 ]) + } + } + } + return result + } + + public func setOnionRequestPaths(to paths: [OnionRequestAPI.Path], using transaction: Any) { + let collection = Storage.onionRequestPathCollection + // FIXME: This approach assumes either 1 or 2 paths of length 3 each. We should do better than this. + clearOnionRequestPaths(using: transaction) + guard let transaction = transaction as? YapDatabaseReadWriteTransaction else { return } + guard paths.count >= 1 else { return } + let path0 = paths[0] + guard path0.count == 3 else { return } + transaction.setObject(path0[0], forKey: "0-0", inCollection: collection) + transaction.setObject(path0[1], forKey: "0-1", inCollection: collection) + transaction.setObject(path0[2], forKey: "0-2", inCollection: collection) + guard paths.count >= 2 else { return } + let path1 = paths[1] + guard path1.count == 3 else { return } + transaction.setObject(path1[0], forKey: "1-0", inCollection: collection) + transaction.setObject(path1[1], forKey: "1-1", inCollection: collection) + transaction.setObject(path1[2], forKey: "1-2", inCollection: collection) + } + + func clearOnionRequestPaths(using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).removeAllObjects(inCollection: Storage.onionRequestPathCollection) + } + + // MARK: Snode Pool + public func getSnodePool() -> Set { + var result: Set = [] + Storage.read { transaction in + transaction.enumerateKeysAndObjects(inCollection: Storage.snodePoolCollection) { _, object, _ in + guard let snode = object as? Snode else { return } + result.insert(snode) + } + } + return result + } + + public func setSnodePool(to snodePool: Set, using transaction: Any) { + clearSnodePool(in: transaction) + snodePool.forEach { snode in + (transaction as! YapDatabaseReadWriteTransaction).setObject(snode, forKey: snode.description, inCollection: Storage.snodePoolCollection) + } + } + + func clearSnodePool(in transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).removeAllObjects(inCollection: Storage.snodePoolCollection) + } + + // MARK: Swarm + public func getSwarm(for publicKey: String) -> Set { + var result: Set = [] + let collection = Storage.getSwarmCollection(for: publicKey) + Storage.read { transaction in + transaction.enumerateKeysAndObjects(inCollection: collection) { _, object, _ in + guard let snode = object as? Snode else { return } + result.insert(snode) + } + } + return result + } + + public func setSwarm(to swarm: Set, for publicKey: String, using transaction: Any) { + clearSwarm(for: publicKey, in: transaction) + let collection = Storage.getSwarmCollection(for: publicKey) + swarm.forEach { snode in + (transaction as! YapDatabaseReadWriteTransaction).setObject(snode, forKey: snode.description, inCollection: collection) + } + } + + func clearSwarm(for publicKey: String, in transaction: Any) { + let collection = Storage.getSwarmCollection(for: publicKey) + (transaction as! YapDatabaseReadWriteTransaction).removeAllObjects(inCollection: collection) + } + + // MARK: Last Message Hash + private static let lastMessageHashCollection = "LokiLastMessageHashCollection" + + func getLastMessageHashInfo(for snode: Snode, associatedWith publicKey: String) -> JSON? { + let key = "\(snode.address):\(snode.port).\(publicKey)" + var result: JSON? + Storage.read { transaction in + result = transaction.object(forKey: key, inCollection: Storage.lastMessageHashCollection) as? JSON + } + if let result = result { + guard result["hash"] as? String != nil else { return nil } + guard result["expirationDate"] as? NSNumber != nil else { return nil } + } + return result + } + + public func getLastMessageHash(for snode: Snode, associatedWith publicKey: String) -> String? { + return getLastMessageHashInfo(for: snode, associatedWith: publicKey)?["hash"] as? String + } + + public func setLastMessageHashInfo(for snode: Snode, associatedWith publicKey: String, to lastMessageHashInfo: JSON, using transaction: Any) { + let key = "\(snode.address):\(snode.port).\(publicKey)" + guard lastMessageHashInfo.count == 2 && lastMessageHashInfo["hash"] as? String != nil && lastMessageHashInfo["expirationDate"] as? NSNumber != nil else { return } + (transaction as! YapDatabaseReadWriteTransaction).setObject(lastMessageHashInfo, forKey: key, inCollection: Storage.lastMessageHashCollection) + } + + public func pruneLastMessageHashInfoIfExpired(for snode: Snode, associatedWith publicKey: String, using transaction: Any) { + guard let lastMessageHashInfo = getLastMessageHashInfo(for: snode, associatedWith: publicKey), + (lastMessageHashInfo["hash"] as? String) != nil, let expirationDate = (lastMessageHashInfo["expirationDate"] as? NSNumber)?.uint64Value else { return } + let now = NSDate.millisecondTimestamp() + if now >= expirationDate { + removeLastMessageHashInfo(for: snode, associatedWith: publicKey, using: transaction) + } + } + + func removeLastMessageHashInfo(for snode: Snode, associatedWith publicKey: String, using transaction: Any) { + let key = "\(snode.address):\(snode.port).\(publicKey)" + (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: key, inCollection: Storage.lastMessageHashCollection) + } + + // MARK: Received Messages + private static let receivedMessagesCollection = "LokiReceivedMessagesCollection" + + public func getReceivedMessages(for publicKey: String) -> Set { + var result: Set? + Storage.read { transaction in + result = transaction.object(forKey: publicKey, inCollection: Storage.receivedMessagesCollection) as? Set + } + return result ?? [] + } + + public func setReceivedMessages(to receivedMessages: Set, for publicKey: String, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).setObject(receivedMessages, forKey: publicKey, inCollection: Storage.receivedMessagesCollection) + } +} diff --git a/Session/Storage+Shared.swift b/Session/Storage+Shared.swift new file mode 100644 index 000000000..f1ed25812 --- /dev/null +++ b/Session/Storage+Shared.swift @@ -0,0 +1,13 @@ + +extension Storage { + + public static let shared = Storage() + + public func with(_ work: @escaping (Any) -> Void) { + Storage.writeSync { work($0) } + } + + public func getUserPublicKey() -> String? { + return OWSIdentityManager.shared().identityKeyPair()?.publicKey()?.toHexString() + } +} diff --git a/SessionProtocolKit/Configuration.swift b/SessionProtocolKit/Configuration.swift index d05ff5769..9e1958654 100644 --- a/SessionProtocolKit/Configuration.swift +++ b/SessionProtocolKit/Configuration.swift @@ -8,7 +8,7 @@ public struct Configuration { public enum SessionProtocolKit { // Just to make the external API nice - public static func configure(with configuration: Configuration) { - Configuration.shared = configuration + public static func configure(storage: SessionProtocolKitStorageProtocol, sharedSenderKeysDelegate: SharedSenderKeysDelegate) { + Configuration.shared = Configuration(storage: storage, sharedSenderKeysDelegate: sharedSenderKeysDelegate) } } diff --git a/SessionProtocolKit/Storage.swift b/SessionProtocolKit/Storage.swift index 8b724fdc1..f684c90dc 100644 --- a/SessionProtocolKit/Storage.swift +++ b/SessionProtocolKit/Storage.swift @@ -5,7 +5,7 @@ public enum ClosedGroupRatchetCollectionType { public protocol SessionProtocolKitStorageProtocol { - func with(_ work: (Any) -> Void) + func with(_ work: @escaping (Any) -> Void) func getClosedGroupRatchet(for groupPublicKey: String, senderPublicKey: String, from collection: ClosedGroupRatchetCollectionType) -> ClosedGroupRatchet? func setClosedGroupRatchet(for groupPublicKey: String, senderPublicKey: String, ratchet: ClosedGroupRatchet, in collection: ClosedGroupRatchetCollectionType, using transaction: Any) diff --git a/SessionSnodeKit/Configuration.swift b/SessionSnodeKit/Configuration.swift index aa070f046..a56b6c908 100644 --- a/SessionSnodeKit/Configuration.swift +++ b/SessionSnodeKit/Configuration.swift @@ -7,7 +7,7 @@ public struct Configuration { public enum SessionSnodeKit { // Just to make the external API nice - public static func configure(with configuration: Configuration) { - Configuration.shared = configuration + public static func configure(storage: SessionSnodeKitStorageProtocol) { + Configuration.shared = Configuration(storage: storage) } } diff --git a/SessionSnodeKit/Storage.swift b/SessionSnodeKit/Storage.swift index 7fa26ed15..8d3836173 100644 --- a/SessionSnodeKit/Storage.swift +++ b/SessionSnodeKit/Storage.swift @@ -2,7 +2,7 @@ import SessionUtilitiesKit public protocol SessionSnodeKitStorageProtocol { - func with(_ work: (Any) -> Void) + func with(_ work: @escaping (Any) -> Void) func getUserPublicKey() -> String? func getOnionRequestPaths() -> [OnionRequestAPI.Path] diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 40fcc8247..51a7bc5e5 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -1188,6 +1188,11 @@ C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */; }; C3E7134F251C867C009649BB /* Sodium+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E7134E251C867C009649BB /* Sodium+Conversion.swift */; }; C3F0A530255C80BC007BE2A3 /* NoopNotificationsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */; }; + C3F0A5EC255C970D007BE2A3 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A5EB255C970D007BE2A3 /* Configuration.swift */; }; + C3F0A5FE255C988A007BE2A3 /* Storage+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A5FD255C988A007BE2A3 /* Storage+Shared.swift */; }; + C3F0A608255C98A6007BE2A3 /* Storage+SessionSnodeKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A607255C98A6007BE2A3 /* Storage+SessionSnodeKit.swift */; }; + C3F0A61A255C9902007BE2A3 /* Storage+SessionProtocolKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A619255C9902007BE2A3 /* Storage+SessionProtocolKit.swift */; }; + C3F0A62C255C9937007BE2A3 /* AppDelegate+SharedSenderKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A62B255C9937007BE2A3 /* AppDelegate+SharedSenderKeys.swift */; }; D2179CFC16BB0B3A0006F3AB /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */; }; D2179CFE16BB0B480006F3AB /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */; }; D221A08E169C9E5E00537ABF /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D221A08D169C9E5E00537ABF /* UIKit.framework */; }; @@ -2579,6 +2584,11 @@ C3E7134E251C867C009649BB /* Sodium+Conversion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sodium+Conversion.swift"; sourceTree = ""; }; C3F0A52F255C80BC007BE2A3 /* NoopNotificationsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoopNotificationsManager.swift; sourceTree = ""; }; C3F0A5B2255C915C007BE2A3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + C3F0A5EB255C970D007BE2A3 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; + C3F0A5FD255C988A007BE2A3 /* Storage+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Shared.swift"; sourceTree = ""; }; + C3F0A607255C98A6007BE2A3 /* Storage+SessionSnodeKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+SessionSnodeKit.swift"; sourceTree = ""; }; + C3F0A619255C9902007BE2A3 /* Storage+SessionProtocolKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+SessionProtocolKit.swift"; sourceTree = ""; }; + C3F0A62B255C9937007BE2A3 /* AppDelegate+SharedSenderKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+SharedSenderKeys.swift"; sourceTree = ""; }; C88965DE4F4EC4FC919BEC4E /* Pods-SessionUIKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUIKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUIKit/Pods-SessionUIKit.debug.xcconfig"; sourceTree = ""; }; CB3724C70247A916D43271FE /* Pods_Session.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Session.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; }; @@ -4534,6 +4544,11 @@ isa = PBXGroup; children = ( C3F0A58F255C8E3D007BE2A3 /* Meta */, + C3F0A62B255C9937007BE2A3 /* AppDelegate+SharedSenderKeys.swift */, + C3F0A5EB255C970D007BE2A3 /* Configuration.swift */, + C3F0A619255C9902007BE2A3 /* Storage+SessionProtocolKit.swift */, + C3F0A607255C98A6007BE2A3 /* Storage+SessionSnodeKit.swift */, + C3F0A5FD255C988A007BE2A3 /* Storage+Shared.swift */, B8CCF63B239757C10091D419 /* Components */, C31F812425258F9C00DD9FD9 /* Database */, C32B405424A961E1001117B5 /* Dependencies */, @@ -6220,6 +6235,7 @@ B8CCF63723961D6D0091D419 /* NewPrivateChatVC.swift in Sources */, 4CC0B59C20EC5F2E00CF6EE0 /* ConversationConfigurationSyncOperation.swift in Sources */, 3461293E1FD1D72B00532771 /* ExperienceUpgradeFinder.swift in Sources */, + C3F0A61A255C9902007BE2A3 /* Storage+SessionProtocolKit.swift in Sources */, 34C4E2582118957600BEA353 /* WebRTCProto.swift in Sources */, C396DAF12518408B00FF6DC5 /* EnumeratedView.swift in Sources */, 34D1F0BD1F8D108C0066283D /* AttachmentUploadView.m in Sources */, @@ -6311,6 +6327,7 @@ C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */, C31FFE57254A5FFE00F19441 /* KeyPairUtilities.swift in Sources */, 45C0DC1E1E69011F00E04C47 /* UIStoryboard+OWS.swift in Sources */, + C3F0A62C255C9937007BE2A3 /* AppDelegate+SharedSenderKeys.swift in Sources */, 452ECA4D1E087E7200E2F016 /* MessageFetcherJob.swift in Sources */, 45A6DAD61EBBF85500893231 /* ReminderView.swift in Sources */, 34D1F0881F8678AA0066283D /* ConversationViewLayout.m in Sources */, @@ -6333,6 +6350,7 @@ 4CC1ECFB211A553000CC13BE /* AppUpdateNag.swift in Sources */, C369549D24D27A3500CEB4E3 /* MultiDeviceRemovalSheet.swift in Sources */, 34B6A903218B3F63007C4606 /* TypingIndicatorView.swift in Sources */, + C3F0A608255C98A6007BE2A3 /* Storage+SessionSnodeKit.swift in Sources */, 458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */, 34B6A905218B4C91007C4606 /* TypingIndicatorInteraction.swift in Sources */, 2400888E239F30A600305217 /* SessionRestorationView.swift in Sources */, @@ -6385,11 +6403,13 @@ 34277A5E20751BDC006049F2 /* OWSQuotedMessageView.m in Sources */, 458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */, 45DDA6242090CEB500DE97F8 /* ConversationHeaderView.swift in Sources */, + C3F0A5FE255C988A007BE2A3 /* Storage+Shared.swift in Sources */, B82B4090239DD75000A248E7 /* RestoreVC.swift in Sources */, 3488F9362191CC4000E524CC /* ConversationMediaView.swift in Sources */, 45F32C242057297A00A300D5 /* MessageDetailViewController.swift in Sources */, C396DAEF2518408B00FF6DC5 /* ParsingState.swift in Sources */, 3496955C219B605E00DCFE74 /* ImagePickerController.swift in Sources */, + C3F0A5EC255C970D007BE2A3 /* Configuration.swift in Sources */, 34D1F0841F8678AA0066283D /* ConversationInputToolbar.m in Sources */, 457F671B20746193000EABCD /* QuotedReplyPreview.swift in Sources */, C31D1DE32521718E005D4DA8 /* UserSelectionVC.swift in Sources */,