diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index 2efcd923b..7909b4ffe 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -188,7 +188,6 @@ B8566C7D256F62030045A0B9 /* OWSUserProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2D3255B6DAF007E1867 /* OWSUserProfile.h */; settings = {ATTRIBUTES = (Public, ); }; }; B8569AC325CB5D2900DBA3DB /* ConversationVC+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8569AC225CB5D2900DBA3DB /* ConversationVC+Interaction.swift */; }; B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8569AE225CBB19A00DBA3DB /* DocumentView.swift */; }; - B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E7134E251C867C009649BB /* Sodium+Conversion.swift */; }; B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; }; B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; }; B875885A264503A6000E60D0 /* JoinOpenGroupModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8758859264503A6000E60D0 /* JoinOpenGroupModal.swift */; }; @@ -287,7 +286,6 @@ C31A6C5C247F2CF3001123EF /* CGRect+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */; }; C31D1DE32521718E005D4DA8 /* UserSelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */; }; C31D1DE9252172D4005D4DA8 /* ContactUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31D1DE8252172D4005D4DA8 /* ContactUtilities.swift */; }; - C31FFE57254A5FFE00F19441 /* KeyPairUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31FFE56254A5FFE00F19441 /* KeyPairUtilities.swift */; }; C3227FF6260AAD66006EA627 /* OpenGroupMessageV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3227FF5260AAD66006EA627 /* OpenGroupMessageV2.swift */; }; C32824D325C9F9790062D0A7 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; }; C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328250E25CA06020062D0A7 /* VoiceMessageView.swift */; }; @@ -348,9 +346,6 @@ C32C5BE6256DC891003C73A2 /* OWSReadReceiptManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB1D255A580900E217F9 /* OWSReadReceiptManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; C32C5BEF256DC8EE003C73A2 /* OWSDisappearingMessagesJob.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDD255A581900E217F9 /* OWSDisappearingMessagesJob.m */; }; C32C5BF8256DC8F6003C73A2 /* OWSDisappearingMessagesJob.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDA80255A57FC00E217F9 /* OWSDisappearingMessagesJob.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C32C5C01256DC9A0003C73A2 /* OWSIdentityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBA9255A581500E217F9 /* OWSIdentityManager.m */; }; - C32C5C0A256DC9B4003C73A2 /* OWSIdentityManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBF1255A581B00E217F9 /* OWSIdentityManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C32C5C1B256DC9E0003C73A2 /* General.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBC1255A581700E217F9 /* General.swift */; }; C32C5C24256DCB30003C73A2 /* NotificationsProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB7A255A581000E217F9 /* NotificationsProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; C32C5C3D256DCBAF003C73A2 /* AppReadiness.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB75255A581000E217F9 /* AppReadiness.m */; }; C32C5C46256DCBB2003C73A2 /* AppReadiness.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB01255A580700E217F9 /* AppReadiness.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -359,8 +354,6 @@ C32C5C89256DD0D2003C73A2 /* Storage+Jobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D8F17625661AFA0092EF10 /* Storage+Jobs.swift */; }; C32C5CA4256DD1DC003C73A2 /* TSAccountManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB88255A581200E217F9 /* TSAccountManager.m */; }; C32C5CAD256DD1DF003C73A2 /* TSAccountManager.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDB94255A581300E217F9 /* TSAccountManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; - C32C5CBE256DD282003C73A2 /* Storage+OnionRequests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D8F1BC25661C6F0092EF10 /* Storage+OnionRequests.swift */; }; - C32C5CBF256DD282003C73A2 /* Storage+SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A607255C98A6007BE2A3 /* Storage+SnodeAPI.swift */; }; C32C5CF0256DD3E4003C73A2 /* Storage+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A5FD255C988A007BE2A3 /* Storage+Shared.swift */; }; C32C5D19256DD493003C73A2 /* OWSLinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBA8255A581500E217F9 /* OWSLinkPreview.swift */; }; C32C5D23256DD4C0003C73A2 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA7E255A57FB00E217F9 /* Mention.swift */; }; @@ -670,7 +663,6 @@ C3C2A5A7255385C100C340D1 /* SessionSnodeKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C3C2A5BF255385EE00C340D1 /* SnodeMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B6255385EC00C340D1 /* SnodeMessage.swift */; }; C3C2A5C0255385EE00C340D1 /* Snode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B7255385EC00C340D1 /* Snode.swift */; }; - C3C2A5C1255385EE00C340D1 /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B8255385EC00C340D1 /* Storage.swift */; }; C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B9255385ED00C340D1 /* Configuration.swift */; }; C3C2A5C3255385EE00C340D1 /* OnionRequestAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5BA255385ED00C340D1 /* OnionRequestAPI.swift */; }; C3C2A5C4255385EE00C340D1 /* OnionRequestAPI+Encryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5BB255385ED00C340D1 /* OnionRequestAPI+Encryption.swift */; }; @@ -754,6 +746,9 @@ F5765D284BC6ECAC0C1D33F0 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4A93ECA93B3DE800CC7D7F6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */; }; FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3BD9871A30A790005B96BB /* Social.framework */; }; FCB11D8C1A129A76002F93FB /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCB11D8B1A129A76002F93FB /* CoreMedia.framework */; }; + FD09796727F6B0B600936362 /* Sodium+Conversion.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E7134E251C867C009649BB /* Sodium+Conversion.swift */; }; + FD09796927F6BEA700936362 /* SwarmSnode.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796827F6BEA700936362 /* SwarmSnode.swift */; }; + FD09796B27F6C67500936362 /* Failable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796A27F6C67500936362 /* Failable.swift */; }; FD17D79927F40AB800122BE0 /* _002_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79827F40AB800122BE0 /* _002_YDBToGRDBMigration.swift */; }; FD17D79C27F40B2E00122BE0 /* SMKLegacyModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79B27F40B2E00122BE0 /* SMKLegacyModels.swift */; }; FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79F27F40CC800122BE0 /* _001_InitialSetupMigration.swift */; }; @@ -765,7 +760,6 @@ FD17D7AE27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */; }; FD17D7B027F4225C00122BE0 /* Set+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7AF27F4225C00122BE0 /* Set+Utilities.swift */; }; FD17D7B327F51E5B00122BE0 /* SSKSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B227F51E5B00122BE0 /* SSKSetting.swift */; }; - FD17D7B627F51E7300122BE0 /* SettingType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B527F51E7300122BE0 /* SettingType.swift */; }; FD17D7B827F51ECA00122BE0 /* Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B727F51ECA00122BE0 /* Migration.swift */; }; FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */; }; FD17D7BD27F51F6900122BE0 /* GRDB+Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7BC27F51F6900122BE0 /* GRDB+Notifications.swift */; }; @@ -774,6 +768,15 @@ FD17D7C327F5204C00122BE0 /* Database+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C227F5204C00122BE0 /* Database+Utilities.swift */; }; FD17D7C527F5206300122BE0 /* ColumnDefinition+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C427F5206300122BE0 /* ColumnDefinition+Utilities.swift */; }; FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */; }; + FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */; }; + FD17D7CD27F546FF00122BE0 /* Setting.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7CC27F546FF00122BE0 /* Setting.swift */; }; + FD17D7D227F5797A00122BE0 /* SSKEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7D127F5797A00122BE0 /* SSKEndpoint.swift */; }; + FD17D7D427F6584600122BE0 /* SSKError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7D327F6584600122BE0 /* SSKError.swift */; }; + FD17D7D827F658E200122BE0 /* SSKDestination.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7D727F658E200122BE0 /* SSKDestination.swift */; }; + FD17D7E127F67BD400122BE0 /* SnodeReceivedMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E027F67BD400122BE0 /* SnodeReceivedMessage.swift */; }; + FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E427F6A09900122BE0 /* Identity.swift */; }; + FD17D7E727F6A16700122BE0 /* _002_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E627F6A16700122BE0 /* _002_YDBToGRDBMigration.swift */; }; + FD17D7EA27F6A1C600122BE0 /* SUKLegacyModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E927F6A1C600122BE0 /* SUKLegacyModels.swift */; }; FD28A4F227E990E800FF65E7 /* BlockingManagerRemovalMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD28A4F127E990E800FF65E7 /* BlockingManagerRemovalMigration.swift */; }; FD28A4F427EA79F800FF65E7 /* BlockListUIUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD28A4F327EA79F800FF65E7 /* BlockListUIUtils.swift */; }; FD3C907327E8387300CD579F /* OpenGroupServerIdLookupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.swift */; }; @@ -1274,7 +1277,6 @@ B8D8F17625661AFA0092EF10 /* Storage+Jobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Jobs.swift"; sourceTree = ""; }; B8D8F18825661BA50092EF10 /* Storage+OpenGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+OpenGroups.swift"; sourceTree = ""; }; B8D8F19225661BF80092EF10 /* Storage+Messaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Messaging.swift"; sourceTree = ""; }; - B8D8F1BC25661C6F0092EF10 /* Storage+OnionRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+OnionRequests.swift"; sourceTree = ""; }; B8D8F1EF256621180092EF10 /* MessageSender+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "MessageSender+Convenience.swift"; path = "../../SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift"; sourceTree = ""; }; B8EB20E6263F7E4B00773E52 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = ""; }; B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+OpenGroupInvitation.swift"; sourceTree = ""; }; @@ -1307,7 +1309,6 @@ C31D1DDC25217014005D4DA8 /* UserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCell.swift; sourceTree = ""; }; C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSelectionVC.swift; sourceTree = ""; }; C31D1DE8252172D4005D4DA8 /* ContactUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactUtilities.swift; sourceTree = ""; }; - C31FFE56254A5FFE00F19441 /* KeyPairUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPairUtilities.swift; sourceTree = ""; }; C3227FF5260AAD66006EA627 /* OpenGroupMessageV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupMessageV2.swift; sourceTree = ""; }; C328250E25CA06020062D0A7 /* VoiceMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageView.swift; sourceTree = ""; }; C328251E25CA3A900062D0A7 /* QuoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteView.swift; sourceTree = ""; }; @@ -1461,7 +1462,6 @@ C33FDBA1255A581400E217F9 /* OWSOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOperation.h; sourceTree = ""; }; C33FDBA4255A581400E217F9 /* OWSDisappearingMessagesConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDisappearingMessagesConfiguration.m; sourceTree = ""; }; C33FDBA8255A581500E217F9 /* OWSLinkPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSLinkPreview.swift; sourceTree = ""; }; - C33FDBA9255A581500E217F9 /* OWSIdentityManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSIdentityManager.m; sourceTree = ""; }; C33FDBAB255A581500E217F9 /* OWSFileSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSFileSystem.h; sourceTree = ""; }; C33FDBAE255A581500E217F9 /* SignalAccount.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalAccount.h; sourceTree = ""; }; C33FDBB4255A581600E217F9 /* NSURLSessionDataTask+StatusCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLSessionDataTask+StatusCode.m"; sourceTree = ""; }; @@ -1472,7 +1472,6 @@ C33FDBBA255A581600E217F9 /* OWSPrimaryStorage+keyFromIntLong.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OWSPrimaryStorage+keyFromIntLong.h"; sourceTree = ""; }; C33FDBBB255A581600E217F9 /* OWSPrimaryStorage+Loki.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OWSPrimaryStorage+Loki.h"; sourceTree = ""; }; C33FDBBC255A581600E217F9 /* SSKKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSKKeychainStorage.swift; sourceTree = ""; }; - C33FDBC1255A581700E217F9 /* General.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = General.swift; sourceTree = ""; }; C33FDBC2255A581700E217F9 /* SSKAsserts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKAsserts.h; sourceTree = ""; }; C33FDBCA255A581700E217F9 /* LKGroupUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LKGroupUtilities.h; sourceTree = ""; }; C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSSignalAddress.swift; sourceTree = ""; }; @@ -1484,7 +1483,6 @@ C33FDBE9255A581A00E217F9 /* TSInteraction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSInteraction.m; sourceTree = ""; }; C33FDBEC255A581B00E217F9 /* OWSRecipientIdentity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSRecipientIdentity.m; sourceTree = ""; }; C33FDBEF255A581B00E217F9 /* TSStorageKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSStorageKeys.h; sourceTree = ""; }; - C33FDBF1255A581B00E217F9 /* OWSIdentityManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSIdentityManager.h; sourceTree = ""; }; C33FDBF6255A581C00E217F9 /* NSURLSessionDataTask+StatusCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLSessionDataTask+StatusCode.h"; sourceTree = ""; }; C33FDBF8255A581C00E217F9 /* NSArray+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+OWS.h"; sourceTree = ""; }; C33FDBF9255A581C00E217F9 /* OWSError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSError.h; sourceTree = ""; }; @@ -1706,7 +1704,6 @@ C3C2A5A2255385C100C340D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; C3C2A5B6255385EC00C340D1 /* SnodeMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeMessage.swift; sourceTree = ""; }; C3C2A5B7255385EC00C340D1 /* Snode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Snode.swift; sourceTree = ""; }; - C3C2A5B8255385EC00C340D1 /* Storage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = ""; }; C3C2A5B9255385ED00C340D1 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; C3C2A5BA255385ED00C340D1 /* OnionRequestAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnionRequestAPI.swift; sourceTree = ""; }; C3C2A5BB255385ED00C340D1 /* OnionRequestAPI+Encryption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OnionRequestAPI+Encryption.swift"; sourceTree = ""; }; @@ -1764,7 +1761,6 @@ 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+SnodeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+SnodeAPI.swift"; sourceTree = ""; }; C5060C3B36A848B71CCE4685 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = ""; }; C98441E849C3CA7FE8220D33 /* Pods-SessionNotificationServiceExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionNotificationServiceExtension.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionNotificationServiceExtension/Pods-SessionNotificationServiceExtension.app store release.xcconfig"; sourceTree = ""; }; @@ -1795,6 +1791,8 @@ F9BBF530D71905BA9007675F /* Pods-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionShareExtension/Pods-SessionShareExtension.debug.xcconfig"; sourceTree = ""; }; FC3BD9871A30A790005B96BB /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; }; FCB11D8B1A129A76002F93FB /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; }; + FD09796827F6BEA700936362 /* SwarmSnode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwarmSnode.swift; sourceTree = ""; }; + FD09796A27F6C67500936362 /* Failable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Failable.swift; sourceTree = ""; }; FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = ""; }; FD17D79827F40AB800122BE0 /* _002_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_YDBToGRDBMigration.swift; sourceTree = ""; }; FD17D79B27F40B2E00122BE0 /* SMKLegacyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKLegacyModels.swift; sourceTree = ""; }; @@ -1805,7 +1803,6 @@ FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeReceivedMessageInfo.swift; sourceTree = ""; }; FD17D7AF27F4225C00122BE0 /* Set+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Utilities.swift"; sourceTree = ""; }; FD17D7B227F51E5B00122BE0 /* SSKSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKSetting.swift; sourceTree = ""; }; - FD17D7B527F51E7300122BE0 /* SettingType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingType.swift; sourceTree = ""; }; FD17D7B727F51ECA00122BE0 /* Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Migration.swift; sourceTree = ""; }; FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetMigrations.swift; sourceTree = ""; }; FD17D7BC27F51F6900122BE0 /* GRDB+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GRDB+Notifications.swift"; sourceTree = ""; }; @@ -1814,9 +1811,18 @@ FD17D7C227F5204C00122BE0 /* Database+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Database+Utilities.swift"; sourceTree = ""; }; FD17D7C427F5206300122BE0 /* ColumnDefinition+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ColumnDefinition+Utilities.swift"; sourceTree = ""; }; FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DatabaseMigrator+Utilities.swift"; sourceTree = ""; }; - FD28A4F527EAD44C00FF65E7 /* GRDBStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GRDBStorage.swift; sourceTree = ""; }; + FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = ""; }; + FD17D7CC27F546FF00122BE0 /* Setting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Setting.swift; sourceTree = ""; }; + FD17D7D127F5797A00122BE0 /* SSKEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKEndpoint.swift; sourceTree = ""; }; + FD17D7D327F6584600122BE0 /* SSKError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKError.swift; sourceTree = ""; }; + FD17D7D727F658E200122BE0 /* SSKDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKDestination.swift; sourceTree = ""; }; + FD17D7E027F67BD400122BE0 /* SnodeReceivedMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeReceivedMessage.swift; sourceTree = ""; }; + FD17D7E427F6A09900122BE0 /* Identity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Identity.swift; sourceTree = ""; }; + FD17D7E627F6A16700122BE0 /* _002_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_YDBToGRDBMigration.swift; sourceTree = ""; }; + FD17D7E927F6A1C600122BE0 /* SUKLegacyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SUKLegacyModels.swift; sourceTree = ""; }; FD28A4F127E990E800FF65E7 /* BlockingManagerRemovalMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockingManagerRemovalMigration.swift; sourceTree = ""; }; FD28A4F327EA79F800FF65E7 /* BlockListUIUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListUIUtils.swift; sourceTree = ""; }; + FD28A4F527EAD44C00FF65E7 /* GRDBStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GRDBStorage.swift; sourceTree = ""; }; FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookupMigration.swift; sourceTree = ""; }; FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookup.swift; sourceTree = ""; }; FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = ""; }; @@ -2062,7 +2068,6 @@ C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */, C31D1DE8252172D4005D4DA8 /* ContactUtilities.swift */, C35E8AAD2485E51D00ACB629 /* IP2Country.swift */, - C31FFE56254A5FFE00F19441 /* KeyPairUtilities.swift */, B84664F4235022F30083A1CD /* MentionUtilities.swift */, B886B4A82398BA1500211ABE /* QRCode.swift */, B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */, @@ -2271,6 +2276,9 @@ B8A582AB258C64E800AFD84C /* Database */ = { isa = PBXGroup; children = ( + FD17D7E827F6A1B800122BE0 /* LegacyDatabase */, + FD17D7C827F546CE00122BE0 /* Migrations */, + FD17D7CB27F546F500122BE0 /* Models */, FD17D7B427F51E6700122BE0 /* Types */, FD17D7BB27F51F5C00122BE0 /* Utilities */, FD28A4F527EAD44C00FF65E7 /* GRDBStorage.swift */, @@ -3183,7 +3191,6 @@ C37F5402255BA9ED002AEA92 /* Environment.m */, C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */, C33FDB7F255A581100E217F9 /* FullTextSearchFinder.swift */, - C33FDBC1255A581700E217F9 /* General.swift */, B82A0C3726B9098200C1BCE3 /* MessageInvalidator.swift */, C3A71D0A2558989C0043A11F /* MessageWrapper.swift */, C3A71D4E25589FF30043A11F /* NSData+messagePadding.h */, @@ -3195,8 +3202,6 @@ C33FDC1B255A581F00E217F9 /* OWSBackgroundTask.m */, C33FDC05255A581D00E217F9 /* OWSDisappearingMessagesFinder.h */, C33FDA86255A57FC00E217F9 /* OWSDisappearingMessagesFinder.m */, - C33FDBF1255A581B00E217F9 /* OWSIdentityManager.h */, - C33FDBA9255A581500E217F9 /* OWSIdentityManager.m */, C33FDAC0255A580100E217F9 /* OWSIncomingMessageFinder.h */, C33FDB1E255A580900E217F9 /* OWSIncomingMessageFinder.m */, C33FDB67255A580F00E217F9 /* OWSMediaGalleryFinder.h */, @@ -3213,7 +3218,6 @@ C33FDB91255A581200E217F9 /* ProtoUtils.h */, C33FDA6C255A57FA00E217F9 /* ProtoUtils.m */, C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */, - C3E7134E251C867C009649BB /* Sodium+Conversion.swift */, C33FDB31255A580A00E217F9 /* SSKEnvironment.h */, C33FDAF4255A580600E217F9 /* SSKEnvironment.m */, C33FDB32255A580A00E217F9 /* SSKIncrementingIdFinder.swift */, @@ -3233,15 +3237,14 @@ children = ( C3C2A5B0255385C700C340D1 /* Meta */, FD17D79D27F40CAA00122BE0 /* Database */, + FD17D7DF27F67BC400122BE0 /* Models */, + FD17D7D027F5795300122BE0 /* Types */, C3C2A5B9255385ED00C340D1 /* Configuration.swift */, C3C2A5BD255385EE00C340D1 /* Notification+OnionRequestAPI.swift */, C3C2A5BA255385ED00C340D1 /* OnionRequestAPI.swift */, C3C2A5BB255385ED00C340D1 /* OnionRequestAPI+Encryption.swift */, C3C2A5BE255385EE00C340D1 /* SnodeAPI.swift */, C3C2A5B6255385EC00C340D1 /* SnodeMessage.swift */, - C3C2A5B8255385EC00C340D1 /* Storage.swift */, - B8D8F1BC25661C6F0092EF10 /* Storage+OnionRequests.swift */, - C3F0A607255C98A6007BE2A3 /* Storage+SnodeAPI.swift */, C3C2A5CD255385F300C340D1 /* Utilities */, ); path = SessionSnodeKit; @@ -3279,6 +3282,7 @@ B8A582B9258C696200AFD84C /* Messaging */, B8A582AE258C65D000AFD84C /* Networking */, B8A582AD258C655E00AFD84C /* PromiseKit */, + FD09796527F6B0A800936362 /* Utilities */, C3D9E43025676D3D0040E4F3 /* Configuration.swift */, ); path = SessionUtilitiesKit; @@ -3591,6 +3595,15 @@ path = Session; sourceTree = ""; }; + FD09796527F6B0A800936362 /* Utilities */ = { + isa = PBXGroup; + children = ( + FD09796A27F6C67500936362 /* Failable.swift */, + C3E7134E251C867C009649BB /* Sodium+Conversion.swift */, + ); + path = Utilities; + sourceTree = ""; + }; FD17D79427F3E03300122BE0 /* Migrations */ = { isa = PBXGroup; children = ( @@ -3659,7 +3672,6 @@ children = ( FD17D7BE27F51F8200122BE0 /* ColumnExpressible.swift */, FD17D7B727F51ECA00122BE0 /* Migration.swift */, - FD17D7B527F51E7300122BE0 /* SettingType.swift */, FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */, FD17D7C027F5200100122BE0 /* TypedTableDefinition.swift */, ); @@ -3677,6 +3689,51 @@ path = Utilities; sourceTree = ""; }; + FD17D7C827F546CE00122BE0 /* Migrations */ = { + isa = PBXGroup; + children = ( + FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */, + FD17D7E627F6A16700122BE0 /* _002_YDBToGRDBMigration.swift */, + ); + path = Migrations; + sourceTree = ""; + }; + FD17D7CB27F546F500122BE0 /* Models */ = { + isa = PBXGroup; + children = ( + FD17D7E427F6A09900122BE0 /* Identity.swift */, + FD17D7CC27F546FF00122BE0 /* Setting.swift */, + ); + path = Models; + sourceTree = ""; + }; + FD17D7D027F5795300122BE0 /* Types */ = { + isa = PBXGroup; + children = ( + FD17D7D127F5797A00122BE0 /* SSKEndpoint.swift */, + FD17D7D327F6584600122BE0 /* SSKError.swift */, + FD17D7D727F658E200122BE0 /* SSKDestination.swift */, + ); + path = Types; + sourceTree = ""; + }; + FD17D7DF27F67BC400122BE0 /* Models */ = { + isa = PBXGroup; + children = ( + FD17D7E027F67BD400122BE0 /* SnodeReceivedMessage.swift */, + FD09796827F6BEA700936362 /* SwarmSnode.swift */, + ); + path = Models; + sourceTree = ""; + }; + FD17D7E827F6A1B800122BE0 /* LegacyDatabase */ = { + isa = PBXGroup; + children = ( + FD17D7E927F6A1C600122BE0 /* SUKLegacyModels.swift */, + ); + path = LegacyDatabase; + sourceTree = ""; + }; FD659ABE27A7648200F12C02 /* Message Requests */ = { isa = PBXGroup; children = ( @@ -3843,7 +3900,6 @@ C3A3A15F256E1BB4004D228D /* ProtoUtils.h in Headers */, C3A3A145256E1B49004D228D /* OWSMediaGalleryFinder.h in Headers */, B8856E33256F18D5001CE70E /* OWSStorage+Subclass.h in Headers */, - C32C5C0A256DC9B4003C73A2 /* OWSIdentityManager.h in Headers */, B8856E9D256F1C3D001CE70E /* OWSSounds.h in Headers */, C32C5E64256DDFD6003C73A2 /* OWSStorage.h in Headers */, ); @@ -4664,27 +4720,29 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FD09796927F6BEA700936362 /* SwarmSnode.swift in Sources */, C3C2A5E02553860B00C340D1 /* Threading.swift in Sources */, C3C2A5BF255385EE00C340D1 /* SnodeMessage.swift in Sources */, + FD17D7E127F67BD400122BE0 /* SnodeReceivedMessage.swift in Sources */, C3C2A5C0255385EE00C340D1 /* Snode.swift in Sources */, FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */, C3C2A5C7255385EE00C340D1 /* SnodeAPI.swift in Sources */, - C32C5CBF256DD282003C73A2 /* Storage+SnodeAPI.swift in Sources */, C3C2A5C6255385EE00C340D1 /* Notification+OnionRequestAPI.swift in Sources */, FD17D7AA27F41BF500122BE0 /* SnodeSet.swift in Sources */, FD17D7A427F40F8100122BE0 /* _002_YDBToGRDBMigration.swift in Sources */, - C32C5CBE256DD282003C73A2 /* Storage+OnionRequests.swift in Sources */, C3C2A5DC2553860B00C340D1 /* Promise+Threading.swift in Sources */, C3C2A5C4255385EE00C340D1 /* OnionRequestAPI+Encryption.swift in Sources */, + FD17D7D227F5797A00122BE0 /* SSKEndpoint.swift in Sources */, C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */, C3C2A5DB2553860B00C340D1 /* Promise+Hashing.swift in Sources */, C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */, FD17D7A727F41AF000122BE0 /* SSKLegacyModels.swift in Sources */, C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */, + FD17D7D827F658E200122BE0 /* SSKDestination.swift in Sources */, FD17D7B327F51E5B00122BE0 /* SSKSetting.swift in Sources */, FD17D7AE27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift in Sources */, C3C2A5C3255385EE00C340D1 /* OnionRequestAPI.swift in Sources */, - C3C2A5C1255385EE00C340D1 /* Storage.swift in Sources */, + FD17D7D427F6584600122BE0 /* SSKError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4694,7 +4752,6 @@ files = ( C3AABDDF2553ECF00042FF4C /* Array+Utilities.swift in Sources */, 7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */, - FD17D7B627F51E7300122BE0 /* SettingType.swift in Sources */, C3AABDDF2553ECF00042FF4C /* Array+Utilities.swift in Sources */, C32C5A47256DB8F0003C73A2 /* ECKeyPair+Hexadecimal.swift in Sources */, C3D9E41525676C320040E4F3 /* Storage.swift in Sources */, @@ -4703,11 +4760,13 @@ C3D9E39B256763C20040E4F3 /* AppContext.m in Sources */, C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */, FD17D7C127F5200100122BE0 /* TypedTableDefinition.swift in Sources */, + FD17D7EA27F6A1C600122BE0 /* SUKLegacyModels.swift in Sources */, C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */, C32C5A2D256DB849003C73A2 /* LKGroupUtilities.m in Sources */, C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */, B8856E09256F1676001CE70E /* UIDevice+featureSupport.swift in Sources */, B8856DEF256F161F001CE70E /* NSString+SSK.m in Sources */, + FD09796727F6B0B600936362 /* Sodium+Conversion.swift in Sources */, C32C5DC9256DD935003C73A2 /* ProxiedContentDownloader.swift in Sources */, C3D9E4C02567767F0040E4F3 /* DataSource.m in Sources */, C3D9E43125676D3D0040E4F3 /* Configuration.swift in Sources */, @@ -4717,6 +4776,7 @@ C32C5FA1256DFED5003C73A2 /* NSArray+Functional.m in Sources */, C3A7225E2558C38D0043A11F /* Promise+Retaining.swift in Sources */, FD17D7BD27F51F6900122BE0 /* GRDB+Notifications.swift in Sources */, + FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */, C3D9E4D12567777D0040E4F3 /* OWSMediaUtils.swift in Sources */, C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Description.swift in Sources */, C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */, @@ -4727,6 +4787,7 @@ FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */, C3D9E3C925676AF30040E4F3 /* TSYapDatabaseObject.m in Sources */, C352A3A62557B60D00338F3E /* TSRequest.m in Sources */, + FD09796B27F6C67500936362 /* Failable.swift in Sources */, FD705A92278D051200F16121 /* ReusableView.swift in Sources */, FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */, FD17D7C327F5204C00122BE0 /* Database+Utilities.swift in Sources */, @@ -4753,8 +4814,10 @@ C32C5E0C256DDAFA003C73A2 /* NSRegularExpression+SSK.swift in Sources */, C3BBE0A92554D4DE0050F1E3 /* HTTP.swift in Sources */, B8856D23256F116B001CE70E /* Weak.swift in Sources */, + FD17D7CD27F546FF00122BE0 /* Setting.swift in Sources */, C32C5A48256DB8F0003C73A2 /* BuildConfiguration.swift in Sources */, FD17D7BF27F51F8200122BE0 /* ColumnExpressible.swift in Sources */, + FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */, B87EF18126377A1D00124B3C /* Features.swift in Sources */, C300A60D2554B31900555489 /* Logging.swift in Sources */, B8FF8EA625C11FEF004D1F22 /* IPv4.swift in Sources */, @@ -4762,6 +4825,7 @@ FD705A8E278CE29800F16121 /* String+Localization.swift in Sources */, C3A7219A2558C1660043A11F /* AnyPromise+Conversion.swift in Sources */, C300A6322554B6D100555489 /* NSDate+Timestamp.mm in Sources */, + FD17D7E727F6A16700122BE0 /* _002_YDBToGRDBMigration.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4848,9 +4912,7 @@ C3D9E3BE25676AD70040E4F3 /* TSAttachmentPointer.m in Sources */, C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */, C32C5AAB256DBE8F003C73A2 /* TSIncomingMessage+Conversion.swift in Sources */, - B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */, C32C5A88256DBCF9003C73A2 /* MessageReceiver+Handling.swift in Sources */, - C32C5C1B256DC9E0003C73A2 /* General.swift in Sources */, C32C5A02256DB658003C73A2 /* MessageSender+Convenience.swift in Sources */, B8566C6C256F60F50045A0B9 /* OWSUserProfile.m in Sources */, B8D0A25925E367AC00C1835E /* Notification+MessageReceiver.swift in Sources */, @@ -4905,7 +4967,6 @@ C32C5EBA256DE130003C73A2 /* OWSQuotedReplyModel.m in Sources */, C32C5B62256DC333003C73A2 /* OWSDisappearingConfigurationUpdateInfoMessage.m in Sources */, C352A2F525574B4700338F3E /* Job.swift in Sources */, - C32C5C01256DC9A0003C73A2 /* OWSIdentityManager.m in Sources */, C32C59C4256DB41F003C73A2 /* TSContactThread.m in Sources */, C32C5AB0256DBE8F003C73A2 /* TSOutgoingMessage.m in Sources */, B82A0C3826B9098200C1BCE3 /* MessageInvalidator.swift in Sources */, @@ -4975,7 +5036,6 @@ FD659AC027A7649600F12C02 /* MessageRequestsViewController.swift in Sources */, B8F5F72325F1B4CA003BF8D4 /* DownloadAttachmentModal.swift in Sources */, C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */, - C31FFE57254A5FFE00F19441 /* KeyPairUtilities.swift in Sources */, B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */, 45C0DC1E1E69011F00E04C47 /* UIStoryboard+OWS.swift in Sources */, FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */, @@ -5248,7 +5308,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5326,7 +5386,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5385,7 +5445,7 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5464,7 +5524,7 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5521,7 +5581,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SessionUIKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5600,7 +5660,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = SessionUIKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5668,7 +5728,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SignalUtilitiesKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5755,7 +5815,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = SignalUtilitiesKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5815,7 +5875,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5894,7 +5954,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5954,7 +6014,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SessionUtilitiesKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -6042,7 +6102,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = SessionUtilitiesKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -6111,7 +6171,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; INFOPLIST_FILE = SessionMessagingKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -6190,7 +6250,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = SessionMessagingKit/Meta/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -6407,7 +6467,7 @@ "\"$(SRCROOT)/Libraries\"/**", ); INFOPLIST_FILE = "Session/Meta/Session-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -6478,7 +6538,7 @@ "\"$(SRCROOT)/Libraries\"/**", ); INFOPLIST_FILE = "Session/Meta/Session-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/Session/Conversations/ConversationVC.swift b/Session/Conversations/ConversationVC.swift index 886352a59..2482b4242 100644 --- a/Session/Conversations/ConversationVC.swift +++ b/Session/Conversations/ConversationVC.swift @@ -1,6 +1,9 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import UIKit import SessionUIKit import SessionMessagingKit -import UIKit +import SessionUtilitiesKit // TODO: // • Slight paging glitch when scrolling up and loading more content @@ -69,13 +72,12 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat } lazy var mnemonic: String = { - let identityManager = OWSIdentityManager.shared() - let databaseConnection = identityManager.value(forKey: "dbConnection") as! YapDatabaseConnection - var hexEncodedSeed: String! = databaseConnection.object(forKey: "LKLokiSeed", inCollection: OWSPrimaryStorageIdentityKeyStoreCollection) as! String? - if hexEncodedSeed == nil { - hexEncodedSeed = identityManager.identityKeyPair()!.hexEncodedPrivateKey // Legacy account + if let hexEncodedSeed: String = Identity.fetchHexEncodedSeed() { + return Mnemonic.encode(hexEncodedString: hexEncodedSeed) } - return Mnemonic.encode(hexEncodedString: hexEncodedSeed) + + // Legacy account + return Mnemonic.encode(hexEncodedString: Identity.fetchUserKeyPair()!.hexEncodedPrivateKey) }() lazy var viewModel = ConversationViewModel(thread: thread, focusMessageIdOnOpen: nil, delegate: self) diff --git a/Session/Conversations/ConversationViewItem.m b/Session/Conversations/ConversationViewItem.m index 4ebdd168a..5be802798 100644 --- a/Session/Conversations/ConversationViewItem.m +++ b/Session/Conversations/ConversationViewItem.m @@ -1005,7 +1005,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) // If it's an incoming message the user must have moderator status if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) { - NSString *userPublicKey = [LKStorage.shared getUserPublicKey]; + NSString *userPublicKey = [SNGeneralUtilities getUserPublicKey]; if (![SNOpenGroupAPIV2 isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; } } @@ -1058,7 +1058,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) // If it's an incoming message the user must have moderator status if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) { - NSString *userPublicKey = [LKStorage.shared getUserPublicKey]; + NSString *userPublicKey = [SNGeneralUtilities getUserPublicKey]; if (openGroupV2 != nil) { if (![SNOpenGroupAPIV2 isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; } } diff --git a/Session/Conversations/Settings/OWSConversationSettingsViewController.m b/Session/Conversations/Settings/OWSConversationSettingsViewController.m index 60244cc88..7d5112ab3 100644 --- a/Session/Conversations/Settings/OWSConversationSettingsViewController.m +++ b/Session/Conversations/Settings/OWSConversationSettingsViewController.m @@ -114,10 +114,6 @@ CGFloat kIconViewLength = 24; - (void)observeNotifications { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(identityStateDidChange:) - name:kNSNotificationName_IdentityStateDidChange - object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(otherUsersProfileDidChange:) name:kNSNotificationName_OtherUsersProfileDidChange @@ -1069,13 +1065,6 @@ CGFloat kIconViewLength = 24; #pragma mark - Notifications -- (void)identityStateDidChange:(NSNotification *)notification -{ - OWSAssertIsOnMainThread(); - - [self updateTableContents]; -} - - (void)otherUsersProfileDidChange:(NSNotification *)notification { OWSAssertIsOnMainThread(); diff --git a/Session/Home/HomeVC.swift b/Session/Home/HomeVC.swift index 1cba65a15..629e83f37 100644 --- a/Session/Home/HomeVC.swift +++ b/Session/Home/HomeVC.swift @@ -1,3 +1,8 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import UIKit +import SessionMessagingKit +import SessionUtilitiesKit // See https://github.com/yapstudios/YapDatabase/wiki/LongLivedReadTransactions and // https://github.com/yapstudios/YapDatabase/wiki/YapDatabaseModifiedNotification for @@ -162,7 +167,7 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, NewConv self.threads.update(with: transaction) // Perform the initial update } // Start polling if needed (i.e. if the user just created or restored their Session ID) - if OWSIdentityManager.shared().identityKeyPair() != nil { + if Identity.fetchUserKeyPair() != nil { let appDelegate = UIApplication.shared.delegate as! AppDelegate appDelegate.startPollerIfNeeded() appDelegate.startClosedGroupPoller() diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index 67357b6eb..965c4c299 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -748,7 +748,7 @@ static NSTimeInterval launchStartedAt; [[LKPushNotificationAPI unregisterToken:deviceToken] retainUntilComplete]; } [ThreadUtil deleteAllContent]; - [SSKEnvironment.shared.identityManager clearIdentityKey]; + [SUKIdentity clearUserKeyPair]; [SNSnodeAPI clearSnodePool]; [self stopPoller]; [self stopClosedGroupPoller]; diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index ce3c93df8..b0719086a 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -1,5 +1,9 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation import PromiseKit import SessionMessagingKit +import SessionUtilitiesKit extension AppDelegate { @@ -22,7 +26,8 @@ extension AppDelegate { } @objc func startClosedGroupPoller() { - guard OWSIdentityManager.shared().identityKeyPair() != nil else { return } + guard Identity.fetchUserKeyPair() != nil else { return } + ClosedGroupPoller.shared.start() } diff --git a/Session/Meta/MainAppContext.m b/Session/Meta/MainAppContext.m index 2fad58d41..b5e82f87d 100644 --- a/Session/Meta/MainAppContext.m +++ b/Session/Meta/MainAppContext.m @@ -8,7 +8,6 @@ #import #import #import -#import NS_ASSUME_NONNULL_BEGIN diff --git a/Session/Meta/Signal-Bridging-Header.h b/Session/Meta/Signal-Bridging-Header.h index 1b5f636b7..a3b196317 100644 --- a/Session/Meta/Signal-Bridging-Header.h +++ b/Session/Meta/Signal-Bridging-Header.h @@ -63,7 +63,6 @@ #import #import #import -#import #import #import #import diff --git a/Session/Notifications/AppNotifications.swift b/Session/Notifications/AppNotifications.swift index 1c68a4d06..df3b6a0ef 100644 --- a/Session/Notifications/AppNotifications.swift +++ b/Session/Notifications/AppNotifications.swift @@ -121,10 +121,6 @@ public class NotificationPresenter: NSObject, NotificationsProtocol { // MARK: - Dependencies - var identityManager: OWSIdentityManager { - return OWSIdentityManager.shared() - } - var preferences: OWSPreferences { return Environment.shared.preferences } diff --git a/Session/Onboarding/LandingVC.swift b/Session/Onboarding/LandingVC.swift index ab79a8fbc..f3bb8c7a2 100644 --- a/Session/Onboarding/LandingVC.swift +++ b/Session/Onboarding/LandingVC.swift @@ -1,5 +1,9 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -final class LandingVC : BaseVC { +import UIKit +import SessionUIKit + +final class LandingVC: BaseVC { // MARK: Components private lazy var fakeChatView: FakeChatView = { diff --git a/Session/Onboarding/LinkDeviceVC.swift b/Session/Onboarding/LinkDeviceVC.swift index e2defa126..9c5edaeae 100644 --- a/Session/Onboarding/LinkDeviceVC.swift +++ b/Session/Onboarding/LinkDeviceVC.swift @@ -1,4 +1,8 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import UIKit import PromiseKit +import SessionUtilitiesKit final class LinkDeviceVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate { private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil) @@ -130,7 +134,7 @@ final class LinkDeviceVC : BaseVC, UIPageViewControllerDataSource, UIPageViewCon presentAlert(alert) return } - let (ed25519KeyPair, x25519KeyPair) = KeyPairUtilities.generate(from: seed) + let (ed25519KeyPair, x25519KeyPair) = try! Identity.generate(from: seed) Onboarding.Flow.link.preregister(with: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) TSAccountManager.sharedInstance().didRegister() NotificationCenter.default.addObserver(self, selector: #selector(handleInitialConfigurationMessageReceived), name: .initialConfigurationMessageReceived, object: nil) @@ -140,7 +144,8 @@ final class LinkDeviceVC : BaseVC, UIPageViewControllerDataSource, UIPageViewCon } @objc private func handleInitialConfigurationMessageReceived(_ notification: Notification) { - TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey + TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = getUserHexEncodedPublicKey() + DispatchQueue.main.async { self.navigationController!.dismiss(animated: true) { let pnModeVC = PNModeVC() diff --git a/Session/Onboarding/Onboarding.swift b/Session/Onboarding/Onboarding.swift index dc89f53b5..00a373e02 100644 --- a/Session/Onboarding/Onboarding.swift +++ b/Session/Onboarding/Onboarding.swift @@ -1,4 +1,8 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation import Sodium +import SessionUtilitiesKit enum Onboarding { @@ -7,7 +11,7 @@ enum Onboarding { func preregister(with seed: Data, ed25519KeyPair: Sign.KeyPair, x25519KeyPair: ECKeyPair) { let userDefaults = UserDefaults.standard - KeyPairUtilities.store(seed: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) + Identity.store(seed: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) let x25519PublicKey = x25519KeyPair.hexEncodedPublicKey TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = x25519PublicKey Storage.writeSync { transaction in @@ -16,25 +20,31 @@ enum Onboarding { user.didApproveMe = true Storage.shared.setContact(user, using: transaction) } + switch self { - case .register: - userDefaults[.hasViewedSeed] = false - // Set hasSyncedInitialConfiguration to true so that when we hit the home screen a configuration sync - // is triggered (yes, the logic is a bit weird). This is needed so that if the user registers and - // immediately links a device, there'll be a configuration in their swarm. - userDefaults[.hasSyncedInitialConfiguration] = true - case .recover, .link: - userDefaults[.hasViewedSeed] = true // No need to show it again if the user is restoring or linking - userDefaults[.hasSyncedInitialConfiguration] = false + case .register: + userDefaults[.hasViewedSeed] = false + // Set hasSyncedInitialConfiguration to true so that when we hit the + // home screen a configuration sync is triggered (yes, the logic is a + // bit weird). This is needed so that if the user registers and + // immediately links a device, there'll be a configuration in their swarm. + userDefaults[.hasSyncedInitialConfiguration] = true + + case .recover, .link: + // No need to show it again if the user is restoring or linking + userDefaults[.hasViewedSeed] = true + userDefaults[.hasSyncedInitialConfiguration] = false } + switch self { - case .register, .recover: - // Set both lastDisplayNameUpdate and lastProfilePictureUpdate to the current date, so that - // we don't overwrite what the user set in the display name step with whatever we find in - // their swarm. - userDefaults[.lastDisplayNameUpdate] = Date() - userDefaults[.lastProfilePictureUpdate] = Date() - case .link: break + case .register, .recover: + // Set both lastDisplayNameUpdate and lastProfilePictureUpdate to the + // current date, so that we don't overwrite what the user set in the + // display name step with whatever we find in their swarm. + userDefaults[.lastDisplayNameUpdate] = Date() + userDefaults[.lastProfilePictureUpdate] = Date() + + case .link: break } } } diff --git a/Session/Onboarding/RegisterVC.swift b/Session/Onboarding/RegisterVC.swift index 3aed52229..0bb1f5945 100644 --- a/Session/Onboarding/RegisterVC.swift +++ b/Session/Onboarding/RegisterVC.swift @@ -136,7 +136,7 @@ final class RegisterVC : BaseVC { } private func updateKeyPair() { - (ed25519KeyPair, x25519KeyPair) = KeyPairUtilities.generate(from: seed) + (ed25519KeyPair, x25519KeyPair) = try! Identity.generate(from: seed) } private func updatePublicKeyLabel() { diff --git a/Session/Onboarding/RestoreVC.swift b/Session/Onboarding/RestoreVC.swift index ad4dbc4ff..762b1e5c1 100644 --- a/Session/Onboarding/RestoreVC.swift +++ b/Session/Onboarding/RestoreVC.swift @@ -1,5 +1,9 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -final class RestoreVC : BaseVC { +import UIKit +import SessionUtilitiesKit + +final class RestoreVC: BaseVC { private var spacer1HeightConstraint: NSLayoutConstraint! private var spacer2HeightConstraint: NSLayoutConstraint! private var spacer3HeightConstraint: NSLayoutConstraint! @@ -164,7 +168,7 @@ final class RestoreVC : BaseVC { do { let hexEncodedSeed = try Mnemonic.decode(mnemonic: mnemonic) let seed = Data(hex: hexEncodedSeed) - let (ed25519KeyPair, x25519KeyPair) = KeyPairUtilities.generate(from: seed) + let (ed25519KeyPair, x25519KeyPair) = try! Identity.generate(from: seed) Onboarding.Flow.recover.preregister(with: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) mnemonicTextView.resignFirstResponder() Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in diff --git a/Session/Onboarding/SeedVC.swift b/Session/Onboarding/SeedVC.swift index a80fed60d..cc993691e 100644 --- a/Session/Onboarding/SeedVC.swift +++ b/Session/Onboarding/SeedVC.swift @@ -1,14 +1,16 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -final class SeedVC : BaseVC { - +import UIKit +import SessionUtilitiesKit + +final class SeedVC: BaseVC { private let mnemonic: String = { - let identityManager = OWSIdentityManager.shared() - let databaseConnection = identityManager.value(forKey: "dbConnection") as! YapDatabaseConnection - var hexEncodedSeed: String! = databaseConnection.object(forKey: "LKLokiSeed", inCollection: OWSPrimaryStorageIdentityKeyStoreCollection) as! String? - if hexEncodedSeed == nil { - hexEncodedSeed = identityManager.identityKeyPair()!.hexEncodedPrivateKey // Legacy account + if let hexEncodedSeed: String = Identity.fetchHexEncodedSeed() { + return Mnemonic.encode(hexEncodedString: hexEncodedSeed) } - return Mnemonic.encode(hexEncodedString: hexEncodedSeed) + + // Legacy account + return Mnemonic.encode(hexEncodedString: Identity.fetchUserKeyPair()!.hexEncodedPrivateKey) }() private lazy var redactedMnemonic: String = { diff --git a/Session/Path/PathStatusView.swift b/Session/Path/PathStatusView.swift index 18ba2dafb..f3dec6a7e 100644 --- a/Session/Path/PathStatusView.swift +++ b/Session/Path/PathStatusView.swift @@ -19,10 +19,7 @@ final class PathStatusView : UIView { private func setUpViewHierarchy() { layer.cornerRadius = PathStatusView.size / 2 layer.masksToBounds = false - if OnionRequestAPI.paths.isEmpty { - OnionRequestAPI.paths = Storage.shared.getOnionRequestPaths() - } - let color = (!OnionRequestAPI.paths.isEmpty) ? Colors.accent : Colors.pathsBuilding + let color = (!OnionRequestAPI.paths.isEmpty ? Colors.accent : Colors.pathsBuilding) setColor(to: color, isAnimated: false) } diff --git a/Session/Path/PathVC.swift b/Session/Path/PathVC.swift index 649501e6f..1e84d1b34 100644 --- a/Session/Path/PathVC.swift +++ b/Session/Path/PathVC.swift @@ -106,26 +106,49 @@ final class PathVC : BaseVC { private func update() { pathStackView.arrangedSubviews.forEach { $0.removeFromSuperview() } - if !OnionRequestAPI.paths.isEmpty { - let pathToDisplay = OnionRequestAPI.paths.first! - let dotAnimationRepeatInterval = Double(pathToDisplay.count) + 2 - let snodeRows: [UIStackView] = pathToDisplay.enumerated().map { index, snode in - let isGuardSnode = (snode == pathToDisplay.first!) - return getPathRow(snode: snode, location: .middle, dotAnimationStartDelay: Double(index) + 2, dotAnimationRepeatInterval: dotAnimationRepeatInterval, isGuardSnode: isGuardSnode) - } - let youRow = getPathRow(title: NSLocalizedString("vc_path_device_row_title", comment: ""), subtitle: nil, location: .top, dotAnimationStartDelay: 1, dotAnimationRepeatInterval: dotAnimationRepeatInterval) - let destinationRow = getPathRow(title: NSLocalizedString("vc_path_destination_row_title", comment: ""), subtitle: nil, location: .bottom, dotAnimationStartDelay: Double(pathToDisplay.count) + 2, dotAnimationRepeatInterval: dotAnimationRepeatInterval) - let rows = [ youRow ] + snodeRows + [ destinationRow ] - rows.forEach { pathStackView.addArrangedSubview($0) } - spinner.stopAnimating() - UIView.animate(withDuration: 0.25) { - self.spinner.alpha = 0 - } - } else { + + guard let pathToDisplay: [Snode] = OnionRequestAPI.paths.first else { spinner.startAnimating() + UIView.animate(withDuration: 0.25) { self.spinner.alpha = 1 } + return + } + + let dotAnimationRepeatInterval = Double(pathToDisplay.count) + 2 + let snodeRows: [UIStackView] = pathToDisplay.enumerated().map { index, snode in + let isGuardSnode = (snode == pathToDisplay.first) + + return getPathRow( + snode: snode, + location: .middle, + dotAnimationStartDelay: Double(index) + 2, + dotAnimationRepeatInterval: dotAnimationRepeatInterval, + isGuardSnode: isGuardSnode + ) + } + + let youRow = getPathRow( + title: NSLocalizedString("vc_path_device_row_title", comment: ""), + subtitle: nil, + location: .top, + dotAnimationStartDelay: 1, + dotAnimationRepeatInterval: dotAnimationRepeatInterval + ) + let destinationRow = getPathRow( + title: NSLocalizedString("vc_path_destination_row_title", comment: ""), + subtitle: nil, + location: .bottom, + dotAnimationStartDelay: Double(pathToDisplay.count) + 2, + dotAnimationRepeatInterval: dotAnimationRepeatInterval + ) + let rows = [ youRow ] + snodeRows + [ destinationRow ] + rows.forEach { pathStackView.addArrangedSubview($0) } + spinner.stopAnimating() + + UIView.animate(withDuration: 0.25) { + self.spinner.alpha = 0 } } @@ -156,7 +179,7 @@ final class PathVC : BaseVC { return stackView } - private func getPathRow(snode: Legacy.Snode, location: LineView.Location, dotAnimationStartDelay: Double, dotAnimationRepeatInterval: Double, isGuardSnode: Bool) -> UIStackView { + private func getPathRow(snode: Snode, location: LineView.Location, dotAnimationStartDelay: Double, dotAnimationRepeatInterval: Double, isGuardSnode: Bool) -> UIStackView { let country = IP2Country.isInitialized ? (IP2Country.shared.countryNamesCache[snode.ip] ?? "Resolving...") : "Resolving..." let title = isGuardSnode ? NSLocalizedString("vc_path_guard_node_row_title", comment: "") : NSLocalizedString("vc_path_service_node_row_title", comment: "") return getPathRow(title: title, subtitle: country, location: location, dotAnimationStartDelay: dotAnimationStartDelay, dotAnimationRepeatInterval: dotAnimationRepeatInterval) diff --git a/Session/Settings/SeedModal.swift b/Session/Settings/SeedModal.swift index 11528450b..28e448c25 100644 --- a/Session/Settings/SeedModal.swift +++ b/Session/Settings/SeedModal.swift @@ -1,15 +1,18 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import UIKit +import SessionUtilitiesKit @objc(LKSeedModal) -final class SeedModal : Modal { - +final class SeedModal: Modal { + private let mnemonic: String = { - let identityManager = OWSIdentityManager.shared() - let databaseConnection = identityManager.value(forKey: "dbConnection") as! YapDatabaseConnection - var hexEncodedSeed: String! = databaseConnection.object(forKey: "LKLokiSeed", inCollection: OWSPrimaryStorageIdentityKeyStoreCollection) as! String? - if hexEncodedSeed == nil { - hexEncodedSeed = identityManager.identityKeyPair()!.hexEncodedPrivateKey // Legacy account + if let hexEncodedSeed: String = Identity.fetchHexEncodedSeed() { + return Mnemonic.encode(hexEncodedString: hexEncodedSeed) } - return Mnemonic.encode(hexEncodedString: hexEncodedSeed) + + // Legacy account + return Mnemonic.encode(hexEncodedString: Identity.fetchUserKeyPair()!.hexEncodedPrivateKey) }() // MARK: Lifecycle diff --git a/Session/Utilities/BackgroundPoller.swift b/Session/Utilities/BackgroundPoller.swift index ff31d0965..ff572ac87 100644 --- a/Session/Utilities/BackgroundPoller.swift +++ b/Session/Utilities/BackgroundPoller.swift @@ -42,18 +42,29 @@ public final class BackgroundPoller : NSObject { guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic } return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) { return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise in - let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) - let promises = messages.compactMap { json -> Promise? in - // Use a best attempt approach here; we don't want to fail the entire process if one of the - // messages failed to parse. - guard let envelope = SNProtoEnvelope.from(json), - let data = try? envelope.serializedData() else { return nil } - let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true) + let messages: [SnodeReceivedMessage] = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey) + let promises = messages.compactMap { message -> Promise? in + // Use a best attempt approach here; we don't want to fail the entire process + // if one of the messages failed to parse + guard + let envelope = SNProtoEnvelope.from(message), + let data = try? envelope.serializedData() + else { return nil } + + let job = MessageReceiveJob( + data: data, + serverHash: message.info.hash, + isBackgroundPoll: true + ) return job.execute() } - // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value - SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: publicKey, from: lastRawMessage) + // Now that the MessageReceiveJob's have been created we can persist the received messages + if !messages.isEmpty { + GRDBStorage.shared.write { db in + messages.forEach { try? $0.info.save(db) } + } + } return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects } diff --git a/Session/Utilities/IP2Country.swift b/Session/Utilities/IP2Country.swift index 3f34f7040..052331a3d 100644 --- a/Session/Utilities/IP2Country.swift +++ b/Session/Utilities/IP2Country.swift @@ -1,3 +1,6 @@ +import Foundation +import GRDB +import SessionSnodeKit final class IP2Country { var countryNamesCache: [String:String] = [:] @@ -53,12 +56,8 @@ final class IP2Country { } func populateCacheIfNeeded() -> Bool { - if OnionRequestAPI.paths.isEmpty { - OnionRequestAPI.paths = Storage.shared.getOnionRequestPaths() - } - let paths = OnionRequestAPI.paths - guard !paths.isEmpty else { return false } - let pathToDisplay = paths.first! + guard let pathToDisplay: [Snode] = OnionRequestAPI.paths.first else { return false } + pathToDisplay.forEach { snode in let _ = self.cacheCountry(for: snode.ip) // Preload if needed } diff --git a/Session/Utilities/KeyPairUtilities.swift b/Session/Utilities/KeyPairUtilities.swift deleted file mode 100644 index 8a0984808..000000000 --- a/Session/Utilities/KeyPairUtilities.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Sodium - -enum KeyPairUtilities { - - static func generate(from seed: Data) -> (ed25519KeyPair: Sign.KeyPair, x25519KeyPair: ECKeyPair) { - assert(seed.count == 16) - let padding = Data(repeating: 0, count: 16) - let ed25519KeyPair = Sodium().sign.keyPair(seed: (seed + padding).bytes)! - let x25519PublicKey = Sodium().sign.toX25519(ed25519PublicKey: ed25519KeyPair.publicKey)! - let x25519SecretKey = Sodium().sign.toX25519(ed25519SecretKey: ed25519KeyPair.secretKey)! - let x25519KeyPair = try! ECKeyPair(publicKeyData: Data(x25519PublicKey), privateKeyData: Data(x25519SecretKey)) - return (ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) - } - - static func store(seed: Data, ed25519KeyPair: Sign.KeyPair, x25519KeyPair: ECKeyPair) { - let dbConnection = OWSIdentityManager.shared().dbConnection - let collection = OWSPrimaryStorageIdentityKeyStoreCollection - dbConnection.setObject(seed.toHexString(), forKey: LKSeedKey, inCollection: collection) - dbConnection.setObject(ed25519KeyPair.secretKey.toHexString(), forKey: LKED25519SecretKey, inCollection: collection) - dbConnection.setObject(ed25519KeyPair.publicKey.toHexString(), forKey: LKED25519PublicKey, inCollection: collection) - dbConnection.setObject(x25519KeyPair, forKey: OWSPrimaryStorageIdentityKeyStoreIdentityKey, inCollection: collection) - } - - static func hasV2KeyPair() -> Bool { - let dbConnection = OWSIdentityManager.shared().dbConnection - return (dbConnection.object(forKey: LKED25519SecretKey, inCollection: OWSPrimaryStorageIdentityKeyStoreCollection) != nil) - } -} diff --git a/Session/Utilities/MockDataGenerator.swift b/Session/Utilities/MockDataGenerator.swift index 5a559b6df..8313d807a 100644 --- a/Session/Utilities/MockDataGenerator.swift +++ b/Session/Utilities/MockDataGenerator.swift @@ -106,7 +106,7 @@ enum MockDataGenerator { (0.. String? { - return OWSIdentityManager.shared().identityKeyPair()?.hexEncodedPublicKey - } - - public func getUserKeyPair() -> ECKeyPair? { - return OWSIdentityManager.shared().identityKeyPair() - } - - public func getUserED25519KeyPair() -> Box.KeyPair? { - let dbConnection = OWSIdentityManager.shared().dbConnection - let collection = OWSPrimaryStorageIdentityKeyStoreCollection - guard let hexEncodedPublicKey = dbConnection.object(forKey: LKED25519PublicKey, inCollection: collection) as? String, - let hexEncodedSecretKey = dbConnection.object(forKey: LKED25519SecretKey, inCollection: collection) as? String else { return nil } - let publicKey = Box.KeyPair.PublicKey(hex: hexEncodedPublicKey) - let secretKey = Box.KeyPair.SecretKey(hex: hexEncodedSecretKey) - return Box.KeyPair(publicKey: publicKey, secretKey: secretKey) - } - @objc public func getUser() -> Contact? { return getUser(using: nil) } diff --git a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift index ce25b18f0..f529e5f3d 100644 --- a/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/ClosedGroupControlMessage.swift @@ -1,3 +1,7 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Curve25519Kit import SessionUtilitiesKit public final class ClosedGroupControlMessage : ControlMessage { diff --git a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage.swift b/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage.swift index 00eea0aa0..af4b26ffa 100644 --- a/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage.swift +++ b/SessionMessagingKit/Messages/Control Messages/ConfigurationMessage.swift @@ -1,3 +1,7 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Curve25519Kit import SessionUtilitiesKit @objc(SNConfigurationMessage) diff --git a/SessionMessagingKit/Meta/SessionMessagingKit.h b/SessionMessagingKit/Meta/SessionMessagingKit.h index da0a86ab6..59bf8c918 100644 --- a/SessionMessagingKit/Meta/SessionMessagingKit.h +++ b/SessionMessagingKit/Meta/SessionMessagingKit.h @@ -14,7 +14,6 @@ FOUNDATION_EXPORT const unsigned char SessionMessagingKitVersionString[]; #import #import #import -#import #import #import #import diff --git a/SessionMessagingKit/Open Groups/OpenGroupAPIV2.swift b/SessionMessagingKit/Open Groups/OpenGroupAPIV2.swift index d8206b604..b4bf3d196 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupAPIV2.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupAPIV2.swift @@ -1,10 +1,15 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation import PromiseKit +import Curve25519Kit import SessionSnodeKit +import SessionUtilitiesKit @objc(SNOpenGroupAPIV2) public final class OpenGroupAPIV2 : NSObject { - private static var authTokenPromises: [String:Promise] = [:] - private static var hasPerformedInitialPoll: [String:Bool] = [:] + private static var authTokenPromises: [String: Promise] = [:] + private static var hasPerformedInitialPoll: [String: Bool] = [:] private static var hasUpdatedLastOpenDate = false public static let workQueue = DispatchQueue(label: "OpenGroupAPIV2.workQueue", qos: .userInitiated) // It's important that this is a serial queue public static var moderators: [String:[String:Set]] = [:] // Server URL to room ID to set of moderator IDs @@ -237,7 +242,7 @@ public final class OpenGroupAPIV2 : NSObject { public static func requestNewAuthToken(for room: String, on server: String) -> Promise { SNLog("Requesting auth token for server: \(server).") - guard let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else { return Promise(error: Error.generic) } + guard let userKeyPair = Identity.fetchUserKeyPair() else { return Promise(error: Error.generic) } let queryParameters = [ "public_key" : getUserHexEncodedPublicKey() ] let request = Request(verb: .get, room: room, server: server, endpoint: "auth_token_challenge", queryParameters: queryParameters, isAuthRequired: false) return send(request).map(on: OpenGroupAPIV2.workQueue) { json in diff --git a/SessionMessagingKit/Open Groups/OpenGroupMessageV2.swift b/SessionMessagingKit/Open Groups/OpenGroupMessageV2.swift index bc82ad7ce..3a3cb7d3a 100644 --- a/SessionMessagingKit/Open Groups/OpenGroupMessageV2.swift +++ b/SessionMessagingKit/Open Groups/OpenGroupMessageV2.swift @@ -1,3 +1,8 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Curve25519Kit +import SessionUtilitiesKit public struct OpenGroupMessageV2 { public let serverID: Int64? @@ -10,8 +15,11 @@ public struct OpenGroupMessageV2 { public let base64EncodedSignature: String? public func sign() -> OpenGroupMessageV2? { - let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair()! - let data = Data(base64Encoded: base64EncodedData)! + guard + let userKeyPair = Identity.fetchUserKeyPair(), + let data: Data = Data(base64Encoded: base64EncodedData) + else { return nil } + guard let signature = try? Ed25519.sign(data, with: userKeyPair) else { SNLog("Failed to sign open group message.") return nil diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift index 20846e9d5..0e84e01e2 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Decryption.swift @@ -1,6 +1,10 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation import CryptoSwift -import SessionUtilitiesKit import Sodium +import Curve25519Kit +import SessionUtilitiesKit extension MessageReceiver { diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift index 3bbec95a7..719400d6e 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver+Handling.swift @@ -1,3 +1,7 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Curve25519Kit import SignalCoreKit import SessionSnodeKit @@ -556,7 +560,7 @@ extension MessageReceiver { let groupPublicKey = explicitGroupPublicKey?.toHexString() ?? message.groupPublicKey else { return } let transaction = transaction as! YapDatabaseReadWriteTransaction let userPublicKey = getUserHexEncodedPublicKey() - guard let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else { + guard let userKeyPair = Identity.fetchUserKeyPair() else { return SNLog("Couldn't find user X25519 key pair.") } let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 654b644b9..ef96124f0 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -1,7 +1,10 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation import SessionUtilitiesKit public enum MessageReceiver { - private static var lastEncryptionKeyPairRequest: [String:Date] = [:] + private static var lastEncryptionKeyPairRequest: [String: Date] = [:] public enum Error : LocalizedError { case duplicateMessage @@ -49,7 +52,7 @@ public enum MessageReceiver { } 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 = getUserHexEncodedPublicKey() let isOpenGroupMessage = (openGroupMessageServerID != nil) // Parse the envelope let envelope = try SNProtoEnvelope.parseData(data) @@ -64,7 +67,7 @@ public enum MessageReceiver { } else { switch envelope.type { case .sessionMessage: - guard let userX25519KeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else { throw Error.noUserX25519KeyPair } + guard let userX25519KeyPair = Identity.fetchUserKeyPair() else { throw Error.noUserX25519KeyPair } (plaintext, sender) = try decryptWithSessionProtocol(ciphertext: ciphertext, using: userX25519KeyPair) case .closedGroupMessage: guard let hexEncodedGroupPublicKey = envelope.source, SNMessagingKitConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey) else { throw Error.invalidGroupPublicKey } diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift index 8b75e2f44..7f335b4ef 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+ClosedGroups.swift @@ -1,7 +1,11 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Curve25519Kit import PromiseKit extension MessageSender { - public static var distributingClosedGroupEncryptionKeyPairs: [String:[ECKeyPair]] = [:] + public static var distributingClosedGroupEncryptionKeyPairs: [String: [ECKeyPair]] = [:] public static func createClosedGroup(name: String, members: Set, transaction: YapDatabaseReadWriteTransaction) -> Promise { // Prepare diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift b/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift index 6fee9e1c9..5c5d70821 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender+Encryption.swift @@ -1,10 +1,13 @@ -import SessionUtilitiesKit +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation import Sodium +import SessionUtilitiesKit extension MessageSender { internal static func encryptWithSessionProtocol(_ plaintext: Data, for recipientHexEncodedX25519PublicKey: String) throws -> Data { - guard let userED25519KeyPair = SNMessagingKitConfiguration.shared.storage.getUserED25519KeyPair() else { throw Error.noUserED25519KeyPair } + guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { throw Error.noUserED25519KeyPair } let recipientX25519PublicKey = Data(hex: recipientHexEncodedX25519PublicKey.removing05PrefixIfNeeded()) let sodium = Sodium() diff --git a/SessionMessagingKit/Sending & Receiving/MessageSender.swift b/SessionMessagingKit/Sending & Receiving/MessageSender.swift index c02ef9088..f111b632f 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageSender.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageSender.swift @@ -108,7 +108,7 @@ public final class MessageSender : NSObject { let (promise, seal) = Promise.pending() let storage = SNMessagingKitConfiguration.shared.storage let transaction = transaction as! YapDatabaseReadWriteTransaction - let userPublicKey = storage.getUserPublicKey() + let userPublicKey = getUserHexEncodedPublicKey() var isMainAppAndActive = false if let sharedUserDefaults = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") { isMainAppAndActive = sharedUserDefaults.bool(forKey: "isMainAppActive") @@ -267,7 +267,7 @@ public final class MessageSender : NSObject { if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set message.sentTimestamp = NSDate.millisecondTimestamp() } - message.sender = storage.getUserPublicKey() + message.sender = getUserHexEncodedPublicKey() switch destination { case .contact(_): preconditionFailure() case .closedGroup(_): preconditionFailure() diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift index 24ecdb2f8..1ce54f4d8 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/ClosedGroupPoller.swift @@ -100,26 +100,29 @@ public final class ClosedGroupPoller : NSObject { private func poll(_ groupPublicKey: String) -> Promise { guard isPolling(for: groupPublicKey) else { return Promise.value(()) } - let promise = SnodeAPI.getSwarm(for: groupPublicKey).then2 { [weak self] swarm -> Promise<(SessionSnodeKit.Legacy.Snode, [JSON], JSON?)> in + let promise = SnodeAPI.getSwarm(for: groupPublicKey).then2 { [weak self] swarm -> Promise<(Snode, [SnodeReceivedMessage])> in // randomElement() uses the system's default random generator, which is cryptographically secure guard let snode = swarm.randomElement() else { return Promise(error: Error.insufficientSnodes) } - guard let self = self, self.isPolling(for: groupPublicKey) else { return Promise(error: Error.pollingCanceled) } + guard let self = self, self.isPolling(for: groupPublicKey) else { + return Promise(error: Error.pollingCanceled) + } + return SnodeAPI.getRawMessages(from: snode, associatedWith: groupPublicKey).map2 { - let (rawMessages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse($0, from: snode, associatedWith: groupPublicKey) + let messages: [SnodeReceivedMessage] = SnodeAPI.parseRawMessagesResponse($0, from: snode, associatedWith: groupPublicKey) - return (snode, rawMessages, lastRawMessage) + return (snode, messages) } } - promise.done2 { [weak self] snode, rawMessages, lastRawMessage in + promise.done2 { [weak self] snode, messages in guard let self = self, self.isPolling(for: groupPublicKey) else { return } - if !rawMessages.isEmpty { - SNLog("Received \(rawMessages.count) new message(s) in closed group with public key: \(groupPublicKey).") + if !messages.isEmpty { + SNLog("Received \(messages.count) new message(s) in closed group with public key: \(groupPublicKey).") } - rawMessages.forEach { json in - guard let envelope = SNProtoEnvelope.from(json) else { return } + messages.forEach { message in + guard let envelope = SNProtoEnvelope.from(message) else { return } do { let data = try envelope.serializedData() - let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: false) + let job = MessageReceiveJob(data: data, serverHash: message.info.hash, isBackgroundPoll: false) SNMessagingKitConfiguration.shared.storage.write { transaction in SessionMessagingKit.JobQueue.shared.add(job, using: transaction) } @@ -128,8 +131,12 @@ public final class ClosedGroupPoller : NSObject { } } - // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value - SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: groupPublicKey, from: lastRawMessage) + // Now that the MessageReceiveJob's have been created we can persist the received messages + if !messages.isEmpty { + GRDBStorage.shared.write { db in + messages.forEach { try? $0.info.save(db) } + } + } } promise.catch2 { error in SNLog("Polling failed for closed group with public key: \(groupPublicKey) due to error: \(error).") diff --git a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift index c61bc3b71..db0dde51d 100644 --- a/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift +++ b/SessionMessagingKit/Sending & Receiving/Pollers/Poller.swift @@ -5,7 +5,7 @@ import PromiseKit public final class Poller : NSObject { private let storage = OWSPrimaryStorage.shared() private var isPolling = false - private var usedSnodes = Set() + private var usedSnodes = Set() private var pollCount = 0 // MARK: Settings @@ -66,7 +66,7 @@ public final class Poller : NSObject { private func pollNextSnode(seal: Resolver) { let userPublicKey = getUserHexEncodedPublicKey() let swarm = SnodeAPI.swarmCache[userPublicKey] ?? [] - let unusedSnodes = Set(swarm).subtracting(usedSnodes) + let unusedSnodes = swarm.subtracting(usedSnodes) if !unusedSnodes.isEmpty { // randomElement() uses the system's default random generator, which is cryptographically secure let nextSnode = unusedSnodes.randomElement()! @@ -89,20 +89,20 @@ public final class Poller : NSObject { } } - private func poll(_ snode: SessionSnodeKit.Legacy.Snode, seal longTermSeal: Resolver) -> Promise { + private func poll(_ snode: Snode, seal longTermSeal: Resolver) -> Promise { guard isPolling else { return Promise { $0.fulfill(()) } } let userPublicKey = getUserHexEncodedPublicKey() return SnodeAPI.getRawMessages(from: snode, associatedWith: userPublicKey).then(on: Threading.pollerQueue) { [weak self] rawResponse -> Promise in guard let strongSelf = self, strongSelf.isPolling else { return Promise { $0.fulfill(()) } } - let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: userPublicKey) + let messages: [SnodeReceivedMessage] = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: userPublicKey) if !messages.isEmpty { SNLog("Received \(messages.count) new message(s).") } - messages.forEach { json in - guard let envelope = SNProtoEnvelope.from(json) else { return } + messages.forEach { message in + guard let envelope = SNProtoEnvelope.from(message) else { return } do { let data = try envelope.serializedData() - let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: false) + let job = MessageReceiveJob(data: data, serverHash: message.info.hash, isBackgroundPoll: false) SNMessagingKitConfiguration.shared.storage.write { transaction in SessionMessagingKit.JobQueue.shared.add(job, using: transaction) } @@ -111,17 +111,22 @@ public final class Poller : NSObject { } } - // Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value - SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: userPublicKey, from: lastRawMessage) + // Now that the MessageReceiveJob's have been created we can persist the received messages + if !messages.isEmpty { + GRDBStorage.shared.write { db in + messages.forEach { try? $0.info.save(db) } + } + } strongSelf.pollCount += 1 - if strongSelf.pollCount == Poller.maxPollCount { + + guard strongSelf.pollCount < Poller.maxPollCount else { throw Error.pollLimitReached - } else { - return withDelay(Poller.pollInterval, completionQueue: Threading.pollerQueue) { - guard let strongSelf = self, strongSelf.isPolling else { return Promise { $0.fulfill(()) } } - return strongSelf.poll(snode, seal: longTermSeal) - } + } + + return withDelay(Poller.pollInterval, completionQueue: Threading.pollerQueue) { + guard let strongSelf = self, strongSelf.isPolling else { return Promise { $0.fulfill(()) } } + return strongSelf.poll(snode, seal: longTermSeal) } } } diff --git a/SessionMessagingKit/Storage.swift b/SessionMessagingKit/Storage.swift index 6de688097..d866cab4f 100644 --- a/SessionMessagingKit/Storage.swift +++ b/SessionMessagingKit/Storage.swift @@ -13,9 +13,6 @@ public protocol SessionMessagingKitStorageProtocol { // MARK: - General - func getUserPublicKey() -> String? - func getUserKeyPair() -> ECKeyPair? - func getUserED25519KeyPair() -> Box.KeyPair? func getUser() -> Contact? func getUser(using transaction: YapDatabaseReadTransaction?) -> Contact? func getAllContacts() -> Set diff --git a/SessionMessagingKit/To Do/OWSRecipientIdentity.m b/SessionMessagingKit/To Do/OWSRecipientIdentity.m index 5cd1dcec2..d402c6a91 100644 --- a/SessionMessagingKit/To Do/OWSRecipientIdentity.m +++ b/SessionMessagingKit/To Do/OWSRecipientIdentity.m @@ -3,7 +3,6 @@ // #import "OWSRecipientIdentity.h" -#import "OWSIdentityManager.h" #import "OWSPrimaryStorage.h" #import #import diff --git a/SessionMessagingKit/Utilities/General.swift b/SessionMessagingKit/Utilities/General.swift deleted file mode 100644 index d2e4e0c96..000000000 --- a/SessionMessagingKit/Utilities/General.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Foundation - -public enum General { - public enum Cache { - public static var cachedEncodedPublicKey: Atomic = Atomic(nil) - } -} - -@objc(SNGeneralUtilities) -public class GeneralUtilities: NSObject { - @objc public static func getUserPublicKey() -> String { - return getUserHexEncodedPublicKey() - } -} - -public func getUserHexEncodedPublicKey() -> String { - if let cachedKey: String = General.Cache.cachedEncodedPublicKey.wrappedValue { return cachedKey } - - if let keyPair = OWSIdentityManager.shared().identityKeyPair() { // Can be nil under some circumstances - General.Cache.cachedEncodedPublicKey.mutate { $0 = keyPair.hexEncodedPublicKey } - return keyPair.hexEncodedPublicKey - } - - return "" -} diff --git a/SessionMessagingKit/Utilities/OWSIdentityManager.h b/SessionMessagingKit/Utilities/OWSIdentityManager.h deleted file mode 100644 index 9972facbc..000000000 --- a/SessionMessagingKit/Utilities/OWSIdentityManager.h +++ /dev/null @@ -1,74 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import -#import - -@class OWSPrimaryStorage; - -NS_ASSUME_NONNULL_BEGIN - -extern NSString *const OWSPrimaryStorageIdentityKeyStoreIdentityKey; -extern NSString *const LKSeedKey; -extern NSString *const LKED25519SecretKey; -extern NSString *const LKED25519PublicKey; -extern NSString *const OWSPrimaryStorageIdentityKeyStoreCollection; - -extern NSString *const OWSPrimaryStorageTrustedKeysCollection; - -// This notification will be fired whenever identities are created -// or their verification state changes. -extern NSString *const kNSNotificationName_IdentityStateDidChange; - -// number of bytes in a signal identity key, excluding the key-type byte. -extern const NSUInteger kIdentityKeyLength; - -#ifdef DEBUG -extern const NSUInteger kStoredIdentityKeyLength; -#endif - -@class OWSRecipientIdentity; -@class OWSStorage; -@class SNProtoVerified; -@class YapDatabaseReadWriteTransaction; - -// This class can be safely accessed and used from any thread. -@interface OWSIdentityManager : NSObject - -@property (nonatomic, readonly) YapDatabaseConnection *dbConnection; - -- (instancetype)init NS_UNAVAILABLE; -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage NS_DESIGNATED_INITIALIZER; - -+ (instancetype)sharedManager; - -- (void)generateNewIdentityKeyPair; -- (void)clearIdentityKey; - -- (nullable OWSRecipientIdentity *)recipientIdentityForRecipientId:(NSString *)recipientId; - -/** - * @param recipientId unique stable identifier for the recipient, e.g. e164 phone number - * @returns nil if the recipient does not exist, or is trusted for sending - * else returns the untrusted recipient. - */ -- (nullable OWSRecipientIdentity *)untrustedIdentityForSendingToRecipientId:(NSString *)recipientId; - -- (BOOL)saveRemoteIdentity:(NSData *)identityKey recipientId:(NSString *)recipientId; - -- (nullable ECKeyPair *)identityKeyPair; - -#pragma mark - Debug - -#if DEBUG -// Clears everything except the local identity key. -- (void)clearIdentityState:(YapDatabaseReadWriteTransaction *)transaction; - -- (void)snapshotIdentityState:(YapDatabaseReadWriteTransaction *)transaction; -- (void)restoreIdentityState:(YapDatabaseReadWriteTransaction *)transaction; -#endif - -@end - -NS_ASSUME_NONNULL_END diff --git a/SessionMessagingKit/Utilities/OWSIdentityManager.m b/SessionMessagingKit/Utilities/OWSIdentityManager.m deleted file mode 100644 index a4a017baf..000000000 --- a/SessionMessagingKit/Utilities/OWSIdentityManager.m +++ /dev/null @@ -1,405 +0,0 @@ -// -// Copyright (c) 2018 Open Whisper Systems. All rights reserved. -// - -#import "OWSIdentityManager.h" -#import "AppContext.h" -#import "AppReadiness.h" -#import "NSNotificationCenter+OWS.h" -#import "NotificationsProtocol.h" -#import "OWSFileSystem.h" -#import "OWSPrimaryStorage.h" -#import "OWSRecipientIdentity.h" -#import "OWSIdentityManager.h" -#import "SSKEnvironment.h" -#import "TSAccountManager.h" -#import "TSContactThread.h" -#import "TSGroupThread.h" -#import "TSMessage.h" -#import "YapDatabaseConnection+OWS.h" -#import "YapDatabaseTransaction+OWS.h" -#import -#import -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -// Storing our own identity key -NSString *const OWSPrimaryStorageIdentityKeyStoreIdentityKey = @"TSStorageManagerIdentityKeyStoreIdentityKey"; -NSString *const LKSeedKey = @"LKLokiSeed"; -NSString *const LKED25519SecretKey = @"LKED25519SecretKey"; -NSString *const LKED25519PublicKey = @"LKED25519PublicKey"; -NSString *const OWSPrimaryStorageIdentityKeyStoreCollection = @"TSStorageManagerIdentityKeyStoreCollection"; - -// Storing recipients identity keys -NSString *const OWSPrimaryStorageTrustedKeysCollection = @"TSStorageManagerTrustedKeysCollection"; - -NSString *const OWSIdentityManager_QueuedVerificationStateSyncMessages = - @"OWSIdentityManager_QueuedVerificationStateSyncMessages"; - -// Don't trust an identity for sending to unless they've been around for at least this long -const NSTimeInterval kIdentityKeyStoreNonBlockingSecondsThreshold = 5.0; - -// The canonical key includes 32 bytes of identity material plus one byte specifying the key type -const NSUInteger kIdentityKeyLength = 33; - -// Cryptographic operations do not use the "type" byte of the identity key, so, for legacy reasons we store just -// the identity material. -// TODO: migrate to storing the full 33 byte representation. -const NSUInteger kStoredIdentityKeyLength = 32; - -NSString *const kNSNotificationName_IdentityStateDidChange = @"kNSNotificationName_IdentityStateDidChange"; - -@interface OWSIdentityManager () - -@property (nonatomic, readonly) OWSPrimaryStorage *primaryStorage; - -@end - -#pragma mark - - -@implementation OWSIdentityManager - -+ (instancetype)sharedManager -{ - return SSKEnvironment.shared.identityManager; -} - -- (instancetype)initWithPrimaryStorage:(OWSPrimaryStorage *)primaryStorage -{ - self = [super init]; - - if (!self) { - return self; - } - - _primaryStorage = primaryStorage; - _dbConnection = primaryStorage.newDatabaseConnection; - self.dbConnection.objectCacheEnabled = NO; - - [self observeNotifications]; - - return self; -} - -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -#pragma mark - - -- (void)observeNotifications -{ - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(applicationDidBecomeActive:) - name:UIApplicationDidBecomeActiveNotification - object:nil]; -} - -- (void)generateNewIdentityKeyPair -{ - ECKeyPair *keyPair = [Curve25519 generateKeyPair]; - [self.dbConnection setObject:keyPair forKey:OWSPrimaryStorageIdentityKeyStoreIdentityKey inCollection:OWSPrimaryStorageIdentityKeyStoreCollection]; -} - -- (void)clearIdentityKey -{ - [self.dbConnection removeObjectForKey:OWSPrimaryStorageIdentityKeyStoreIdentityKey - inCollection:OWSPrimaryStorageIdentityKeyStoreCollection]; -} - -- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId -{ - __block NSData *_Nullable result = nil; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { - result = [self identityKeyForRecipientId:recipientId transaction:transaction]; - }]; - return result; -} - -- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId protocolContext:(nullable id)protocolContext -{ - YapDatabaseReadTransaction *transaction = protocolContext; - - return [self identityKeyForRecipientId:recipientId transaction:transaction]; -} - -- (nullable NSData *)identityKeyForRecipientId:(NSString *)recipientId - transaction:(YapDatabaseReadTransaction *)transaction -{ - return [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId transaction:transaction].identityKey; -} - -- (nullable ECKeyPair *)identityKeyPair -{ - __block ECKeyPair *_Nullable identityKeyPair = nil; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - identityKeyPair = [self identityKeyPairWithTransaction:transaction]; - }]; - return identityKeyPair; -} - -// This method should only be called from SignalProtocolKit, which doesn't know about YapDatabaseTransactions. -// Whenever possible, prefer to call the strongly typed variant: `identityKeyPairWithTransaction:`. -- (nullable ECKeyPair *)identityKeyPair:(nullable id)protocolContext -{ - YapDatabaseReadTransaction *transaction = protocolContext; - - return [self identityKeyPairWithTransaction:transaction]; -} - -- (nullable ECKeyPair *)identityKeyPairWithTransaction:(YapDatabaseReadTransaction *)transaction -{ - ECKeyPair *_Nullable identityKeyPair = [transaction keyPairForKey:OWSPrimaryStorageIdentityKeyStoreIdentityKey - inCollection:OWSPrimaryStorageIdentityKeyStoreCollection]; - return identityKeyPair; -} - -- (int)localRegistrationId:(nullable id)protocolContext -{ - YapDatabaseReadWriteTransaction *transaction = protocolContext; - - return (int)[TSAccountManager getOrGenerateRegistrationId:transaction]; -} - -- (BOOL)saveRemoteIdentity:(NSData *)identityKey recipientId:(NSString *)recipientId -{ - __block BOOL result; - [LKStorage writeSyncWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - result = [self saveRemoteIdentity:identityKey recipientId:recipientId protocolContext:transaction]; - }]; - - return result; -} - -- (BOOL)saveRemoteIdentity:(NSData *)identityKey - recipientId:(NSString *)recipientId - protocolContext:(nullable id)protocolContext -{ - YapDatabaseReadWriteTransaction *transaction = protocolContext; - - // Deprecated. We actually no longer use the OWSPrimaryStorageTrustedKeysCollection for trust - // decisions, but it's desirable to try to keep it up to date with our trusted identitys - // while we're switching between versions, e.g. so we don't get into a state where we have a - // session for an identity not in our key store. - [transaction setObject:identityKey forKey:recipientId inCollection:OWSPrimaryStorageTrustedKeysCollection]; - - OWSRecipientIdentity *existingIdentity = - [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId transaction:transaction]; - - if (existingIdentity == nil) { - [[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId - identityKey:identityKey - isFirstKnownKey:YES - createdAt:[NSDate new] - verificationState:OWSVerificationStateDefault] - saveWithTransaction:transaction]; - - [self fireIdentityStateChangeNotification]; - - return NO; - } - - if (![existingIdentity.identityKey isEqual:identityKey]) { - OWSVerificationState verificationState; - switch (existingIdentity.verificationState) { - case OWSVerificationStateDefault: - verificationState = OWSVerificationStateDefault; - break; - case OWSVerificationStateVerified: - case OWSVerificationStateNoLongerVerified: - verificationState = OWSVerificationStateNoLongerVerified; - break; - } - - [[[OWSRecipientIdentity alloc] initWithRecipientId:recipientId - identityKey:identityKey - isFirstKnownKey:NO - createdAt:[NSDate new] - verificationState:verificationState] saveWithTransaction:transaction]; - - [self fireIdentityStateChangeNotification]; - - return YES; - } - - return NO; -} - -- (nullable OWSRecipientIdentity *)recipientIdentityForRecipientId:(NSString *)recipientId -{ - __block OWSRecipientIdentity *_Nullable result; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - result = [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId transaction:transaction]; - }]; - return result; -} - -- (nullable OWSRecipientIdentity *)untrustedIdentityForSendingToRecipientId:(NSString *)recipientId -{ - __block OWSRecipientIdentity *_Nullable result; - [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) { - OWSRecipientIdentity *_Nullable recipientIdentity = - [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId transaction:transaction]; - - if (recipientIdentity == nil) { - // trust on first use - return; - } - - BOOL isTrusted = [self isTrustedIdentityKey:recipientIdentity.identityKey - recipientId:recipientId - direction:TSMessageDirectionOutgoing - transaction:transaction]; - if (isTrusted) { - return; - } else { - result = recipientIdentity; - } - }]; - return result; -} - -- (void)fireIdentityStateChangeNotification -{ - [[NSNotificationCenter defaultCenter] postNotificationNameAsync:kNSNotificationName_IdentityStateDidChange - object:nil - userInfo:nil]; -} - -- (BOOL)isTrustedIdentityKey:(NSData *)identityKey - recipientId:(NSString *)recipientId - direction:(TSMessageDirection)direction - protocolContext:(nullable id)protocolContext -{ - YapDatabaseReadWriteTransaction *transaction = protocolContext; - - return [self isTrustedIdentityKey:identityKey recipientId:recipientId direction:direction transaction:transaction]; -} - -- (BOOL)isTrustedIdentityKey:(NSData *)identityKey - recipientId:(NSString *)recipientId - direction:(TSMessageDirection)direction - transaction:(YapDatabaseReadTransaction *)transaction -{ - if ([[TSAccountManager localNumber] isEqualToString:recipientId]) { - ECKeyPair *_Nullable localIdentityKeyPair = [self identityKeyPairWithTransaction:transaction]; - - if ([localIdentityKeyPair.publicKey isEqualToData:identityKey]) { - return YES; - } else { - return NO; - } - } - - switch (direction) { - case TSMessageDirectionIncoming: { - return YES; - } - case TSMessageDirectionOutgoing: { - OWSRecipientIdentity *existingIdentity = - [OWSRecipientIdentity fetchObjectWithUniqueID:recipientId transaction:transaction]; - return [self isTrustedKey:identityKey forSendingToIdentity:existingIdentity]; - } - default: { - return NO; - } - } -} - -- (BOOL)isTrustedKey:(NSData *)identityKey forSendingToIdentity:(nullable OWSRecipientIdentity *)recipientIdentity -{ - if (recipientIdentity == nil) { - return YES; - } - - if (![recipientIdentity.identityKey isEqualToData:identityKey]) { - return NO; - } - - if ([recipientIdentity isFirstKnownKey]) { - return YES; - } - - switch (recipientIdentity.verificationState) { - case OWSVerificationStateDefault: { - BOOL isNew = (fabs([recipientIdentity.createdAt timeIntervalSinceNow]) - < kIdentityKeyStoreNonBlockingSecondsThreshold); - if (isNew) { - return NO; - } else { - return YES; - } - } - case OWSVerificationStateVerified: - return YES; - case OWSVerificationStateNoLongerVerified: - return NO; - } -} - -#pragma mark - Debug - -#if DEBUG -- (void)clearIdentityState:(YapDatabaseReadWriteTransaction *)transaction -{ - NSMutableArray *identityKeysToRemove = [NSMutableArray new]; - [transaction enumerateKeysInCollection:OWSPrimaryStorageIdentityKeyStoreCollection - usingBlock:^(NSString *_Nonnull key, BOOL *_Nonnull stop) { - if ([key isEqualToString:OWSPrimaryStorageIdentityKeyStoreIdentityKey]) { - // Don't delete our own key. - return; - } - [identityKeysToRemove addObject:key]; - }]; - for (NSString *key in identityKeysToRemove) { - [transaction removeObjectForKey:key inCollection:OWSPrimaryStorageIdentityKeyStoreCollection]; - } - [transaction removeAllObjectsInCollection:OWSPrimaryStorageTrustedKeysCollection]; -} - -- (NSString *)identityKeySnapshotFilePath -{ - // Prefix name with period "." so that backups will ignore these snapshots. - NSString *dirPath = [OWSFileSystem appDocumentDirectoryPath]; - return [dirPath stringByAppendingPathComponent:@".identity-key-snapshot"]; -} - -- (NSString *)trustedKeySnapshotFilePath -{ - // Prefix name with period "." so that backups will ignore these snapshots. - NSString *dirPath = [OWSFileSystem appDocumentDirectoryPath]; - return [dirPath stringByAppendingPathComponent:@".trusted-key-snapshot"]; -} - -- (void)snapshotIdentityState:(YapDatabaseReadWriteTransaction *)transaction -{ - [transaction snapshotCollection:OWSPrimaryStorageIdentityKeyStoreCollection - snapshotFilePath:self.identityKeySnapshotFilePath]; - [transaction snapshotCollection:OWSPrimaryStorageTrustedKeysCollection - snapshotFilePath:self.trustedKeySnapshotFilePath]; -} - -- (void)restoreIdentityState:(YapDatabaseReadWriteTransaction *)transaction -{ - [transaction restoreSnapshotOfCollection:OWSPrimaryStorageIdentityKeyStoreCollection - snapshotFilePath:self.identityKeySnapshotFilePath]; - [transaction restoreSnapshotOfCollection:OWSPrimaryStorageTrustedKeysCollection - snapshotFilePath:self.trustedKeySnapshotFilePath]; -} - -#endif - -#pragma mark - Notifications - -- (void)applicationDidBecomeActive:(NSNotification *)notification -{ - -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SessionMessagingKit/Utilities/SNProtoEnvelope+Conversion.swift b/SessionMessagingKit/Utilities/SNProtoEnvelope+Conversion.swift index b186b8cf5..d17f65563 100644 --- a/SessionMessagingKit/Utilities/SNProtoEnvelope+Conversion.swift +++ b/SessionMessagingKit/Utilities/SNProtoEnvelope+Conversion.swift @@ -1,15 +1,15 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionSnodeKit public extension SNProtoEnvelope { - - static func from(_ json: JSON) -> SNProtoEnvelope? { - guard let base64EncodedData = json["data"] as? String, let data = Data(base64Encoded: base64EncodedData) else { - SNLog("Failed to decode data for message: \(json).") - return nil - } - guard let result = try? MessageWrapper.unwrap(data: data) else { - SNLog("Failed to unwrap data for message: \(json).") + static func from(_ message: SnodeReceivedMessage) -> SNProtoEnvelope? { + guard let result = try? MessageWrapper.unwrap(data: message.data) else { + SNLog("Failed to unwrap data for message: \(String(reflecting: message)).") return nil } + return result } } diff --git a/SessionMessagingKit/Utilities/SSKEnvironment.h b/SessionMessagingKit/Utilities/SSKEnvironment.h index d44250910..c1e3112f5 100644 --- a/SessionMessagingKit/Utilities/SSKEnvironment.h +++ b/SessionMessagingKit/Utilities/SSKEnvironment.h @@ -38,7 +38,6 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithProfileManager:(id)profileManager primaryStorage:(OWSPrimaryStorage *)primaryStorage - identityManager:(OWSIdentityManager *)identityManager tsAccountManager:(TSAccountManager *)tsAccountManager disappearingMessagesJob:(OWSDisappearingMessagesJob *)disappearingMessagesJob readReceiptManager:(OWSReadReceiptManager *)readReceiptManager diff --git a/SessionMessagingKit/Utilities/SSKEnvironment.m b/SessionMessagingKit/Utilities/SSKEnvironment.m index 2c9046129..959487585 100644 --- a/SessionMessagingKit/Utilities/SSKEnvironment.m +++ b/SessionMessagingKit/Utilities/SSKEnvironment.m @@ -14,7 +14,6 @@ static SSKEnvironment *sharedSSKEnvironment; @property (nonatomic) id profileManager; @property (nonatomic) OWSPrimaryStorage *primaryStorage; -@property (nonatomic) OWSIdentityManager *identityManager; @property (nonatomic) TSAccountManager *tsAccountManager; @property (nonatomic) OWSDisappearingMessagesJob *disappearingMessagesJob; @property (nonatomic) OWSReadReceiptManager *readReceiptManager; @@ -36,7 +35,6 @@ static SSKEnvironment *sharedSSKEnvironment; - (instancetype)initWithProfileManager:(id)profileManager primaryStorage:(OWSPrimaryStorage *)primaryStorage - identityManager:(OWSIdentityManager *)identityManager tsAccountManager:(TSAccountManager *)tsAccountManager disappearingMessagesJob:(OWSDisappearingMessagesJob *)disappearingMessagesJob readReceiptManager:(OWSReadReceiptManager *)readReceiptManager @@ -52,7 +50,6 @@ static SSKEnvironment *sharedSSKEnvironment; _profileManager = profileManager; _primaryStorage = primaryStorage; - _identityManager = identityManager; _tsAccountManager = tsAccountManager; _disappearingMessagesJob = disappearingMessagesJob; _readReceiptManager = readReceiptManager; diff --git a/SessionNotificationServiceExtension/NSENotificationPresenter.swift b/SessionNotificationServiceExtension/NSENotificationPresenter.swift index 148876540..4f1b8b940 100644 --- a/SessionNotificationServiceExtension/NSENotificationPresenter.swift +++ b/SessionNotificationServiceExtension/NSENotificationPresenter.swift @@ -28,7 +28,7 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol { } let senderPublicKey = incomingMessage.authorId - let userPublicKey = GeneralUtilities.getUserPublicKey() + let userPublicKey = getUserHexEncodedPublicKey() guard senderPublicKey != userPublicKey else { // Ignore PNs for messages sent by the current user // after handling the message. Otherwise the closed diff --git a/SessionSnodeKit/Configuration.swift b/SessionSnodeKit/Configuration.swift index 24a3074d5..2c063507a 100644 --- a/SessionSnodeKit/Configuration.swift +++ b/SessionSnodeKit/Configuration.swift @@ -2,8 +2,6 @@ import Foundation import SessionUtilitiesKit public struct SNSnodeKitConfiguration { - public let storage: SessionSnodeKitStorageProtocol - internal static var shared: SNSnodeKitConfiguration! } @@ -22,7 +20,6 @@ public enum SNSnodeKit { // Just to make the external API nice ) } - public static func configure(storage: SessionSnodeKitStorageProtocol) { - SNSnodeKitConfiguration.shared = SNSnodeKitConfiguration(storage: storage) + public static func configure() { } } diff --git a/SessionSnodeKit/Database/LegacyDatabase/SSKLegacyModels.swift b/SessionSnodeKit/Database/LegacyDatabase/SSKLegacyModels.swift index 52d873245..72db8da97 100644 --- a/SessionSnodeKit/Database/LegacyDatabase/SSKLegacyModels.swift +++ b/SessionSnodeKit/Database/LegacyDatabase/SSKLegacyModels.swift @@ -6,12 +6,12 @@ public enum Legacy { // MARK: - Collections and Keys internal static let swarmCollectionPrefix = "LokiSwarmCollection-" + internal static let lastSnodePoolRefreshDateKey = "lastSnodePoolRefreshDate" internal static let snodePoolCollection = "LokiSnodePoolCollection" internal static let onionRequestPathCollection = "LokiOnionRequestPathCollection" internal static let lastSnodePoolRefreshDateCollection = "LokiLastSnodePoolRefreshDateCollection" internal static let lastMessageHashCollection = "LokiLastMessageHashCollection" // TODO: Remove this one? (make it a query??) internal static let receivedMessagesCollection = "LokiReceivedMessagesCollection" - // TODO: - "lastSnodePoolRefreshDate" // MARK: - Types @@ -29,15 +29,6 @@ public enum Legacy { } // MARK: Nested Types - public enum Method : String { - case getSwarm = "get_snodes_for_pubkey" - case getMessages = "retrieve" - case sendMessage = "store" - case deleteMessage = "delete" - case oxenDaemonRPCCall = "oxend_request" - case getInfo = "info" - case clearAllData = "delete_all" - } public struct KeySet { public let ed25519Key: String diff --git a/SessionSnodeKit/Database/Migrations/_002_YDBToGRDBMigration.swift b/SessionSnodeKit/Database/Migrations/_002_YDBToGRDBMigration.swift index 4a173265f..416f5d65d 100644 --- a/SessionSnodeKit/Database/Migrations/_002_YDBToGRDBMigration.swift +++ b/SessionSnodeKit/Database/Migrations/_002_YDBToGRDBMigration.swift @@ -14,8 +14,15 @@ enum _002_YDBToGRDBMigration: Migration { // Note: Want to exclude the Snode's we already added from the 'onionRequestPathResult' var snodeResult: Set = [] var snodeSetResult: [String: Set] = [:] + var lastSnodePoolRefreshDate: Date? = nil Storage.read { transaction in + // Process the lastSnodePoolRefreshDate + lastSnodePoolRefreshDate = transaction.object( + forKey: Legacy.lastSnodePoolRefreshDateKey, + inCollection: Legacy.lastSnodePoolRefreshDateCollection + ) as? Date + // Process the OnionRequestPaths if let path0Snode0 = transaction.object(forKey: "0-0", inCollection: Legacy.onionRequestPathCollection) as? Legacy.Snode, @@ -65,6 +72,10 @@ enum _002_YDBToGRDBMigration: Migration { } } + // Insert the data into GRDB + + db[.lastSnodePoolRefreshDate] = lastSnodePoolRefreshDate + try snodeResult.forEach { legacySnode in try Snode( address: legacySnode.address, @@ -79,20 +90,13 @@ enum _002_YDBToGRDBMigration: Migration { // Note: In this case the 'nodeIndex' is irrelivant try SnodeSet( key: key, - nodeIndex: UInt(nodeIndex), + nodeIndex: nodeIndex, address: legacySnode.address, port: legacySnode.port ).insert(db) } } - // TODO: This -// public func setLastSnodePoolRefreshDate(to date: Date, using transaction: Any) { -// (transaction as! YapDatabaseReadWriteTransaction).setObject(date, forKey: "lastSnodePoolRefreshDate", inCollection: Storage.lastSnodePoolRefreshDateCollection) -// } - - print("RAWR") - // MARK: - Received Messages & Last Message Hash var lastMessageResults: [String: (hash: String, json: JSON)] = [:] diff --git a/SessionSnodeKit/Database/Models/Snode.swift b/SessionSnodeKit/Database/Models/Snode.swift index 7e4a2afc6..1e18066cd 100644 --- a/SessionSnodeKit/Database/Models/Snode.swift +++ b/SessionSnodeKit/Database/Models/Snode.swift @@ -4,18 +4,100 @@ import Foundation import GRDB import SessionUtilitiesKit -public struct Snode: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible, Hashable { +public struct Snode: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible, Hashable, CustomStringConvertible { public static var databaseTableName: String { "snode" } + static let snodeSet = hasMany(SnodeSet.self) + static let snodeSetForeignKey = ForeignKey( + [Columns.address, Columns.port], + to: [SnodeSet.Columns.address, SnodeSet.Columns.port] + ) - public enum Columns: String, CodingKey, ColumnExpression { - case address - case port - case ed25519PublicKey - case x25519PublicKey + public typealias Columns = CodingKeys + public enum CodingKeys: String, CodingKey, ColumnExpression { + case address = "public_ip" + case port = "storage_port" + case ed25519PublicKey = "pubkey_ed25519" + case x25519PublicKey = "pubkey_x25519" + } + + public let address: String + public let port: UInt16 + public let ed25519PublicKey: String + public let x25519PublicKey: String + + public var ip: String { + guard let range = address.range(of: "https://"), range.lowerBound == address.startIndex else { + return address + } + + return String(address[range.upperBound.. { + request(for: Snode.snodeSet) + } + + public var description: String { return "\(address):\(port)" } +} + +// MARK: - Decoder + +extension Snode { + public init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + do { + let address: String = try container.decode(String.self, forKey: .address) + + guard address != "0.0.0.0" else { throw SnodeAPI.Error.invalidIP } + + self = Snode( + address: (address.starts(with: "https://") ? address : "https://\(address)"), + port: try container.decode(UInt16.self, forKey: .port), + ed25519PublicKey: try container.decode(String.self, forKey: .ed25519PublicKey), + x25519PublicKey: try container.decode(String.self, forKey: .x25519PublicKey) + ) + } + catch { + SNLog("Failed to parse snode: \(error.localizedDescription).") + throw HTTP.Error.invalidJSON + } + } +} + +// MARK: - Convenience + +internal extension Snode { + static func fetchSet(_ db: Database, publicKey: String) throws -> Set { + return try Snode + .joining( + required: Snode.snodeSet + .filter(SnodeSet.Columns.key == publicKey) + .order(SnodeSet.Columns.nodeIndex) + ) + .fetchSet(db) + } +} + +internal extension Collection where Element == Snode { + func save(_ db: Database, key: String) throws { + try self.enumerated().forEach { nodeIndex, node in + try node.save(db) + + try SnodeSet( + key: key, + nodeIndex: nodeIndex, + address: node.address, + port: node.port + ).save(db) + } + } +} + +internal extension Collection where Element == [Snode] { + func save(_ db: Database) throws { + try self.enumerated().forEach { pathIndex, path in + try path.save(db, key: "\(SnodeSet.onionRequestPathPrefix)\(pathIndex)") + } + } } diff --git a/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift b/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift index d6c80c16d..7c1a2ade7 100644 --- a/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift +++ b/SessionSnodeKit/Database/Models/SnodeReceivedMessageInfo.swift @@ -4,16 +4,56 @@ import Foundation import GRDB import SessionUtilitiesKit -struct SnodeReceivedMessageInfo: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { - static var databaseTableName: String { "snodeReceivedMessageInfo" } +public struct SnodeReceivedMessageInfo: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { + public static var databaseTableName: String { "snodeReceivedMessageInfo" } - public enum Columns: String, CodingKey, ColumnExpression { + public typealias Columns = CodingKeys + public enum CodingKeys: String, CodingKey, ColumnExpression { case key case hash case expirationDateMs } - let key: String - let hash: String - let expirationDateMs: Int64 + public let key: String + public let hash: String + public let expirationDateMs: Int64 +} + +// MARK: - Convenience + +public extension SnodeReceivedMessageInfo { + private static func key(for snode: Snode, publicKey: String) -> String { + return "\(snode.address):\(snode.port).\(publicKey)" + } + + init( + snode: Snode, + publicKey: String, + hash: String, + expirationDateMs: Int64? + ) { + self.key = SnodeReceivedMessageInfo.key(for: snode, publicKey: publicKey) + self.hash = hash + self.expirationDateMs = (expirationDateMs ?? 0) + } + + static func pruneLastMessageHashInfoIfExpired(for snode: Snode, associatedWith publicKey: String) { + // Clear out the 'expirationDateMs' value for all expired (but non-0) message infos + GRDBStorage.shared.write { db in + try? SnodeReceivedMessageInfo + .filter(SnodeReceivedMessageInfo.Columns.key == key(for: snode, publicKey: publicKey)) + .filter(SnodeReceivedMessageInfo.Columns.expirationDateMs > 0) + .updateAll(db, SnodeReceivedMessageInfo.Columns.expirationDateMs.set(to: 0)) + } + } + + static func fetchLastNotExpired(for snode: Snode, associatedWith publicKey: String) -> SnodeReceivedMessageInfo? { + return GRDBStorage.shared.read { db in + try? SnodeReceivedMessageInfo + .filter(SnodeReceivedMessageInfo.Columns.key == key(for: snode, publicKey: publicKey)) + .order(SnodeReceivedMessageInfo.Columns.expirationDateMs) + .reversed() + .fetchOne(db) + } + } } diff --git a/SessionSnodeKit/Database/Models/SnodeSet.swift b/SessionSnodeKit/Database/Models/SnodeSet.swift index 4668d2e40..597597ea8 100644 --- a/SessionSnodeKit/Database/Models/SnodeSet.swift +++ b/SessionSnodeKit/Database/Models/SnodeSet.swift @@ -4,24 +4,59 @@ import Foundation import GRDB import SessionUtilitiesKit -struct SnodeSet: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { - static var databaseTableName: String { "snodeSet" } - static let nodes = hasMany(Snode.self) - static let onionRequestPathPrefix = "OnionRequestPath-" - - public enum Columns: String, CodingKey, ColumnExpression { +public struct SnodeSet: Codable, FetchableRecord, EncodableRecord, PersistableRecord, TableRecord, ColumnExpressible { + public static let onionRequestPathPrefix = "OnionRequestPath-" + public static var databaseTableName: String { "snodeSetAssociation" } + static let node = hasOne(Snode.self, using: Snode.snodeSetForeignKey) + + public typealias Columns = CodingKeys + public enum CodingKeys: String, CodingKey, ColumnExpression { case key case nodeIndex case address case port } - let key: String - let nodeIndex: UInt - let address: String - let port: UInt16 + public let key: String + public let nodeIndex: Int + public let address: String + public let port: UInt16 - var nodes: QueryInterfaceRequest { - request(for: SnodeSet.nodes) + public var node: QueryInterfaceRequest { + request(for: SnodeSet.node) + } +} + +// MARK: - Convenience + +internal extension SnodeSet { + static func fetchAllOnionRequestPaths(_ db: Database) throws -> [[Snode]] { + struct ResultWrapper: Decodable, FetchableRecord { + let key: String + let nodeIndex: Int + let address: String + let port: UInt16 + let snode: Snode + } + + return try SnodeSet + .filter(SnodeSet.Columns.key.like("\(SnodeSet.onionRequestPathPrefix)%")) + .order(SnodeSet.Columns.nodeIndex) + .order(SnodeSet.Columns.key) + .including(required: SnodeSet.node) + .asRequest(of: ResultWrapper.self) + .fetchAll(db) + .reduce(into: [:]) { prev, next in // Reducing will lose the 'key' sorting + prev[next.key] = (prev[next.key] ?? []).appending(next.snode) + } + .asArray() + .sorted(by: { lhs, rhs in lhs.key < rhs.key }) + .compactMap { _, nodes in !nodes.isEmpty ? nodes : nil } // Exclude empty sets + } + + static func clearOnionRequestPaths(_ db: Database) throws { + try SnodeSet + .filter(SnodeSet.Columns.key.like("\(SnodeSet.onionRequestPathPrefix)%")) + .deleteAll(db) } } diff --git a/SessionSnodeKit/Database/Types/SSKSetting.swift b/SessionSnodeKit/Database/Types/SSKSetting.swift index 0a8ab0dac..7db9629d9 100644 --- a/SessionSnodeKit/Database/Types/SSKSetting.swift +++ b/SessionSnodeKit/Database/Types/SSKSetting.swift @@ -1,3 +1,8 @@ // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. import Foundation +import SessionUtilitiesKit + +extension Setting.DateKey { + static let lastSnodePoolRefreshDate: Setting.DateKey = "lastSnodePoolRefreshDate" +} diff --git a/SessionSnodeKit/Models/SnodeReceivedMessage.swift b/SessionSnodeKit/Models/SnodeReceivedMessage.swift new file mode 100644 index 000000000..36bd94cc9 --- /dev/null +++ b/SessionSnodeKit/Models/SnodeReceivedMessage.swift @@ -0,0 +1,33 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +public struct SnodeReceivedMessage: CustomDebugStringConvertible { + public let info: SnodeReceivedMessageInfo + public let data: Data + + init?(snode: Snode, publicKey: String, rawMessage: JSON) { + guard let hash: String = rawMessage["hash"] as? String else { return nil } + + guard + let base64EncodedString: String = rawMessage["data"] as? String, + let data: Data = Data(base64Encoded: base64EncodedString) + else { + SNLog("Failed to decode data for message: \(rawMessage).") + return nil + } + + self.info = SnodeReceivedMessageInfo( + snode: snode, + publicKey: publicKey, + hash: hash, + expirationDateMs: rawMessage["expiration"] as? Int64 + ) + self.data = data + } + + public var debugDescription: String { + return "{\"hash\":\(info.hash),\"expiration\":\(info.expirationDateMs),\"data\":\"\(data.base64EncodedString())\"}" + } +} diff --git a/SessionSnodeKit/Models/SwarmSnode.swift b/SessionSnodeKit/Models/SwarmSnode.swift new file mode 100644 index 000000000..09dc2963e --- /dev/null +++ b/SessionSnodeKit/Models/SwarmSnode.swift @@ -0,0 +1,58 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +/// It looks like the structure for the service node returned from `get_snodes_for_pubkey` is different from +/// the usual structure, this type is used as an intemediary to convert to the usual 'Snode' type +// FIXME: Hopefully at some point this different Snode structure will be deprecated and can be removed +internal struct SwarmSnode: Codable { + public enum CodingKeys: String, CodingKey { + case address = "ip" + case port + case ed25519PublicKey = "pubkey_ed25519" + case x25519PublicKey = "pubkey_x25519" + } + + let address: String + let port: UInt16 + let ed25519PublicKey: String + let x25519PublicKey: String +} + +// MARK: - Convenience + +extension SwarmSnode { + public init(from decoder: Decoder) throws { + let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + + do { + let address: String = try container.decode(String.self, forKey: .address) + let portString: String = try container.decode(String.self, forKey: .port) + + guard address != "0.0.0.0", let port: UInt16 = UInt16(portString) else { + throw SnodeAPI.Error.invalidIP + } + + self = SwarmSnode( + address: (address.starts(with: "https://") ? address : "https://\(address)"), + port: port, + ed25519PublicKey: try container.decode(String.self, forKey: .ed25519PublicKey), + x25519PublicKey: try container.decode(String.self, forKey: .x25519PublicKey) + ) + } + catch { + SNLog("Failed to parse snode: \(error.localizedDescription).") + throw HTTP.Error.invalidJSON + } + } + + func toSnode() -> Snode { + return Snode( + address: address, + port: port, + ed25519PublicKey: ed25519PublicKey, + x25519PublicKey: x25519PublicKey + ) + } +} diff --git a/SessionSnodeKit/OnionRequestAPI+Encryption.swift b/SessionSnodeKit/OnionRequestAPI+Encryption.swift index 27a7ab31c..fda01bfe7 100644 --- a/SessionSnodeKit/OnionRequestAPI+Encryption.swift +++ b/SessionSnodeKit/OnionRequestAPI+Encryption.swift @@ -22,7 +22,7 @@ internal extension OnionRequestAPI { // Wrapping isn't needed for file server or open group onion requests switch destination { case .snode(let snode): - let snodeX25519PublicKey = snode.publicKeySet.x25519Key + let snodeX25519PublicKey = snode.x25519PublicKey let payloadAsData = try JSONSerialization.data(withJSONObject: payload, options: [ .fragmentsAllowed ]) let plaintext = try encode(ciphertext: payloadAsData, json: [ "headers" : "" ]) let result = try AESGCM.encrypt(plaintext, for: snodeX25519PublicKey) @@ -46,7 +46,7 @@ internal extension OnionRequestAPI { var parameters: JSON switch rhs { case .snode(let snode): - let snodeED25519PublicKey = snode.publicKeySet.ed25519Key + let snodeED25519PublicKey = snode.ed25519PublicKey parameters = [ "destination" : snodeED25519PublicKey ] case .server(let host, let target, _, let scheme, let port): let scheme = scheme ?? "https" @@ -57,7 +57,7 @@ internal extension OnionRequestAPI { let x25519PublicKey: String switch lhs { case .snode(let snode): - let snodeX25519PublicKey = snode.publicKeySet.x25519Key + let snodeX25519PublicKey = snode.x25519PublicKey x25519PublicKey = snodeX25519PublicKey case .server(_, _, let serverX25519PublicKey, _, _): x25519PublicKey = serverX25519PublicKey diff --git a/SessionSnodeKit/OnionRequestAPI.swift b/SessionSnodeKit/OnionRequestAPI.swift index b50bee214..2ae59b8a7 100644 --- a/SessionSnodeKit/OnionRequestAPI.swift +++ b/SessionSnodeKit/OnionRequestAPI.swift @@ -1,19 +1,40 @@ +import Foundation import CryptoSwift +import GRDB import PromiseKit import SessionUtilitiesKit /// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information. public enum OnionRequestAPI { - private static var buildPathsPromise: Promise<[Path]>? = nil + private static var buildPathsPromise: Promise<[[Snode]]>? = nil + /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - private static var pathFailureCount: [Path:UInt] = [:] + private static var pathFailureCount: [[Snode]: UInt] = [:] + /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - private static var snodeFailureCount: [Legacy.Snode:UInt] = [:] + private static var snodeFailureCount: [Snode: UInt] = [:] + /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - public static var guardSnodes: Set = [] - public static var paths: [Path] = [] // Not a set to ensure we consistently show the same path to the user + public static var guardSnodes: Set = [] + + // Not a set to ensure we consistently show the same path to the user + private static var _paths: [[Snode]]? + public static var paths: [[Snode]] { + get { + if let paths: [[Snode]] = _paths { return paths } + + let results: [[Snode]]? = GRDBStorage.shared.read { db in + try? SnodeSet.fetchAllOnionRequestPaths(db) + } + + if results?.isEmpty == false { _paths = results } + return (results ?? []) + } + set { _paths = newValue } + } - // MARK: Settings + // MARK: - Settings + public static let maxRequestSize = 10_000_000 // 10 MB /// The number of snodes (including the guard snode) in a path. private static let pathSize: UInt = 3 @@ -27,97 +48,80 @@ public enum OnionRequestAPI { /// The number of guard snodes required to maintain `targetPathCount` paths. private static var targetGuardSnodeCount: UInt { return targetPathCount } // One per path - // MARK: Destination - public enum Destination : CustomStringConvertible { - case snode(Legacy.Snode) - case server(host: String, target: String, x25519PublicKey: String, scheme: String?, port: UInt16?) - - public var description: String { - switch self { - case .snode(let snode): return "Service node \(snode.ip):\(snode.port)" - case .server(let host, _, _, _, _): return host - } - } - } - - // MARK: Error - public enum Error : LocalizedError { - case httpRequestFailedAtDestination(statusCode: UInt, json: JSON, destination: Destination) - case insufficientSnodes - case invalidURL - case missingSnodeVersion - case snodePublicKeySetMissing - case unsupportedSnodeVersion(String) - - public var errorDescription: String? { - switch self { - case .httpRequestFailedAtDestination(let statusCode, _, let destination): - if statusCode == 429 { - return "Rate limited." - } else { - return "HTTP request failed at destination (\(destination)) with status code: \(statusCode)." - } - case .insufficientSnodes: return "Couldn't find enough Service Nodes to build a path." - case .invalidURL: return "Invalid URL" - case .missingSnodeVersion: return "Missing Service Node version." - case .snodePublicKeySetMissing: return "Missing Service Node public key set." - case .unsupportedSnodeVersion(let version): return "Unsupported Service Node version: \(version)." - } - } - } - - // MARK: Path - public typealias Path = [Legacy.Snode] - // MARK: Onion Building Result - private typealias OnionBuildingResult = (guardSnode: Legacy.Snode, finalEncryptionResult: AESGCM.EncryptionResult, destinationSymmetricKey: Data) + private typealias OnionBuildingResult = (guardSnode: Snode, finalEncryptionResult: AESGCM.EncryptionResult, destinationSymmetricKey: Data) // MARK: Private API /// Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise. - private static func testSnode(_ snode: Legacy.Snode) -> Promise { + private static func testSnode(_ snode: Snode) -> Promise { let (promise, seal) = Promise.pending() + DispatchQueue.global(qos: .userInitiated).async { let url = "\(snode.address):\(snode.port)/get_stats/v1" let timeout: TimeInterval = 3 // Use a shorter timeout for testing - HTTP.execute(.get, url, timeout: timeout).done2 { json in - guard let version = json["version"] as? String else { return seal.reject(Error.missingSnodeVersion) } - if version >= "2.0.7" { - seal.fulfill(()) - } else { - SNLog("Unsupported snode version: \(version).") - seal.reject(Error.unsupportedSnodeVersion(version)) + + HTTP.execute(.get, url, timeout: timeout) + .done2 { json in + guard let version = json["version"] as? String else { + return seal.reject(Error.missingSnodeVersion) + } + + if version >= "2.0.7" { + seal.fulfill(()) + } + else { + SNLog("Unsupported snode version: \(version).") + seal.reject(Error.unsupportedSnodeVersion(version)) + } + } + .catch2 { error in + seal.reject(error) } - }.catch2 { error in - seal.reject(error) - } } + return promise } /// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with `Error.insufficientSnodes` /// if not enough (reliable) snodes are available. - private static func getGuardSnodes(reusing reusableGuardSnodes: [Legacy.Snode]) -> Promise> { + private static func getGuardSnodes(reusing reusableGuardSnodes: [Snode]) -> Promise> { if guardSnodes.count >= targetGuardSnodeCount { - return Promise> { $0.fulfill(guardSnodes) } - } else { + return Promise> { $0.fulfill(guardSnodes) } + } + else { SNLog("Populating guard snode cache.") - var unusedSnodes = SnodeAPI.snodePool.subtracting(reusableGuardSnodes) // Sync on LokiAPI.workQueue + // Sync on LokiAPI.workQueue + var unusedSnodes = SnodeAPI.snodePool.subtracting(reusableGuardSnodes) let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count) - guard unusedSnodes.count >= (targetGuardSnodeCount - reusableGuardSnodeCount) else { return Promise(error: Error.insufficientSnodes) } - func getGuardSnode() -> Promise { - // randomElement() uses the system's default random generator, which is cryptographically secure - guard let candidate = unusedSnodes.randomElement() else { return Promise { $0.reject(Error.insufficientSnodes) } } + + guard unusedSnodes.count >= (targetGuardSnodeCount - reusableGuardSnodeCount) else { + return Promise(error: Error.insufficientSnodes) + } + + func getGuardSnode() -> Promise { + // randomElement() uses the system's default random generator, which + // is cryptographically secure + guard let candidate = unusedSnodes.randomElement() else { + return Promise { $0.reject(Error.insufficientSnodes) } + } + unusedSnodes.remove(candidate) // All used snodes should be unique SNLog("Testing guard snode: \(candidate).") + // Loop until a reliable guard snode is found return testSnode(candidate).map2 { candidate }.recover(on: DispatchQueue.main) { _ in withDelay(0.1, completionQueue: Threading.workQueue) { getGuardSnode() } } } - let promises = (0..<(targetGuardSnodeCount - reusableGuardSnodeCount)).map { _ in getGuardSnode() } + + let promises = (0..<(targetGuardSnodeCount - reusableGuardSnodeCount)).map { _ in + getGuardSnode() + } + return when(fulfilled: promises).map2 { guardSnodes in let guardSnodesAsSet = Set(guardSnodes + reusableGuardSnodes) OnionRequestAPI.guardSnodes = guardSnodesAsSet + return guardSnodesAsSet } } @@ -126,40 +130,50 @@ public enum OnionRequestAPI { /// Builds and returns `targetPathCount` paths. The returned promise errors out with `Error.insufficientSnodes` /// if not enough (reliable) snodes are available. @discardableResult - private static func buildPaths(reusing reusablePaths: [Path]) -> Promise<[Path]> { + private static func buildPaths(reusing reusablePaths: [[Snode]]) -> Promise<[[Snode]]> { if let existingBuildPathsPromise = buildPathsPromise { return existingBuildPathsPromise } SNLog("Building onion request paths.") DispatchQueue.main.async { NotificationCenter.default.post(name: .buildingPaths, object: nil) } let reusableGuardSnodes = reusablePaths.map { $0[0] } - let promise: Promise<[Path]> = getGuardSnodes(reusing: reusableGuardSnodes).map2 { guardSnodes -> [Path] in - var unusedSnodes = SnodeAPI.snodePool.subtracting(guardSnodes).subtracting(reusablePaths.flatMap { $0 }) - let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count) - let pathSnodeCount = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount) - guard unusedSnodes.count >= pathSnodeCount else { throw Error.insufficientSnodes } - // Don't test path snodes as this would reveal the user's IP to them - return guardSnodes.subtracting(reusableGuardSnodes).map { guardSnode in - let result = [ guardSnode ] + (0..<(pathSize - 1)).map { _ in - // randomElement() uses the system's default random generator, which is cryptographically secure - let pathSnode = unusedSnodes.randomElement()! // Safe because of the pathSnodeCount check above - unusedSnodes.remove(pathSnode) // All used snodes should be unique - return pathSnode + let promise: Promise<[[Snode]]> = getGuardSnodes(reusing: reusableGuardSnodes) + .map2 { guardSnodes -> [[Snode]] in + var unusedSnodes = SnodeAPI.snodePool + .subtracting(guardSnodes) + .subtracting(reusablePaths.flatMap { $0 }) + let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count) + let pathSnodeCount = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount) + + guard unusedSnodes.count >= pathSnodeCount else { throw Error.insufficientSnodes } + + // Don't test path snodes as this would reveal the user's IP to them + return guardSnodes.subtracting(reusableGuardSnodes).map { guardSnode in + let result = [ guardSnode ] + (0..<(pathSize - 1)).map { _ in + // randomElement() uses the system's default random generator, which is cryptographically secure + let pathSnode = unusedSnodes.randomElement()! // Safe because of the pathSnodeCount check above + unusedSnodes.remove(pathSnode) // All used snodes should be unique + return pathSnode + } + + SNLog("Built new onion request path: \(result.prettifiedDescription).") + return result } - SNLog("Built new onion request path: \(result.prettifiedDescription).") - return result } - }.map2 { paths in - OnionRequestAPI.paths = paths + reusablePaths - SNSnodeKitConfiguration.shared.storage.writeSync { transaction in - SNLog("Persisting onion request paths to database.") - SNSnodeKitConfiguration.shared.storage.setOnionRequestPaths(to: paths, using: transaction) + .map2 { paths in + OnionRequestAPI.paths = paths + reusablePaths + + GRDBStorage.shared.write { db in + SNLog("Persisting onion request paths to database.") + try? paths.save(db) + } + + DispatchQueue.main.async { + NotificationCenter.default.post(name: .pathsBuilt, object: nil) + } + return paths } - DispatchQueue.main.async { - NotificationCenter.default.post(name: .pathsBuilt, object: nil) - } - return paths - } + promise.done2 { _ in buildPathsPromise = nil } promise.catch2 { _ in buildPathsPromise = nil } buildPathsPromise = promise @@ -167,63 +181,68 @@ public enum OnionRequestAPI { } /// Returns a `Path` to be used for building an onion request. Builds new paths as needed. - private static func getPath(excluding snode: Legacy.Snode?) -> Promise { + private static func getPath(excluding snode: Snode?) -> Promise<[Snode]> { guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") } - var paths = OnionRequestAPI.paths - if paths.isEmpty { - paths = SNSnodeKitConfiguration.shared.storage.getOnionRequestPaths() - OnionRequestAPI.paths = paths - if !paths.isEmpty { - guardSnodes.formUnion([ paths[0][0] ]) - if paths.count >= 2 { - guardSnodes.formUnion([ paths[1][0] ]) - } + + let paths: [[Snode]] = OnionRequestAPI.paths + + if !paths.isEmpty { + guardSnodes.formUnion([ paths[0][0] ]) + + if paths.count >= 2 { + guardSnodes.formUnion([ paths[1][0] ]) } } + // randomElement() uses the system's default random generator, which is cryptographically secure if paths.count >= targetPathCount { - if let snode = snode { + if let snode: Snode = snode { return Promise { $0.fulfill(paths.filter { !$0.contains(snode) }.randomElement()!) } - } else { + } + else { return Promise { $0.fulfill(paths.randomElement()!) } } - } else if !paths.isEmpty { + } + else if !paths.isEmpty { if let snode = snode { if let path = paths.first(where: { !$0.contains(snode) }) { buildPaths(reusing: paths) // Re-build paths in the background return Promise { $0.fulfill(path) } - } else { + } + else { return buildPaths(reusing: paths).map2 { paths in return paths.filter { !$0.contains(snode) }.randomElement()! } } - } else { + } + else { buildPaths(reusing: paths) // Re-build paths in the background return Promise { $0.fulfill(paths.randomElement()!) } } - } else { + } + else { return buildPaths(reusing: []).map2 { paths in if let snode = snode { if let path = paths.filter({ !$0.contains(snode) }).randomElement() { return path - } else { - throw Error.insufficientSnodes } - } else { - return paths.randomElement()! + + throw Error.insufficientSnodes } + + return paths.randomElement()! } } } - private static func dropGuardSnode(_ snode: Legacy.Snode) { + private static func dropGuardSnode(_ snode: Snode) { #if DEBUG dispatchPrecondition(condition: .onQueue(Threading.workQueue)) #endif guardSnodes = guardSnodes.filter { $0 != snode } } - private static func drop(_ snode: Legacy.Snode) throws { + private static func drop(_ snode: Snode) throws { #if DEBUG dispatchPrecondition(condition: .onQueue(Threading.workQueue)) #endif @@ -244,13 +263,14 @@ public enum OnionRequestAPI { oldPaths.remove(at: pathIndex) let newPaths = oldPaths + [ path ] paths = newPaths - SNSnodeKitConfiguration.shared.storage.writeSync { transaction in + + GRDBStorage.shared.write { db in SNLog("Persisting onion request paths to database.") - SNSnodeKitConfiguration.shared.storage.setOnionRequestPaths(to: newPaths, using: transaction) + try? newPaths.save(db) } } - private static func drop(_ path: Path) { + private static func drop(_ path: [Snode]) { #if DEBUG dispatchPrecondition(condition: .onQueue(Threading.workQueue)) #endif @@ -259,56 +279,69 @@ public enum OnionRequestAPI { guard let pathIndex = paths.firstIndex(of: path) else { return } paths.remove(at: pathIndex) OnionRequestAPI.paths = paths - SNSnodeKitConfiguration.shared.storage.writeSync { transaction in - if !paths.isEmpty { - SNLog("Persisting onion request paths to database.") - SNSnodeKitConfiguration.shared.storage.setOnionRequestPaths(to: paths, using: transaction) - } else { + + GRDBStorage.shared.write { db in + guard !paths.isEmpty else { SNLog("Clearing onion request paths.") - SNSnodeKitConfiguration.shared.storage.setOnionRequestPaths(to: [], using: transaction) + try? SnodeSet.clearOnionRequestPaths(db) + return } + + SNLog("Persisting onion request paths to database.") + try? paths.save(db) } } /// Builds an onion around `payload` and returns the result. private static func buildOnion(around payload: JSON, targetedAt destination: Destination) -> Promise { - var guardSnode: Legacy.Snode! + var guardSnode: Snode! var targetSnodeSymmetricKey: Data! // Needed by invoke(_:on:with:) to decrypt the response sent back by the destination var encryptionResult: AESGCM.EncryptionResult! - var snodeToExclude: Legacy.Snode? + var snodeToExclude: Snode? + if case .snode(let snode) = destination { snodeToExclude = snode } - return getPath(excluding: snodeToExclude).then2 { path -> Promise in - guardSnode = path.first! - // Encrypt in reverse order, i.e. the destination first - return encrypt(payload, for: destination).then2 { r -> Promise in - targetSnodeSymmetricKey = r.symmetricKey - // Recursively encrypt the layers of the onion (again in reverse order) - encryptionResult = r - var path = path - var rhs = destination - func addLayer() -> Promise { - if path.isEmpty { - return Promise { $0.fulfill(encryptionResult) } - } else { - let lhs = Destination.snode(path.removeLast()) - return OnionRequestAPI.encryptHop(from: lhs, to: rhs, using: encryptionResult).then2 { r -> Promise in - encryptionResult = r - rhs = lhs - return addLayer() + + return getPath(excluding: snodeToExclude) + .then2 { path -> Promise in + guardSnode = path.first! + // Encrypt in reverse order, i.e. the destination first + return encrypt(payload, for: destination) + .then2 { r -> Promise in + targetSnodeSymmetricKey = r.symmetricKey + + // Recursively encrypt the layers of the onion (again in reverse order) + encryptionResult = r + var path = path + var rhs = destination + + func addLayer() -> Promise { + guard !path.isEmpty else { + return Promise { $0.fulfill(encryptionResult) } + } + + let lhs = Destination.snode(path.removeLast()) + return OnionRequestAPI + .encryptHop(from: lhs, to: rhs, using: encryptionResult) + .then2 { r -> Promise in + encryptionResult = r + rhs = lhs + return addLayer() + } } + + return addLayer() } - } - return addLayer() } - }.map2 { _ in (guardSnode, encryptionResult, targetSnodeSymmetricKey) } + .map2 { _ in (guardSnode, encryptionResult, targetSnodeSymmetricKey) } } // MARK: Public API /// Sends an onion request to `snode`. Builds new paths as needed. - public static func sendOnionRequest(to snode: Legacy.Snode, invoking method: Legacy.Snode.Method, with parameters: JSON, associatedWith publicKey: String? = nil) -> Promise { + public static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPI.Endpoint, with parameters: JSON, associatedWith publicKey: String? = nil) -> Promise { let payload: JSON = [ "method" : method.rawValue, "params" : parameters ] return sendOnionRequest(with: payload, to: Destination.snode(snode)).recover2 { error -> Promise in guard case OnionRequestAPI.Error.httpRequestFailedAtDestination(let statusCode, let json, _) = error else { throw error } + throw SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode, associatedWith: publicKey) ?? error } } @@ -365,7 +398,7 @@ public enum OnionRequestAPI { public static func sendOnionRequest(with payload: JSON, to destination: Destination) -> Promise { let (promise, seal) = Promise.pending() - var guardSnode: Legacy.Snode? + var guardSnode: Snode? Threading.workQueue.async { // Avoid race conditions on `guardSnodes` and `paths` buildOnion(around: payload, targetedAt: destination).done2 { intermediate in guardSnode = intermediate.guardSnode @@ -442,7 +475,7 @@ public enum OnionRequestAPI { let prefix = "Next node not found: " if let message = json?["result"] as? String, message.hasPrefix(prefix) { let ed25519PublicKey = message[message.index(message.startIndex, offsetBy: prefix.count)..= snodeFailureThreshold { diff --git a/SessionSnodeKit/SnodeAPI.swift b/SessionSnodeKit/SnodeAPI.swift index acd3a19c9..83c088905 100644 --- a/SessionSnodeKit/SnodeAPI.swift +++ b/SessionSnodeKit/SnodeAPI.swift @@ -1,6 +1,7 @@ import PromiseKit -import SessionUtilitiesKit import Sodium +import GRDB +import SessionUtilitiesKit @objc(SNSnodeAPI) public final class SnodeAPI : NSObject { @@ -8,12 +9,12 @@ public final class SnodeAPI : NSObject { private static var hasLoadedSnodePool = false private static var loadedSwarms: Set = [] - private static var getSnodePoolPromise: Promise>? + private static var getSnodePoolPromise: Promise>? /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - internal static var snodeFailureCount: [Legacy.Snode:UInt] = [:] + internal static var snodeFailureCount: [Snode: UInt] = [:] /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - internal static var snodePool: Set = [] + internal static var snodePool: Set = [] /// The offset between the user's clock and the Service Node's clock. Used in cases where the /// user's clock is incorrect. @@ -21,7 +22,7 @@ public final class SnodeAPI : NSObject { /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. public static var clockOffset: Int64 = 0 /// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions. - public static var swarmCache: [String:Set] = [:] + public static var swarmCache: [String: Set] = [:] // MARK: Settings private static let maxRetryCount: UInt = 8 @@ -30,35 +31,6 @@ public final class SnodeAPI : NSObject { private static let snodeFailureThreshold = 3 private static let targetSwarmSnodeCount = 2 private static let minSnodePoolCount = 12 - - // MARK: Error - public enum Error : LocalizedError { - case generic - case clockOutOfSync - case snodePoolUpdatingFailed - case inconsistentSnodePools - case noKeyPair - case signingFailed - // ONS - case decryptionFailed - case hashingFailed - case validationFailed - - public var errorDescription: String? { - switch self { - case .generic: return "An error occurred." - case .clockOutOfSync: return "Your clock is out of sync with the Service Node network. Please check that your device's clock is set to automatic time." - case .snodePoolUpdatingFailed: return "Failed to update the Service Node pool." - case .inconsistentSnodePools: return "Received inconsistent Service Node pool information from the Service Node network." - case .noKeyPair: return "Missing user key pair." - case .signingFailed: return "Couldn't sign message." - // ONS - case .decryptionFailed: return "Couldn't decrypt ONS name." - case .hashingFailed: return "Couldn't compute ONS name hash." - case .validationFailed: return "ONS name validation failed." - } - } - } // MARK: Type Aliases public typealias MessageListPromise = Promise<[JSON]> @@ -68,23 +40,30 @@ public final class SnodeAPI : NSObject { // MARK: Snode Pool Interaction private static func loadSnodePoolIfNeeded() { guard !hasLoadedSnodePool else { return } - snodePool = SNSnodeKitConfiguration.shared.storage.getSnodePool() + + GRDBStorage.shared.read { db in + snodePool = ((try? Snode.fetchSet(db)) ?? Set()) + } + hasLoadedSnodePool = true } - private static func setSnodePool(to newValue: Set, using transaction: Any? = nil) { + private static func setSnodePool(to newValue: Set, db: Database? = nil) { snodePool = newValue - let storage = SNSnodeKitConfiguration.shared.storage - if let transaction = transaction { - storage.setSnodePool(to: newValue, using: transaction) - } else { - storage.writeSync { transaction in - storage.setSnodePool(to: newValue, using: transaction) + + if let db: Database = db { + _ = try? Snode.deleteAll(db) + newValue.forEach { try? $0.save(db) } + } + else { + GRDBStorage.shared.write { db in + _ = try? Snode.deleteAll(db) + newValue.forEach { try? $0.save(db) } } } } - private static func dropSnodeFromSnodePool(_ snode: Legacy.Snode) { + private static func dropSnodeFromSnodePool(_ snode: Snode) { #if DEBUG dispatchPrecondition(condition: .onQueue(Threading.workQueue)) #endif @@ -95,6 +74,7 @@ public final class SnodeAPI : NSObject { @objc public static func clearSnodePool() { snodePool.removeAll() + Threading.workQueue.async { setSnodePool(to: []) } @@ -103,22 +83,27 @@ public final class SnodeAPI : NSObject { // MARK: Swarm Interaction private static func loadSwarmIfNeeded(for publicKey: String) { guard !loadedSwarms.contains(publicKey) else { return } - swarmCache[publicKey] = SNSnodeKitConfiguration.shared.storage.getSwarm(for: publicKey) + + GRDBStorage.shared.read { db in + swarmCache[publicKey] = ((try? Snode.fetchSet(db, publicKey: publicKey)) ?? []) + } + loadedSwarms.insert(publicKey) } - private static func setSwarm(to newValue: Set, for publicKey: String, persist: Bool = true) { + private static func setSwarm(to newValue: Set, for publicKey: String, persist: Bool = true) { #if DEBUG dispatchPrecondition(condition: .onQueue(Threading.workQueue)) #endif swarmCache[publicKey] = newValue guard persist else { return } - SNSnodeKitConfiguration.shared.storage.writeSync { transaction in - SNSnodeKitConfiguration.shared.storage.setSwarm(to: newValue, for: publicKey, using: transaction) + + GRDBStorage.shared.write { db in + try? newValue.save(db, key: publicKey) } } - public static func dropSnodeFromSwarmIfNeeded(_ snode: Legacy.Snode, publicKey: String) { + public static func dropSnodeFromSwarmIfNeeded(_ snode: Snode, publicKey: String) { #if DEBUG dispatchPrecondition(condition: .onQueue(Threading.workQueue)) #endif @@ -129,19 +114,21 @@ public final class SnodeAPI : NSObject { } // MARK: Internal API - internal static func invoke(_ method: Legacy.Snode.Method, on snode: Legacy.Snode, associatedWith publicKey: String? = nil, parameters: JSON) -> RawResponsePromise { + internal static func invoke(_ method: Endpoint, on snode: Snode, associatedWith publicKey: String? = nil, parameters: JSON) -> RawResponsePromise { if Features.useOnionRequests { return OnionRequestAPI.sendOnionRequest(to: snode, invoking: method, with: parameters, associatedWith: publicKey).map2 { $0 as Any } - } else { + } + else { let url = "\(snode.address):\(snode.port)/storage_rpc/v1" return HTTP.execute(.post, url, parameters: parameters).map2 { $0 as Any }.recover2 { error -> Promise in guard case HTTP.Error.httpRequestFailed(let statusCode, let json) = error else { throw error } + throw SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode, associatedWith: publicKey) ?? error } } } - private static func getNetworkTime(from snode: Legacy.Snode) -> Promise { + private static func getNetworkTime(from snode: Snode) -> Promise { return invoke(.getInfo, on: snode, parameters: [:]).map2 { rawResponse in guard let json = rawResponse as? JSON, let timestamp = json["timestamp"] as? UInt64 else { throw HTTP.Error.invalidJSON } @@ -149,140 +136,165 @@ public final class SnodeAPI : NSObject { } } - internal static func getRandomSnode() -> Promise { + internal static func getRandomSnode() -> Promise { // randomElement() uses the system's default random generator, which is cryptographically secure return getSnodePool().map2 { $0.randomElement()! } } - private static func getSnodePoolFromSeedNode() -> Promise> { + private static func getSnodePoolFromSeedNode() -> Promise> { let target = seedNodePool.randomElement()! let url = "\(target)/json_rpc" let parameters: JSON = [ - "method" : "get_n_service_nodes", - "params" : [ - "active_only" : true, - "limit" : 256, - "fields" : [ - "public_ip" : true, "storage_port" : true, "pubkey_ed25519" : true, "pubkey_x25519" : true + "method": "get_n_service_nodes", + "params": [ + "active_only": true, + "limit": 256, + "fields": [ + "public_ip": true, + "storage_port": true, + "pubkey_ed25519": true, + "pubkey_x25519": true ] ] ] SNLog("Populating snode pool using seed node: \(target).") - let (promise, seal) = Promise>.pending() + let (promise, seal) = Promise>.pending() + Threading.workQueue.async { attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) { - HTTP.execute(.post, url, parameters: parameters, useSeedNodeURLSession: true).map2 { json -> Set in - guard let intermediate = json["result"] as? JSON, let rawSnodes = intermediate["service_node_states"] as? [JSON] else { throw Error.snodePoolUpdatingFailed } - return Set(rawSnodes.compactMap { rawSnode in - guard let address = rawSnode["public_ip"] as? String, let port = rawSnode["storage_port"] as? Int, - let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else { - SNLog("Failed to parse snode from: \(rawSnode).") - return nil + HTTP.execute(.post, url, parameters: parameters, useSeedNodeURLSession: true) + .map2 { json -> Set in + guard + let intermediate = json["result"] as? JSON, + let rawSnodes = intermediate["service_node_states"] as? [JSON], + let snodeData: Data = try? JSONSerialization.data(withJSONObject: rawSnodes, options: []) + else { + throw Error.snodePoolUpdatingFailed } - return Legacy.Snode(address: "https://\(address)", port: UInt16(port), publicKeySet: Legacy.Snode.KeySet(ed25519Key: ed25519PublicKey, x25519Key: x25519PublicKey)) - }) - } - }.done2 { snodePool in + + return ((try? JSONDecoder().decode([Failable].self, from: snodeData)) ?? []) + .compactMap { $0.value } + .asSet() + } + } + .done2 { snodePool in SNLog("Got snode pool from seed node: \(target).") seal.fulfill(snodePool) - }.catch2 { error in + } + .catch2 { error in SNLog("Failed to contact seed node at: \(target).") seal.reject(error) } } + return promise } - private static func getSnodePoolFromSnode() -> Promise> { + private static func getSnodePoolFromSnode() -> Promise> { var snodePool = SnodeAPI.snodePool - var snodes: Set = [] + var snodes: Set = [] (0..<3).forEach { _ in - let snode = snodePool.randomElement()! + guard let snode = snodePool.randomElement() else { return } + snodePool.remove(snode) snodes.insert(snode) } - let snodePoolPromises: [Promise>] = snodes.map { snode in + + let snodePoolPromises: [Promise>] = snodes.map { snode in return attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) { // Don't specify a limit in the request. Service nodes return a shuffled // list of nodes so if we specify a limit the 3 responses we get might have // very little overlap. let parameters: JSON = [ - "endpoint" : "get_service_nodes", - "params" : [ - "active_only" : true, - "fields" : [ - "public_ip" : true, "storage_port" : true, "pubkey_ed25519" : true, "pubkey_x25519" : true + "endpoint": "get_service_nodes", + "params": [ + "active_only": true, + "fields": [ + "public_ip": true, + "storage_port": true, + "pubkey_ed25519": true, + "pubkey_x25519": true ] ] ] return invoke(.oxenDaemonRPCCall, on: snode, parameters: parameters).map2 { rawResponse in - guard let json = rawResponse as? JSON, let intermediate = json["result"] as? JSON, - let rawSnodes = intermediate["service_node_states"] as? [JSON] else { + guard + let json = rawResponse as? JSON, + let intermediate = json["result"] as? JSON, + let rawSnodes = intermediate["service_node_states"] as? [JSON], + let snodeData: Data = try? JSONSerialization.data(withJSONObject: rawSnodes, options: []) + else { throw Error.snodePoolUpdatingFailed } - return Set(rawSnodes.compactMap { rawSnode in - guard let address = rawSnode["public_ip"] as? String, let port = rawSnode["storage_port"] as? Int, - let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else { - SNLog("Failed to parse snode from: \(rawSnode).") - return nil - } - return Legacy.Snode(address: "https://\(address)", port: UInt16(port), publicKeySet: Legacy.Snode.KeySet(ed25519Key: ed25519PublicKey, x25519Key: x25519PublicKey)) - }) + + return ((try? JSONDecoder().decode([Failable].self, from: snodeData)) ?? []) + .compactMap { $0.value } + .asSet() } } } - let promise = when(fulfilled: snodePoolPromises).map2 { results -> Set in - var result: Set = results[0] - results.forEach { result = result.intersection($0) } - if result.count > 24 { // We want the snodes to agree on at least this many snodes - // Limit the snode pool size to 256 so that we don't go too long without - // refreshing it - return (result.count > 256) ? Set([Legacy.Snode](result)[0..<256]) : result - } else { - throw Error.inconsistentSnodePools - } + + let promise = when(fulfilled: snodePoolPromises).map2 { results -> Set in + let result: Set = results.reduce(Set()) { prev, next in prev.intersection(next) } + + // We want the snodes to agree on at least this many snodes + guard result.count > 24 else { throw Error.inconsistentSnodePools } + + // Limit the snode pool size to 256 so that we don't go too long without + // refreshing it + return Set(result.prefix(256)) } + return promise } // MARK: Public API + @objc(getSnodePool) public static func objc_getSnodePool() -> AnyPromise { AnyPromise.from(getSnodePool()) } - public static func getSnodePool() -> Promise> { + public static func getSnodePool() -> Promise> { loadSnodePoolIfNeeded() let now = Date() - let hasSnodePoolExpired = given(Storage.shared.getLastSnodePoolRefreshDate()) { now.timeIntervalSince($0) > 2 * 60 * 60 } ?? true + let hasSnodePoolExpired = given(GRDBStorage.shared[.lastSnodePoolRefreshDate]) { now.timeIntervalSince($0) > 2 * 60 * 60 } ?? true let snodePool = SnodeAPI.snodePool let hasInsufficientSnodes = (snodePool.count < minSnodePoolCount) + if hasInsufficientSnodes || hasSnodePoolExpired { if let getSnodePoolPromise = getSnodePoolPromise { return getSnodePoolPromise } - let promise: Promise> + + let promise: Promise> if snodePool.count < minSnodePoolCount { promise = getSnodePoolFromSeedNode() - } else { + } + else { promise = getSnodePoolFromSnode().recover2 { _ in getSnodePoolFromSeedNode() } } + getSnodePoolPromise = promise - promise.map2 { snodePool -> Set in - if snodePool.isEmpty { - throw Error.snodePoolUpdatingFailed - } else { - return snodePool - } + promise.map2 { snodePool -> Set in + guard !snodePool.isEmpty else { throw Error.snodePoolUpdatingFailed } + + return snodePool } - promise.then2 { snodePool -> Promise> in - let (promise, seal) = Promise>.pending() - SNSnodeKitConfiguration.shared.storage.write(with: { transaction in - Storage.shared.setLastSnodePoolRefreshDate(to: now, using: transaction) - setSnodePool(to: snodePool, using: transaction) - }, completion: { - seal.fulfill(snodePool) - }) + + promise.then2 { snodePool -> Promise> in + let (promise, seal) = Promise>.pending() + + GRDBStorage.shared.writeAsync( + updates: { db in + db[.lastSnodePoolRefreshDate] = now + setSnodePool(to: snodePool, db: db) + }, + completion: { _, _ in + seal.fulfill(snodePool) + } + ) + return promise } promise.done2 { _ in @@ -291,10 +303,11 @@ public final class SnodeAPI : NSObject { promise.catch2 { _ in getSnodePoolPromise = nil } + return promise - } else { - return Promise.value(snodePool) } + + return Promise.value(snodePool) } public static func getSessionID(for onsName: String) -> Promise { @@ -366,49 +379,55 @@ public final class SnodeAPI : NSObject { return promise } - public static func getTargetSnodes(for publicKey: String) -> Promise<[Legacy.Snode]> { + public static func getTargetSnodes(for publicKey: String) -> Promise<[Snode]> { // shuffled() uses the system's default random generator, which is cryptographically secure return getSwarm(for: publicKey).map2 { Array($0.shuffled().prefix(targetSwarmSnodeCount)) } } - public static func getSwarm(for publicKey: String) -> Promise> { + public static func getSwarm(for publicKey: String) -> Promise> { loadSwarmIfNeeded(for: publicKey) + if let cachedSwarm = swarmCache[publicKey], cachedSwarm.count >= minSwarmSnodeCount { - return Promise> { $0.fulfill(cachedSwarm) } - } else { - SNLog("Getting swarm for: \((publicKey == SNSnodeKitConfiguration.shared.storage.getUserPublicKey()) ? "self" : publicKey).") - let parameters: [String:Any] = [ "pubKey" : Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey ] - return getRandomSnode().then2 { snode in + return Promise> { $0.fulfill(cachedSwarm) } + } + + SNLog("Getting swarm for: \((publicKey == getUserHexEncodedPublicKey()) ? "self" : publicKey).") + let parameters: [String: Any] = [ + "pubKey": Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey + ] + + return getRandomSnode() + .then2 { snode in attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) { invoke(.getSwarm, on: snode, associatedWith: publicKey, parameters: parameters) } - }.map2 { rawSnodes in + } + .map2 { rawSnodes in let swarm = parseSnodes(from: rawSnodes) setSwarm(to: swarm, for: publicKey) return swarm } - } } - public static func getRawMessages(from snode: Legacy.Snode, associatedWith publicKey: String) -> RawResponsePromise { + public static func getRawMessages(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { let (promise, seal) = RawResponsePromise.pending() Threading.workQueue.async { - getMessagesInternal(from: snode, associatedWith: publicKey).done2 { seal.fulfill($0) }.catch2 { seal.reject($0) } + getMessagesInternal(from: snode, associatedWith: publicKey) + .done2 { seal.fulfill($0) } + .catch2 { seal.reject($0) } } return promise } - private static func getMessagesInternal(from snode: Legacy.Snode, associatedWith publicKey: String) -> RawResponsePromise { - let storage = SNSnodeKitConfiguration.shared.storage - + private static func getMessagesInternal(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise { // NOTE: All authentication logic is currently commented out, the reason being that we can't currently support // it yet for closed groups. The Storage Server requires an ed25519 key pair, but we don't have that for our // closed groups. // guard let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) } // Get last message hash - storage.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey) - let lastHash = storage.getLastMessageHash(for: snode, associatedWith: publicKey) ?? "" + SnodeReceivedMessageInfo.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey) + let lastHash = SnodeReceivedMessageInfo.fetchLastNotExpired(for: snode, associatedWith: publicKey)?.hash ?? "" // Construct signature // let timestamp = UInt64(Int64(NSDate.millisecondTimestamp()) + SnodeAPI.clockOffset) // let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString() @@ -427,17 +446,28 @@ public final class SnodeAPI : NSObject { public static func sendMessage(_ message: SnodeMessage) -> Promise> { let (promise, seal) = Promise>.pending() - let publicKey = Features.useTestnet ? message.recipient.removing05PrefixIfNeeded() : message.recipient + let publicKey = (Features.useTestnet ? + message.recipient.removing05PrefixIfNeeded() : + message.recipient + ) + Threading.workQueue.async { - getTargetSnodes(for: publicKey).map2 { targetSnodes in - let parameters = message.toJSON() - return Set(targetSnodes.map { targetSnode in - attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - invoke(.sendMessage, on: targetSnode, associatedWith: publicKey, parameters: parameters) - } - }) - }.done2 { seal.fulfill($0) }.catch2 { seal.reject($0) } + getTargetSnodes(for: publicKey) + .map2 { targetSnodes in + let parameters = message.toJSON() + + return targetSnodes + .map { targetSnode in + attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { + invoke(.sendMessage, on: targetSnode, associatedWith: publicKey, parameters: parameters) + } + } + .asSet() + } + .done2 { seal.fulfill($0) } + .catch2 { seal.reject($0) } } + return promise } @@ -446,75 +476,123 @@ public final class SnodeAPI : NSObject { AnyPromise.from(deleteMessage(publicKey: publicKey, serverHashes: serverHashes)) } - public static func deleteMessage(publicKey: String, serverHashes: [String]) -> Promise<[String:Bool]> { - let storage = SNSnodeKitConfiguration.shared.storage - guard let userX25519PublicKey = storage.getUserPublicKey(), - let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) } - let publicKey = Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey + public static func deleteMessage(publicKey: String, serverHashes: [String]) -> Promise<[String: Bool]> { + guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { + return Promise(error: Error.noKeyPair) + } + + let publicKey = (Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey) + let userX25519PublicKey: String = getUserHexEncodedPublicKey() + return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - getSwarm(for: publicKey).then2 { swarm -> Promise<[String:Bool]> in - let snode = swarm.randomElement()! - let verificationData = (Legacy.Snode.Method.deleteMessage.rawValue + serverHashes.joined(separator: "")).data(using: String.Encoding.utf8)! - guard let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) else { throw Error.signingFailed } - let parameters: JSON = [ - "pubkey" : userX25519PublicKey, - "pubkey_ed25519" : userED25519KeyPair.publicKey.toHexString(), - "messages": serverHashes, - "signature": signature.toBase64() - ] - return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - invoke(.deleteMessage, on: snode, associatedWith: publicKey, parameters: parameters).map2{ rawResponse -> [String:Bool] in - guard let json = rawResponse as? JSON, let swarm = json["swarm"] as? JSON else { throw HTTP.Error.invalidJSON } - var result: [String:Bool] = [:] - for (snodePublicKey, rawJSON) in swarm { - guard let json = rawJSON as? JSON else { throw HTTP.Error.invalidJSON } - let isFailed = json["failed"] as? Bool ?? false - if !isFailed { - guard let hashes = json["deleted"] as? [String], let signature = json["signature"] as? String else { throw HTTP.Error.invalidJSON } - // The signature format is ( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] ) - let verificationData = (userX25519PublicKey + serverHashes.joined(separator: "") + hashes.joined(separator: "")).data(using: String.Encoding.utf8)! - let isValid = sodium.sign.verify(message: Bytes(verificationData), publicKey: Bytes(Data(hex: snodePublicKey)), signature: Bytes(Data(base64Encoded: signature)!)) - result[snodePublicKey] = isValid - } else { - if let reason = json["reason"] as? String, let statusCode = json["code"] as? String { - SNLog("Couldn't delete data from: \(snodePublicKey) due to error: \(reason) (\(statusCode)).") - } else { - SNLog("Couldn't delete data from: \(snodePublicKey).") - } - result[snodePublicKey] = false + getSwarm(for: publicKey) + .then2 { swarm -> Promise<[String: Bool]> in + guard + let snode = swarm.randomElement(), + let verificationData = (Endpoint.deleteMessage.rawValue + serverHashes.joined()).data(using: String.Encoding.utf8), + let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) + else { + throw Error.signingFailed + } + + let parameters: JSON = [ + "pubkey" : userX25519PublicKey, + "pubkey_ed25519" : userED25519KeyPair.publicKey.toHexString(), + "messages": serverHashes, + "signature": signature.toBase64() + ] + + return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { + invoke(.deleteMessage, on: snode, associatedWith: publicKey, parameters: parameters) + .map2 { rawResponse -> [String: Bool] in + guard let json = rawResponse as? JSON, let swarm = json["swarm"] as? JSON else { + throw HTTP.Error.invalidJSON } + + var result: [String: Bool] = [:] + + for (snodePublicKey, rawJSON) in swarm { + guard let json = rawJSON as? JSON else { throw HTTP.Error.invalidJSON } + + let isFailed = (json["failed"] as? Bool ?? false) + + if !isFailed { + guard + let hashes = json["deleted"] as? [String], + let signature = json["signature"] as? String + else { + throw HTTP.Error.invalidJSON + } + + // The signature format is ( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] ) + let verificationData = [ + userX25519PublicKey, + serverHashes.joined(), + hashes.joined() + ] + .joined() + .data(using: String.Encoding.utf8)! + let isValid = sodium.sign.verify( + message: Bytes(verificationData), + publicKey: Bytes(Data(hex: snodePublicKey)), + signature: Bytes(Data(base64Encoded: signature)!) + ) + result[snodePublicKey] = isValid + } + else { + if let reason = json["reason"] as? String, let statusCode = json["code"] as? String { + SNLog("Couldn't delete data from: \(snodePublicKey) due to error: \(reason) (\(statusCode)).") + } else { + SNLog("Couldn't delete data from: \(snodePublicKey).") + } + result[snodePublicKey] = false + } + } + + return result } - return result } } - } } } /// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation. public static func clearAllData() -> Promise<[String:Bool]> { - let storage = SNSnodeKitConfiguration.shared.storage - guard let userX25519PublicKey = storage.getUserPublicKey(), - let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) } + guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else { + return Promise(error: Error.noKeyPair) + } + + let userX25519PublicKey: String = getUserHexEncodedPublicKey() + return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { getSwarm(for: userX25519PublicKey).then2 { swarm -> Promise<[String:Bool]> in let snode = swarm.randomElement()! return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { getNetworkTime(from: snode).then2 { timestamp -> Promise<[String:Bool]> in - let verificationData = (Legacy.Snode.Method.clearAllData.rawValue + String(timestamp)).data(using: String.Encoding.utf8)! - guard let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) else { throw Error.signingFailed } + let verificationData = (Endpoint.clearAllData.rawValue + String(timestamp)).data(using: String.Encoding.utf8)! + guard let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) else { + throw Error.signingFailed + } + let parameters: JSON = [ - "pubkey" : userX25519PublicKey, - "pubkey_ed25519" : userED25519KeyPair.publicKey.toHexString(), - "timestamp" : timestamp, - "signature" : signature.toBase64() + "pubkey": userX25519PublicKey, + "pubkey_ed25519": userED25519KeyPair.publicKey.toHexString(), + "timestamp": timestamp, + "signature": signature.toBase64() ] + return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) { - invoke(.clearAllData, on: snode, parameters: parameters).map2 { rawResponse -> [String:Bool] in - guard let json = rawResponse as? JSON, let swarm = json["swarm"] as? JSON else { throw HTTP.Error.invalidJSON } - var result: [String:Bool] = [:] + invoke(.clearAllData, on: snode, parameters: parameters).map2 { rawResponse -> [String: Bool] in + guard + let json = rawResponse as? JSON, + let swarm = json["swarm"] as? JSON + else { throw HTTP.Error.invalidJSON } + + var result: [String: Bool] = [:] + for (snodePublicKey, rawJSON) in swarm { guard let json = rawJSON as? JSON else { throw HTTP.Error.invalidJSON } + let isFailed = json["failed"] as? Bool ?? false if !isFailed { guard let hashes = json["deleted"] as? [String], let signature = json["signature"] as? String else { throw HTTP.Error.invalidJSON } @@ -531,6 +609,7 @@ public final class SnodeAPI : NSObject { result[snodePublicKey] = false } } + return result } } @@ -544,66 +623,73 @@ public final class SnodeAPI : NSObject { // The parsing utilities below use a best attempt approach to parsing; they warn for parsing failures but don't throw exceptions. - private static func parseSnodes(from rawResponse: Any) -> Set { + private static func parseSnodes(from rawResponse: Any) -> Set { guard let json = rawResponse as? JSON, let rawSnodes = json["snodes"] as? [JSON] else { SNLog("Failed to parse snodes from: \(rawResponse).") return [] } - return Set(rawSnodes.compactMap { rawSnode in - guard let address = rawSnode["ip"] as? String, let portAsString = rawSnode["port"] as? String, let port = UInt16(portAsString), - let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else { - SNLog("Failed to parse snode from: \(rawSnode).") - return nil - } - return Legacy.Snode(address: "https://\(address)", port: port, publicKeySet: Legacy.Snode.KeySet(ed25519Key: ed25519PublicKey, x25519Key: x25519PublicKey)) - }) - } - - public static func parseRawMessagesResponse(_ rawResponse: Any, from snode: Legacy.Snode, associatedWith publicKey: String) -> (messages: [JSON], lastRawMessage: JSON?) { - guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { return ([], nil) } - - return ( - removeDuplicates(from: rawMessages, associatedWith: publicKey), - rawMessages.last - ) - } - - public static func updateLastMessageHashValueIfPossible(for snode: Legacy.Snode, associatedWith publicKey: String, from lastRawMessage: JSON?) { - if let lastMessage = lastRawMessage, let lastHash = lastMessage["hash"] as? String, let expirationDate = lastMessage["expiration"] as? UInt64 { - SNSnodeKitConfiguration.shared.storage.writeSync { transaction in - SNSnodeKitConfiguration.shared.storage.setLastMessageHashInfo(for: snode, associatedWith: publicKey, - to: [ "hash" : lastHash, "expirationDate" : NSNumber(value: expirationDate) ], using: transaction) - } - } else if (lastRawMessage != nil) { - SNLog("Failed to update last message hash value from: \(String(describing: lastRawMessage)).") + + guard let snodeData: Data = try? JSONSerialization.data(withJSONObject: rawSnodes, options: []) else { + return [] } + + // FIXME: Hopefully at some point this different Snode structure will be deprecated and can be removed + if + let swarmSnodes: [SwarmSnode] = try? JSONDecoder().decode([Failable].self, from: snodeData).compactMap({ $0.value }), + !swarmSnodes.isEmpty + { + return swarmSnodes.map { $0.toSnode() }.asSet() + } + + return ((try? JSONDecoder().decode([Failable].self, from: snodeData)) ?? []) + .compactMap { $0.value } + .asSet() + } + + public static func parseRawMessagesResponse(_ rawResponse: Any, from snode: Snode, associatedWith publicKey: String) -> [SnodeReceivedMessage] { + guard let json = rawResponse as? JSON, let rawMessages = json["messages"] as? [JSON] else { + return [] + } + + return removeDuplicates(from: rawMessages, associatedWith: publicKey) + .compactMap { rawMessage -> SnodeReceivedMessage? in + return SnodeReceivedMessage( + snode: snode, + publicKey: publicKey, + rawMessage: rawMessage + ) + } } private static func removeDuplicates(from rawMessages: [JSON], associatedWith publicKey: String) -> [JSON] { - let oldReceivedMessages = SNSnodeKitConfiguration.shared.storage.getReceivedMessages(for: publicKey) - var newReceivedMessages = oldReceivedMessages - let result = rawMessages.filter { rawMessage in - guard let hash = rawMessage["hash"] as? String else { - SNLog("Missing hash value for message: \(rawMessage).") - return false - } - let isDuplicate = newReceivedMessages.contains(hash) - newReceivedMessages.insert(hash) - return !isDuplicate + var oldReceivedMessages: [SnodeReceivedMessageInfo] = [] + + GRDBStorage.shared.read { db in + oldReceivedMessages = oldReceivedMessages.appending( + contentsOf: try? SnodeReceivedMessageInfo + .filter(SnodeReceivedMessageInfo.Columns.key.like("%\(publicKey)")) + .fetchAll(db) + ) } - // Avoid the sync write transaction if possible - if oldReceivedMessages != newReceivedMessages { - SNSnodeKitConfiguration.shared.storage.writeSync { transaction in - SNSnodeKitConfiguration.shared.storage.setReceivedMessages(to: newReceivedMessages, for: publicKey, using: transaction) + + let oldMessageHashes: Set = oldReceivedMessages.map { $0.hash }.asSet() + + return rawMessages + .compactMap { rawMessage -> JSON? in + guard let hash: String = rawMessage["hash"] as? String else { + SNLog("Missing hash value for message: \(rawMessage).") + return nil + } + guard !oldMessageHashes.contains(hash) else { return nil } + + return rawMessage } - } - return result } // MARK: Error Handling /// - Note: Should only be invoked from `Threading.workQueue` to avoid race conditions. @discardableResult - internal static func handleError(withStatusCode statusCode: UInt, json: JSON?, forSnode snode: Legacy.Snode, associatedWith publicKey: String? = nil) -> Error? { + internal static func handleError(withStatusCode statusCode: UInt, json: JSON?, forSnode snode: Snode, associatedWith publicKey: String? = nil) -> Error? { #if DEBUG dispatchPrecondition(condition: .onQueue(Threading.workQueue)) #endif diff --git a/SessionSnodeKit/Storage+OnionRequests.swift b/SessionSnodeKit/Storage+OnionRequests.swift deleted file mode 100644 index f99b3c6ff..000000000 --- a/SessionSnodeKit/Storage+OnionRequests.swift +++ /dev/null @@ -1,49 +0,0 @@ -import SessionUtilitiesKit - -extension Storage { - - private 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? Legacy.Snode, - let path0Snode1 = transaction.object(forKey: "0-1", inCollection: collection) as? Legacy.Snode, - let path0Snode2 = transaction.object(forKey: "0-2", inCollection: collection) as? Legacy.Snode { - result.append([ path0Snode0, path0Snode1, path0Snode2 ]) - if - let path1Snode0 = transaction.object(forKey: "1-0", inCollection: collection) as? Legacy.Snode, - let path1Snode1 = transaction.object(forKey: "1-1", inCollection: collection) as? Legacy.Snode, - let path1Snode2 = transaction.object(forKey: "1-2", inCollection: collection) as? Legacy.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) - } -} diff --git a/SessionSnodeKit/Storage+SnodeAPI.swift b/SessionSnodeKit/Storage+SnodeAPI.swift deleted file mode 100644 index 71df7c0a2..000000000 --- a/SessionSnodeKit/Storage+SnodeAPI.swift +++ /dev/null @@ -1,140 +0,0 @@ -import SessionUtilitiesKit - -extension Storage { - - // MARK: - Snode Pool - - private static let snodePoolCollection = "LokiSnodePoolCollection" - private static let lastSnodePoolRefreshDateCollection = "LokiLastSnodePoolRefreshDateCollection" - - public func getSnodePool() -> Set { - var result: Set = [] - Storage.read { transaction in - transaction.enumerateKeysAndObjects(inCollection: Storage.snodePoolCollection) { _, object, _ in - guard let snode = object as? Legacy.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) - } - } - - public func clearSnodePool(in transaction: Any) { - (transaction as! YapDatabaseReadWriteTransaction).removeAllObjects(inCollection: Storage.snodePoolCollection) - } - - public func getLastSnodePoolRefreshDate() -> Date? { - var result: Date? - Storage.read { transaction in - result = transaction.object(forKey: "lastSnodePoolRefreshDate", inCollection: Storage.lastSnodePoolRefreshDateCollection) as? Date - } - return result - } - - public func setLastSnodePoolRefreshDate(to date: Date, using transaction: Any) { - (transaction as! YapDatabaseReadWriteTransaction).setObject(date, forKey: "lastSnodePoolRefreshDate", inCollection: Storage.lastSnodePoolRefreshDateCollection) - } - - - - // MARK: - Swarm - - private static func getSwarmCollection(for publicKey: String) -> String { - return "LokiSwarmCollection-\(publicKey)" - } - - 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? Legacy.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 tmp = getSnodePool() - let collection = Storage.getSwarmCollection(for: publicKey) - swarm.forEach { snode in - (transaction as! YapDatabaseReadWriteTransaction).setObject(snode, forKey: snode.description, inCollection: collection) - } - } - - public 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" - - public func getLastMessageHashInfo(for snode: Legacy.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: Legacy.Snode, associatedWith publicKey: String) -> String? { - return getLastMessageHashInfo(for: snode, associatedWith: publicKey)?["hash"] as? String - } - - public func setLastMessageHashInfo(for snode: Legacy.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: Legacy.Snode, associatedWith publicKey: String) { - 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 { - Storage.writeSync { transaction in - self.removeLastMessageHashInfo(for: snode, associatedWith: publicKey, using: transaction) - } - } - } - - public func removeLastMessageHashInfo(for snode: Legacy.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/SessionSnodeKit/Storage.swift b/SessionSnodeKit/Storage.swift deleted file mode 100644 index 7ea5e0d3d..000000000 --- a/SessionSnodeKit/Storage.swift +++ /dev/null @@ -1,28 +0,0 @@ -import SessionUtilitiesKit -import PromiseKit -import Sodium - -public protocol SessionSnodeKitStorageProtocol { - - @discardableResult - func write(with block: @escaping (Any) -> Void) -> Promise - @discardableResult - func write(with block: @escaping (Any) -> Void, completion: @escaping () -> Void) -> Promise - func writeSync(with block: @escaping (Any) -> Void) - - func getUserPublicKey() -> String? - func getUserED25519KeyPair() -> Box.KeyPair? - func getOnionRequestPaths() -> [OnionRequestAPI.Path] - func setOnionRequestPaths(to paths: [OnionRequestAPI.Path], using transaction: Any) - func getSnodePool() -> Set - func setSnodePool(to snodePool: Set, using transaction: Any) - func getLastSnodePoolRefreshDate() -> Date? - func setLastSnodePoolRefreshDate(to date: Date, using transaction: Any) - func getSwarm(for publicKey: String) -> Set - func setSwarm(to swarm: Set, for publicKey: String, using transaction: Any) - func getLastMessageHash(for snode: Legacy.Snode, associatedWith publicKey: String) -> String? - func setLastMessageHashInfo(for snode: Legacy.Snode, associatedWith publicKey: String, to lastMessageHashInfo: JSON, using transaction: Any) - func pruneLastMessageHashInfoIfExpired(for snode: Legacy.Snode, associatedWith publicKey: String) - func getReceivedMessages(for publicKey: String) -> Set - func setReceivedMessages(to receivedMessages: Set, for publicKey: String, using transaction: Any) -} diff --git a/SessionSnodeKit/Types/SSKDestination.swift b/SessionSnodeKit/Types/SSKDestination.swift new file mode 100644 index 000000000..f879c1034 --- /dev/null +++ b/SessionSnodeKit/Types/SSKDestination.swift @@ -0,0 +1,17 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension OnionRequestAPI { + public enum Destination: CustomStringConvertible { + case snode(Snode) + case server(host: String, target: String, x25519PublicKey: String, scheme: String?, port: UInt16?) + + public var description: String { + switch self { + case .snode(let snode): return "Service node \(snode.ip):\(snode.port)" + case .server(let host, _, _, _, _): return host + } + } + } +} diff --git a/SessionSnodeKit/Types/SSKEndpoint.swift b/SessionSnodeKit/Types/SSKEndpoint.swift new file mode 100644 index 000000000..26dcc0c01 --- /dev/null +++ b/SessionSnodeKit/Types/SSKEndpoint.swift @@ -0,0 +1,15 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +extension SnodeAPI { + public enum Endpoint: String { + case getSwarm = "get_snodes_for_pubkey" + case getMessages = "retrieve" + case sendMessage = "store" + case deleteMessage = "delete" + case oxenDaemonRPCCall = "oxend_request" + case getInfo = "info" + case clearAllData = "delete_all" + } +} diff --git a/SessionSnodeKit/Types/SSKError.swift b/SessionSnodeKit/Types/SSKError.swift new file mode 100644 index 000000000..7fdd38a5f --- /dev/null +++ b/SessionSnodeKit/Types/SSKError.swift @@ -0,0 +1,63 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import SessionUtilitiesKit + +extension OnionRequestAPI { + public enum Error: LocalizedError { + case httpRequestFailedAtDestination(statusCode: UInt, json: JSON, destination: Destination) + case insufficientSnodes + case invalidURL + case missingSnodeVersion + case snodePublicKeySetMissing + case unsupportedSnodeVersion(String) + + public var errorDescription: String? { + switch self { + case .httpRequestFailedAtDestination(let statusCode, _, let destination): + if statusCode == 429 { return "Rate limited." } + + return "HTTP request failed at destination (\(destination)) with status code: \(statusCode)." + + case .insufficientSnodes: return "Couldn't find enough Service Nodes to build a path." + case .invalidURL: return "Invalid URL" + case .missingSnodeVersion: return "Missing Service Node version." + case .snodePublicKeySetMissing: return "Missing Service Node public key set." + case .unsupportedSnodeVersion(let version): return "Unsupported Service Node version: \(version)." + } + } + } +} + +extension SnodeAPI { + public enum Error: LocalizedError { + case generic + case clockOutOfSync + case snodePoolUpdatingFailed + case inconsistentSnodePools + case noKeyPair + case signingFailed + case invalidIP + + // ONS + case decryptionFailed + case hashingFailed + case validationFailed + + public var errorDescription: String? { + switch self { + case .generic: return "An error occurred." + case .clockOutOfSync: return "Your clock is out of sync with the Service Node network. Please check that your device's clock is set to automatic time." + case .snodePoolUpdatingFailed: return "Failed to update the Service Node pool." + case .inconsistentSnodePools: return "Received inconsistent Service Node pool information from the Service Node network." + case .noKeyPair: return "Missing user key pair." + case .signingFailed: return "Couldn't sign message." + case .invalidIP: return "Invalid IP." + // ONS + case .decryptionFailed: return "Couldn't decrypt ONS name." + case .hashingFailed: return "Couldn't compute ONS name hash." + case .validationFailed: return "ONS name validation failed." + } + } + } +} diff --git a/SessionUtilitiesKit/Configuration.swift b/SessionUtilitiesKit/Configuration.swift index ca72ad9df..c1b19b512 100644 --- a/SessionUtilitiesKit/Configuration.swift +++ b/SessionUtilitiesKit/Configuration.swift @@ -13,6 +13,17 @@ public final class SNUtilitiesKitConfiguration : NSObject { } public enum SNUtilitiesKit { // Just to make the external API nice + public static func migrations() -> TargetMigrations { + return TargetMigrations( + identifier: .utilitiesKit, + migrations: [ + [ + _001_InitialSetupMigration.self, + _002_YDBToGRDBMigration.self + ] + ] + ) + } public static func configure(owsPrimaryStorage: OWSPrimaryStorageProtocol, maxFileSize: UInt) { SNUtilitiesKitConfiguration.shared = SNUtilitiesKitConfiguration(owsPrimaryStorage: owsPrimaryStorage, maxFileSize: maxFileSize) diff --git a/SessionUtilitiesKit/Database/GRDBStorage.swift b/SessionUtilitiesKit/Database/GRDBStorage.swift index a628cb220..9aea43d52 100644 --- a/SessionUtilitiesKit/Database/GRDBStorage.swift +++ b/SessionUtilitiesKit/Database/GRDBStorage.swift @@ -210,15 +210,15 @@ public final class GRDBStorage { // MARK: - Functions - public func write(updates: (Database) throws -> T) throws -> T { - return try dbPool.write(updates) + @discardableResult public func write(updates: (Database) throws -> T?) -> T? { + return try? dbPool.write(updates) } public func writeAsync(updates: @escaping (Database) throws -> T, completion: @escaping (Database, Result) -> Void) { dbPool.asyncWrite(updates, completion: completion) } - public func read(_ value: (Database) throws -> T) throws -> T { - return try dbPool.read(value) + @discardableResult public func read(_ value: (Database) throws -> T?) -> T? { + return try? dbPool.read(value) } } diff --git a/SessionUtilitiesKit/Database/LegacyDatabase/SUKLegacyModels.swift b/SessionUtilitiesKit/Database/LegacyDatabase/SUKLegacyModels.swift new file mode 100644 index 000000000..d355dd8b4 --- /dev/null +++ b/SessionUtilitiesKit/Database/LegacyDatabase/SUKLegacyModels.swift @@ -0,0 +1,25 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Curve25519Kit + +public enum Legacy { + // MARK: - Collections and Keys + + internal static let userAccountRegisteredNumberKey = "TSStorageRegisteredNumberKey" + internal static let userAccountCollection = "TSStorageUserAccountCollection" + + internal static let identityKeyStoreSeedKey = "LKLokiSeed" + internal static let identityKeyStoreEd25519SecretKey = "LKED25519SecretKey" + internal static let identityKeyStoreEd25519PublicKey = "LKED25519PublicKey" + internal static let identityKeyStoreIdentityKey = "TSStorageManagerIdentityKeyStoreIdentityKey" + internal static let identityKeyStoreCollection = "TSStorageManagerIdentityKeyStoreCollection" +} + +// MARK: - Legacy Extensions + +internal extension YapDatabaseReadTransaction { + func keyPair(forKey key: String, in collection: String) -> ECKeyPair? { + return (self.object(forKey: key, inCollection: collection) as? ECKeyPair) + } +} diff --git a/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift b/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift new file mode 100644 index 000000000..b67b006d9 --- /dev/null +++ b/SessionUtilitiesKit/Database/Migrations/_001_InitialSetupMigration.swift @@ -0,0 +1,26 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB + +enum _001_InitialSetupMigration: Migration { + static let identifier: String = "initialSetup" + + static func migrate(_ db: Database) throws { + try db.create(table: Identity.self) { t in + t.column(.variant, .text) + .notNull() + .unique() + .primaryKey() + t.column(.data, .blob).notNull() + } + + try db.create(table: Setting.self) { t in + t.column(.key, .text) + .notNull() + .unique() + .primaryKey() + t.column(.value, .blob).notNull() + } + } +} diff --git a/SessionUtilitiesKit/Database/Migrations/_002_YDBToGRDBMigration.swift b/SessionUtilitiesKit/Database/Migrations/_002_YDBToGRDBMigration.swift new file mode 100644 index 000000000..0c53f6fcc --- /dev/null +++ b/SessionUtilitiesKit/Database/Migrations/_002_YDBToGRDBMigration.swift @@ -0,0 +1,89 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import Curve25519Kit + +enum _002_YDBToGRDBMigration: Migration { + static let identifier: String = "YDBToGRDBMigration" + + static func migrate(_ db: Database) throws { + // MARK: - Identity keys + + // Note: Want to exclude the Snode's we already added from the 'onionRequestPathResult' + var registeredNumber: String? + var seedHexString: String? + var userEd25519SecretKeyHexString: String? + var userEd25519PublicKeyHexString: String? + var userX25519KeyPair: ECKeyPair? + + Storage.read { transaction in + registeredNumber = transaction.object( + forKey: Legacy.userAccountRegisteredNumberKey, + inCollection: Legacy.userAccountCollection + ) as? String + + // Note: The 'seed', 'ed25519SecretKey' and 'ed25519PublicKey' were + // all previously stored as hex strings, so we need to convert them + // to data before we store them in the new database + seedHexString = transaction.object( + forKey: Legacy.identityKeyStoreSeedKey, + inCollection: Legacy.identityKeyStoreCollection + ) as? String + + userEd25519SecretKeyHexString = transaction.object( + forKey: Legacy.identityKeyStoreEd25519SecretKey, + inCollection: Legacy.identityKeyStoreCollection + ) as? String + + userEd25519PublicKeyHexString = transaction.object( + forKey: Legacy.identityKeyStoreEd25519PublicKey, + inCollection: Legacy.identityKeyStoreCollection + ) as? String + + userX25519KeyPair = transaction.keyPair( + forKey: Legacy.identityKeyStoreIdentityKey, + in: Legacy.identityKeyStoreCollection + ) + } + + // No need to continue if the user isn't registered + if registeredNumber == nil { return } + + // If the user is registered then it's all-or-nothing for these values + guard + let seedHexString: String = seedHexString, + let userEd25519SecretKeyHexString: String = userEd25519SecretKeyHexString, + let userEd25519PublicKeyHexString: String = userEd25519PublicKeyHexString, + let userX25519KeyPair: ECKeyPair = userX25519KeyPair + else { + throw GRDBStorageError.migrationFailed + } + + // Insert the data into GRDB + try Identity( + variant: .seed, + data: Data(hex: seedHexString) + ).insert(db) + + try Identity( + variant: .ed25519SecretKey, + data: Data(hex: userEd25519SecretKeyHexString) + ).insert(db) + + try Identity( + variant: .ed25519PublicKey, + data: Data(hex: userEd25519PublicKeyHexString) + ).insert(db) + + try Identity( + variant: .x25519PrivateKey, + data: userX25519KeyPair.privateKey + ).insert(db) + + try Identity( + variant: .x25519PublicKey, + data: userX25519KeyPair.publicKey + ).insert(db) + } +} diff --git a/SessionUtilitiesKit/Database/Models/Identity.swift b/SessionUtilitiesKit/Database/Models/Identity.swift new file mode 100644 index 000000000..3e55ee0a3 --- /dev/null +++ b/SessionUtilitiesKit/Database/Models/Identity.swift @@ -0,0 +1,129 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB +import Sodium +import Curve25519Kit +import CryptoSwift + +public struct Identity: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { + public static var databaseTableName: String { "identity" } + + public typealias Columns = CodingKeys + public enum CodingKeys: String, CodingKey, ColumnExpression { + case variant + case data + } + + public enum Variant: String, Codable, DatabaseValueConvertible { + case seed + case ed25519SecretKey + case ed25519PublicKey + case x25519PrivateKey + case x25519PublicKey + } + + public var id: Variant { variant } + + let variant: Variant + let data: Data +} + +// MARK: - Convenience + +extension ECKeyPair { + func toData() -> Data { + var targetValue: ECKeyPair = self + + return Data(bytes: &targetValue, count: MemoryLayout.size(ofValue: targetValue)) + } +} + +// MARK: - User Identity + +public extension Identity { + static func generate(from seed: Data) throws -> (ed25519KeyPair: Sign.KeyPair, x25519KeyPair: ECKeyPair) { + assert(seed.count == 16) + let padding = Data(repeating: 0, count: 16) + + guard + let ed25519KeyPair = Sodium().sign.keyPair(seed: (seed + padding).bytes), + let x25519PublicKey = Sodium().sign.toX25519(ed25519PublicKey: ed25519KeyPair.publicKey), + let x25519SecretKey = Sodium().sign.toX25519(ed25519SecretKey: ed25519KeyPair.secretKey) + else { + throw GeneralError.keyGenerationFailed + } + + let x25519KeyPair = try ECKeyPair(publicKeyData: Data(x25519PublicKey), privateKeyData: Data(x25519SecretKey)) + + return (ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair) + } + + static func store(seed: Data, ed25519KeyPair: Sign.KeyPair, x25519KeyPair: ECKeyPair) { + GRDBStorage.shared.write { db in + try Identity(variant: .seed, data: seed).save(db) + try Identity(variant: .ed25519SecretKey, data: Data(ed25519KeyPair.secretKey)).save(db) + try Identity(variant: .ed25519PublicKey, data: Data(ed25519KeyPair.publicKey)).save(db) + try Identity(variant: .x25519PrivateKey, data: x25519KeyPair.privateKey).save(db) + try Identity(variant: .x25519PublicKey, data: x25519KeyPair.publicKey).save(db) + } + } + + static func fetchUserKeyPair() -> ECKeyPair? { + return GRDBStorage.shared.read { db -> ECKeyPair? in + guard + let publicKey: Identity = try? Identity.fetchOne(db, id: .x25519PublicKey), + let privateKey: Identity = try? Identity.fetchOne(db, id: .x25519PrivateKey) + else { + return nil + } + + return try? ECKeyPair( + publicKeyData: publicKey.data, + privateKeyData: privateKey.data + ) + } + } + + static func fetchUserEd25519KeyPair() -> Box.KeyPair? { + return GRDBStorage.shared.read { db -> Box.KeyPair? in + guard + let publicKey: Identity = try? Identity.fetchOne(db, id: .ed25519PublicKey), + let secretKey: Identity = try? Identity.fetchOne(db, id: .ed25519SecretKey) + else { + return nil + } + + return Box.KeyPair( + publicKey: publicKey.data.bytes, + secretKey: secretKey.data.bytes + ) + } + } + + static func fetchHexEncodedSeed() -> String? { + return GRDBStorage.shared.read { db in + guard let value: Identity = try? Identity.fetchOne(db, id: .seed) else { + return nil + } + + return value.data.toHexString() + } + } + + // TODO: Should this actually clear all identity values??? + static func clearUserKeyPair() { + GRDBStorage.shared.write { db in + try Identity.deleteOne(db, id: .x25519PublicKey) + try Identity.deleteOne(db, id: .x25519PrivateKey) + } + } +} + +@objc(SUKIdentity) +public class objc_Identity: NSObject { + @objc(clearUserKeyPair) + public static func objc_clearUserKeyPair() { + Identity.clearUserKeyPair() + } +} diff --git a/SessionUtilitiesKit/Database/Models/Setting.swift b/SessionUtilitiesKit/Database/Models/Setting.swift new file mode 100644 index 000000000..a0e5295c1 --- /dev/null +++ b/SessionUtilitiesKit/Database/Models/Setting.swift @@ -0,0 +1,149 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import GRDB + +// MARK: - Setting + +public struct Setting: Codable, Identifiable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible { + public static var databaseTableName: String { "settings" } + + public typealias Columns = CodingKeys + public enum CodingKeys: String, CodingKey, ColumnExpression { + case key + case value + } + + public var id: String { key } + + let key: String + let value: Data +} + +extension Setting { + fileprivate init?(key: String, value: T?) { + guard let value: T = value else { return nil } + + var targetValue: T = value + + self.key = key + self.value = Data(bytes: &targetValue, count: MemoryLayout.size(ofValue: targetValue)) + } + + fileprivate func value(as type: T.Type) -> T { + return value.withUnsafeBytes { $0.load(as: T.self) } + } +} + +// MARK: - Keys + +public extension Setting { + struct BoolKey: RawRepresentable, ExpressibleByStringLiteral { + public let rawValue: String + + public init(_ rawValue: String) { self.rawValue = rawValue } + public init?(rawValue: String) { self.rawValue = rawValue } + public init(stringLiteral value: String) { self.init(value) } + public init(unicodeScalarLiteral value: String) { self.init(value) } + public init(extendedGraphemeClusterLiteral value: String) { self.init(value) } + } + + struct DateKey: RawRepresentable, ExpressibleByStringLiteral { + public let rawValue: String + + public init(_ rawValue: String) { self.rawValue = rawValue } + public init?(rawValue: String) { self.rawValue = rawValue } + public init(stringLiteral value: String) { self.init(value) } + public init(unicodeScalarLiteral value: String) { self.init(value) } + public init(extendedGraphemeClusterLiteral value: String) { self.init(value) } + } + + struct DoubleKey: RawRepresentable, ExpressibleByStringLiteral { + public let rawValue: String + + public init(_ rawValue: String) { self.rawValue = rawValue } + public init?(rawValue: String) { self.rawValue = rawValue } + public init(stringLiteral value: String) { self.init(value) } + public init(unicodeScalarLiteral value: String) { self.init(value) } + public init(extendedGraphemeClusterLiteral value: String) { self.init(value) } + } + + struct IntKey: RawRepresentable, ExpressibleByStringLiteral { + public let rawValue: String + + public init(_ rawValue: String) { self.rawValue = rawValue } + public init?(rawValue: String) { self.rawValue = rawValue } + public init(stringLiteral value: String) { self.init(value) } + public init(unicodeScalarLiteral value: String) { self.init(value) } + public init(extendedGraphemeClusterLiteral value: String) { self.init(value) } + } + + struct StringKey: RawRepresentable, ExpressibleByStringLiteral { + public let rawValue: String + + public init(_ rawValue: String) { self.rawValue = rawValue } + public init?(rawValue: String) { self.rawValue = rawValue } + public init(stringLiteral value: String) { self.init(value) } + public init(unicodeScalarLiteral value: String) { self.init(value) } + public init(extendedGraphemeClusterLiteral value: String) { self.init(value) } + } +} + +// MARK: - Database Access + +public extension GRDBStorage { + subscript(key: Setting.BoolKey) -> Bool? { return read { db in db[key] } } + subscript(key: Setting.DoubleKey) -> Double? { return read { db in db[key] } } + subscript(key: Setting.IntKey) -> Int? { return read { db in db[key] } } + subscript(key: Setting.StringKey) -> String? { return read { db in db[key] } } + subscript(key: Setting.DateKey) -> Date? { return read { db in db[key] } } +} + +public extension Database { + private subscript(key: String) -> Setting? { + get { try? Setting.filter(id: key).fetchOne(self) } + set { + guard let newValue: Setting = newValue else { + _ = try? Setting.filter(id: key).deleteAll(self) + return + } + + try? newValue.save(self) + } + } + + subscript(key: Setting.BoolKey) -> Bool? { + get { self[key.rawValue]?.value(as: Bool.self) } + set { self[key.rawValue] = Setting(key: key.rawValue, value: newValue) } + } + + subscript(key: Setting.DoubleKey) -> Double? { + get { self[key.rawValue]?.value(as: Double.self) } + set { self[key.rawValue] = Setting(key: key.rawValue, value: newValue) } + } + + subscript(key: Setting.IntKey) -> Int? { + get { self[key.rawValue]?.value(as: Int.self) } + set { self[key.rawValue] = Setting(key: key.rawValue, value: newValue) } + } + + subscript(key: Setting.StringKey) -> String? { + get { self[key.rawValue]?.value(as: String.self) } + set { self[key.rawValue] = Setting(key: key.rawValue, value: newValue) } + } + + /// Value will be stored as a timestamp in seconds since 1970 + subscript(key: Setting.DateKey) -> Date? { + get { + let timestamp: TimeInterval? = self[key.rawValue]?.value(as: TimeInterval.self) + + return timestamp.map { Date(timeIntervalSince1970: $0) } + } + set { + self[key.rawValue] = Setting( + key: key.rawValue, + value: newValue.map { $0.timeIntervalSince1970 } + ) + } + } +} diff --git a/SessionUtilitiesKit/Database/Types/SettingType.swift b/SessionUtilitiesKit/Database/Types/SettingType.swift deleted file mode 100644 index 0a8ab0dac..000000000 --- a/SessionUtilitiesKit/Database/Types/SettingType.swift +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. - -import Foundation diff --git a/SessionUtilitiesKit/Database/Types/TargetMigrations.swift b/SessionUtilitiesKit/Database/Types/TargetMigrations.swift index 83cb310a5..4718d5714 100644 --- a/SessionUtilitiesKit/Database/Types/TargetMigrations.swift +++ b/SessionUtilitiesKit/Database/Types/TargetMigrations.swift @@ -16,6 +16,7 @@ public struct TargetMigrations: Comparable { public enum Identifier: String, CaseIterable, Comparable { // WARNING: The string version of these cases are used as migration identifiers so // changing them will result in the migrations running again + case utilitiesKit case snodeKit case messagingKit diff --git a/SessionUtilitiesKit/Database/Types/TypedTableDefinition.swift b/SessionUtilitiesKit/Database/Types/TypedTableDefinition.swift index 8a5b86c92..db2157576 100644 --- a/SessionUtilitiesKit/Database/Types/TypedTableDefinition.swift +++ b/SessionUtilitiesKit/Database/Types/TypedTableDefinition.swift @@ -20,7 +20,14 @@ public class TypedTableDefinition where T: TableRecord, T: ColumnExpressible definition.primaryKey(columns.map { $0.name }, onConflict: onConflict) } - public func foreignKey(_ columns: [T.Columns], references table: Other.Type, columns destinationColumns: [Other.Columns]? = nil, onDelete: Database.ForeignKeyAction? = nil, onUpdate: Database.ForeignKeyAction? = nil, deferred: Bool = false) where Other: TableRecord, Other: ColumnExpressible { + public func foreignKey( + _ columns: [T.Columns], + references table: Other.Type, + columns destinationColumns: [Other.Columns]? = nil, + onDelete: Database.ForeignKeyAction? = nil, + onUpdate: Database.ForeignKeyAction? = nil, + deferred: Bool = false + ) where Other: TableRecord, Other: ColumnExpressible { return definition.foreignKey( columns.map { $0.name }, references: table.databaseTableName, diff --git a/SessionUtilitiesKit/General/Array+Utilities.swift b/SessionUtilitiesKit/General/Array+Utilities.swift index 0b4d8b7fd..43d74f007 100644 --- a/SessionUtilitiesKit/General/Array+Utilities.swift +++ b/SessionUtilitiesKit/General/Array+Utilities.swift @@ -6,6 +6,25 @@ public extension Array where Element : CustomStringConvertible { } } +public extension Array { + func appending(_ other: Element?) -> [Element] { + guard let other: Element = other else { return self } + + var updatedArray: [Element] = self + updatedArray.append(other) + return updatedArray + } + + func appending(contentsOf other: [Element]?) -> [Element] { + guard let other: [Element] = other else { return self } + + var updatedArray: [Element] = self + updatedArray.append(contentsOf: other) + return updatedArray + } +} + + public extension Array where Element: Hashable { func asSet() -> Set { return Set(self) diff --git a/SessionUtilitiesKit/General/Dictionary+Description.swift b/SessionUtilitiesKit/General/Dictionary+Description.swift index f402736ac..927c7c8e5 100644 --- a/SessionUtilitiesKit/General/Dictionary+Description.swift +++ b/SessionUtilitiesKit/General/Dictionary+Description.swift @@ -10,4 +10,14 @@ public extension Dictionary { return keyDescription + " : " + truncatedValueDescription }.joined(separator: ", ") + " ]" } + + func asArray() -> [(key: Key, value: Value)] { + return Array(self) + } +} + +public extension Dictionary.Values { + func asArray() -> [Value] { + return Array(self) + } } diff --git a/SessionUtilitiesKit/General/General.swift b/SessionUtilitiesKit/General/General.swift index 7fb5bacdf..d25e5b774 100644 --- a/SessionUtilitiesKit/General/General.swift +++ b/SessionUtilitiesKit/General/General.swift @@ -1,3 +1,35 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation +import Curve25519Kit + +public enum General { + public enum Cache { + public static var cachedEncodedPublicKey: Atomic = Atomic(nil) + } +} + +public enum GeneralError: Error { + case keyGenerationFailed +} + +@objc(SNGeneralUtilities) +public class GeneralUtilities: NSObject { + @objc public static func getUserPublicKey() -> String { + return getUserHexEncodedPublicKey() + } +} + +public func getUserHexEncodedPublicKey() -> String { + if let cachedKey: String = General.Cache.cachedEncodedPublicKey.wrappedValue { return cachedKey } + + if let keyPair: ECKeyPair = Identity.fetchUserKeyPair() { // Can be nil under some circumstances + General.Cache.cachedEncodedPublicKey.mutate { $0 = keyPair.hexEncodedPublicKey } + return keyPair.hexEncodedPublicKey + } + + return "" +} /// Does nothing, but is never inlined and thus evaluating its argument will never be optimized away. /// diff --git a/SessionUtilitiesKit/Utilities/Failable.swift b/SessionUtilitiesKit/Utilities/Failable.swift new file mode 100644 index 000000000..47ff9227d --- /dev/null +++ b/SessionUtilitiesKit/Utilities/Failable.swift @@ -0,0 +1,26 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation + +/// The `Failable` type allows for coding an array of values without failing the entire array if a single +/// value fails to encode/decode correctly +public struct Failable: Codable { + public let value: T? + + public init(from decoder: Decoder) throws { + guard let container = try? decoder.singleValueContainer() else { + self.value = nil + return + } + + self.value = try? container.decode(T.self) + } + + public func encode(to encoder: Encoder) throws { + guard let value: T = value else { return } + + var container: SingleValueEncodingContainer = encoder.singleValueContainer() + + try container.encode(value) + } +} diff --git a/SessionMessagingKit/Utilities/Sodium+Conversion.swift b/SessionUtilitiesKit/Utilities/Sodium+Conversion.swift similarity index 94% rename from SessionMessagingKit/Utilities/Sodium+Conversion.swift rename to SessionUtilitiesKit/Utilities/Sodium+Conversion.swift index c522bdf92..fc6ead795 100644 --- a/SessionMessagingKit/Utilities/Sodium+Conversion.swift +++ b/SessionUtilitiesKit/Utilities/Sodium+Conversion.swift @@ -1,3 +1,6 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. + +import Foundation import Clibsodium import Sodium diff --git a/SignalUtilitiesKit/Configuration.swift b/SignalUtilitiesKit/Configuration.swift index b32479eb4..362a47f17 100644 --- a/SignalUtilitiesKit/Configuration.swift +++ b/SignalUtilitiesKit/Configuration.swift @@ -24,6 +24,7 @@ public final class Configuration : NSObject { //DispatchQueue.main.once let storage: GRDBStorage? = try? GRDBStorage( migrations: [ + SNUtilitiesKit.migrations(), SNSnodeKit.migrations(), SNMessagingKit.migrations() ] @@ -31,6 +32,6 @@ public final class Configuration : NSObject { } SNMessagingKit.configure(storage: Storage.shared) - SNSnodeKit.configure(storage: Storage.shared) + SNSnodeKit.configure() } } diff --git a/SignalUtilitiesKit/Database/Storage+Conformances.swift b/SignalUtilitiesKit/Database/Storage+Conformances.swift index 21c74ee43..b6c9604bc 100644 --- a/SignalUtilitiesKit/Database/Storage+Conformances.swift +++ b/SignalUtilitiesKit/Database/Storage+Conformances.swift @@ -1,5 +1,8 @@ +// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved. -extension Storage : SessionMessagingKitStorageProtocol, SessionSnodeKitStorageProtocol { +import Foundation + +extension Storage : SessionMessagingKitStorageProtocol { public func updateMessageIDCollectionByPruningMessagesWithIDs(_ messageIDs: Set, using transaction: Any) { let transaction = transaction as! YapDatabaseReadWriteTransaction diff --git a/SignalUtilitiesKit/Database/TSStorageHeaders.h b/SignalUtilitiesKit/Database/TSStorageHeaders.h index 9f7894e8e..11615a843 100644 --- a/SignalUtilitiesKit/Database/TSStorageHeaders.h +++ b/SignalUtilitiesKit/Database/TSStorageHeaders.h @@ -4,7 +4,7 @@ #ifndef Signal_TSStorageHeaders_h #define Signal_TSStorageHeaders_h -#import + #import #import diff --git a/SignalUtilitiesKit/To Do/OWSPrimaryStorage+Loki.m b/SignalUtilitiesKit/To Do/OWSPrimaryStorage+Loki.m index 97e2f4e8a..2e1e1056c 100644 --- a/SignalUtilitiesKit/To Do/OWSPrimaryStorage+Loki.m +++ b/SignalUtilitiesKit/To Do/OWSPrimaryStorage+Loki.m @@ -1,6 +1,5 @@ #import "OWSPrimaryStorage+Loki.h" #import "OWSPrimaryStorage+keyFromIntLong.h" -#import "OWSIdentityManager.h" #import "NSDate+OWS.h" #import "TSAccountManager.h" #import "YapDatabaseConnection+OWS.h" diff --git a/SignalUtilitiesKit/Utilities/AppSetup.m b/SignalUtilitiesKit/Utilities/AppSetup.m index 062c572d9..3e0fcbd40 100644 --- a/SignalUtilitiesKit/Utilities/AppSetup.m +++ b/SignalUtilitiesKit/Utilities/AppSetup.m @@ -9,7 +9,6 @@ #import #import #import -#import #import #import #import @@ -51,7 +50,6 @@ NS_ASSUME_NONNULL_BEGIN OWSPreferences *preferences = [OWSPreferences new]; OWSProfileManager *profileManager = [[OWSProfileManager alloc] initWithPrimaryStorage:primaryStorage]; - OWSIdentityManager *identityManager = [[OWSIdentityManager alloc] initWithPrimaryStorage:primaryStorage]; TSAccountManager *tsAccountManager = [[TSAccountManager alloc] initWithPrimaryStorage:primaryStorage]; OWSDisappearingMessagesJob *disappearingMessagesJob = [[OWSDisappearingMessagesJob alloc] initWithPrimaryStorage:primaryStorage]; @@ -75,7 +73,6 @@ NS_ASSUME_NONNULL_BEGIN [SSKEnvironment setShared:[[SSKEnvironment alloc] initWithProfileManager:profileManager primaryStorage:primaryStorage - identityManager:identityManager tsAccountManager:tsAccountManager disappearingMessagesJob:disappearingMessagesJob readReceiptManager:readReceiptManager