mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Migrated the SessionSnodeKit from YapDatabase to GRDB
Changed the min OS version to iOS 13.0 (support for 'Identifiable') Removed the alternate approaches to fetching the userKeyPair and userPublicKeyHexString (no consistently routed through the caching method) Migrated the 'OWSIdentityManager' logic to use the new 'Identity' type Added the 'Setting' table and got the pattern working fairly nicely (unfortunately there isn't a good way to avoid key collision without proper enums) Updated the SessionSnodeKit to migration it's data from YDB to GRDB Updated the SessionSnodeKit to use GRDB throughout it's logic
This commit is contained in:
parent
e65682ae9b
commit
a1b4554cdb
|
@ -188,7 +188,6 @@
|
||||||
B8566C7D256F62030045A0B9 /* OWSUserProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = C38EF2D3255B6DAF007E1867 /* OWSUserProfile.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
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 */; };
|
B8569AC325CB5D2900DBA3DB /* ConversationVC+Interaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8569AC225CB5D2900DBA3DB /* ConversationVC+Interaction.swift */; };
|
||||||
B8569AE325CBB19A00DBA3DB /* DocumentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8569AE225CBB19A00DBA3DB /* DocumentView.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 */; };
|
B86BD08423399ACF000F5AE3 /* Modal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08323399ACF000F5AE3 /* Modal.swift */; };
|
||||||
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; };
|
B86BD08623399CEF000F5AE3 /* SeedModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86BD08523399CEF000F5AE3 /* SeedModal.swift */; };
|
||||||
B875885A264503A6000E60D0 /* JoinOpenGroupModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8758859264503A6000E60D0 /* JoinOpenGroupModal.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 */; };
|
C31A6C5C247F2CF3001123EF /* CGRect+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */; };
|
||||||
C31D1DE32521718E005D4DA8 /* UserSelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */; };
|
C31D1DE32521718E005D4DA8 /* UserSelectionVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */; };
|
||||||
C31D1DE9252172D4005D4DA8 /* ContactUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C31D1DE8252172D4005D4DA8 /* ContactUtilities.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 */; };
|
C3227FF6260AAD66006EA627 /* OpenGroupMessageV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3227FF5260AAD66006EA627 /* OpenGroupMessageV2.swift */; };
|
||||||
C32824D325C9F9790062D0A7 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; };
|
C32824D325C9F9790062D0A7 /* SessionSnodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A59F255385C100C340D1 /* SessionSnodeKit.framework */; };
|
||||||
C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328250E25CA06020062D0A7 /* VoiceMessageView.swift */; };
|
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, ); }; };
|
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 */; };
|
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, ); }; };
|
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, ); }; };
|
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 */; };
|
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, ); }; };
|
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 */; };
|
C32C5C89256DD0D2003C73A2 /* Storage+Jobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D8F17625661AFA0092EF10 /* Storage+Jobs.swift */; };
|
||||||
C32C5CA4256DD1DC003C73A2 /* TSAccountManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB88255A581200E217F9 /* TSAccountManager.m */; };
|
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, ); }; };
|
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 */; };
|
C32C5CF0256DD3E4003C73A2 /* Storage+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3F0A5FD255C988A007BE2A3 /* Storage+Shared.swift */; };
|
||||||
C32C5D19256DD493003C73A2 /* OWSLinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBA8255A581500E217F9 /* OWSLinkPreview.swift */; };
|
C32C5D19256DD493003C73A2 /* OWSLinkPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBA8255A581500E217F9 /* OWSLinkPreview.swift */; };
|
||||||
C32C5D23256DD4C0003C73A2 /* Mention.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDA7E255A57FB00E217F9 /* Mention.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, ); }; };
|
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 */; };
|
C3C2A5BF255385EE00C340D1 /* SnodeMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B6255385EC00C340D1 /* SnodeMessage.swift */; };
|
||||||
C3C2A5C0255385EE00C340D1 /* Snode.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B7255385EC00C340D1 /* Snode.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 */; };
|
C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5B9255385ED00C340D1 /* Configuration.swift */; };
|
||||||
C3C2A5C3255385EE00C340D1 /* OnionRequestAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C2A5BA255385ED00C340D1 /* OnionRequestAPI.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 */; };
|
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 */; };
|
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 */; };
|
FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3BD9871A30A790005B96BB /* Social.framework */; };
|
||||||
FCB11D8C1A129A76002F93FB /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FCB11D8B1A129A76002F93FB /* CoreMedia.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 */; };
|
FD17D79927F40AB800122BE0 /* _002_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79827F40AB800122BE0 /* _002_YDBToGRDBMigration.swift */; };
|
||||||
FD17D79C27F40B2E00122BE0 /* SMKLegacyModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D79B27F40B2E00122BE0 /* SMKLegacyModels.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 */; };
|
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 */; };
|
FD17D7AE27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */; };
|
||||||
FD17D7B027F4225C00122BE0 /* Set+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7AF27F4225C00122BE0 /* Set+Utilities.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 */; };
|
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 */; };
|
FD17D7B827F51ECA00122BE0 /* Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B727F51ECA00122BE0 /* Migration.swift */; };
|
||||||
FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7B927F51F2100122BE0 /* TargetMigrations.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 */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
FD28A4F227E990E800FF65E7 /* BlockingManagerRemovalMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD28A4F127E990E800FF65E7 /* BlockingManagerRemovalMigration.swift */; };
|
||||||
FD28A4F427EA79F800FF65E7 /* BlockListUIUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD28A4F327EA79F800FF65E7 /* BlockListUIUtils.swift */; };
|
FD28A4F427EA79F800FF65E7 /* BlockListUIUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD28A4F327EA79F800FF65E7 /* BlockListUIUtils.swift */; };
|
||||||
FD3C907327E8387300CD579F /* OpenGroupServerIdLookupMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.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 = "<group>"; };
|
B8D8F17625661AFA0092EF10 /* Storage+Jobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Jobs.swift"; sourceTree = "<group>"; };
|
||||||
B8D8F18825661BA50092EF10 /* Storage+OpenGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+OpenGroups.swift"; sourceTree = "<group>"; };
|
B8D8F18825661BA50092EF10 /* Storage+OpenGroups.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+OpenGroups.swift"; sourceTree = "<group>"; };
|
||||||
B8D8F19225661BF80092EF10 /* Storage+Messaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Messaging.swift"; sourceTree = "<group>"; };
|
B8D8F19225661BF80092EF10 /* Storage+Messaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Messaging.swift"; sourceTree = "<group>"; };
|
||||||
B8D8F1BC25661C6F0092EF10 /* Storage+OnionRequests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+OnionRequests.swift"; sourceTree = "<group>"; };
|
|
||||||
B8D8F1EF256621180092EF10 /* MessageSender+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "MessageSender+Convenience.swift"; path = "../../SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift"; sourceTree = "<group>"; };
|
B8D8F1EF256621180092EF10 /* MessageSender+Convenience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "MessageSender+Convenience.swift"; path = "../../SignalUtilitiesKit/Messaging/Sending & Receiving/MessageSender+Convenience.swift"; sourceTree = "<group>"; };
|
||||||
B8EB20E6263F7E4B00773E52 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
B8EB20E6263F7E4B00773E52 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+OpenGroupInvitation.swift"; sourceTree = "<group>"; };
|
B8EB20ED2640F28000773E52 /* VisibleMessage+OpenGroupInvitation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "VisibleMessage+OpenGroupInvitation.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -1307,7 +1309,6 @@
|
||||||
C31D1DDC25217014005D4DA8 /* UserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCell.swift; sourceTree = "<group>"; };
|
C31D1DDC25217014005D4DA8 /* UserCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserCell.swift; sourceTree = "<group>"; };
|
||||||
C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSelectionVC.swift; sourceTree = "<group>"; };
|
C31D1DE22521718E005D4DA8 /* UserSelectionVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSelectionVC.swift; sourceTree = "<group>"; };
|
||||||
C31D1DE8252172D4005D4DA8 /* ContactUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactUtilities.swift; sourceTree = "<group>"; };
|
C31D1DE8252172D4005D4DA8 /* ContactUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactUtilities.swift; sourceTree = "<group>"; };
|
||||||
C31FFE56254A5FFE00F19441 /* KeyPairUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyPairUtilities.swift; sourceTree = "<group>"; };
|
|
||||||
C3227FF5260AAD66006EA627 /* OpenGroupMessageV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupMessageV2.swift; sourceTree = "<group>"; };
|
C3227FF5260AAD66006EA627 /* OpenGroupMessageV2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupMessageV2.swift; sourceTree = "<group>"; };
|
||||||
C328250E25CA06020062D0A7 /* VoiceMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageView.swift; sourceTree = "<group>"; };
|
C328250E25CA06020062D0A7 /* VoiceMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceMessageView.swift; sourceTree = "<group>"; };
|
||||||
C328251E25CA3A900062D0A7 /* QuoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteView.swift; sourceTree = "<group>"; };
|
C328251E25CA3A900062D0A7 /* QuoteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuoteView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1461,7 +1462,6 @@
|
||||||
C33FDBA1255A581400E217F9 /* OWSOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOperation.h; sourceTree = "<group>"; };
|
C33FDBA1255A581400E217F9 /* OWSOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSOperation.h; sourceTree = "<group>"; };
|
||||||
C33FDBA4255A581400E217F9 /* OWSDisappearingMessagesConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDisappearingMessagesConfiguration.m; sourceTree = "<group>"; };
|
C33FDBA4255A581400E217F9 /* OWSDisappearingMessagesConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSDisappearingMessagesConfiguration.m; sourceTree = "<group>"; };
|
||||||
C33FDBA8255A581500E217F9 /* OWSLinkPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSLinkPreview.swift; sourceTree = "<group>"; };
|
C33FDBA8255A581500E217F9 /* OWSLinkPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSLinkPreview.swift; sourceTree = "<group>"; };
|
||||||
C33FDBA9255A581500E217F9 /* OWSIdentityManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSIdentityManager.m; sourceTree = "<group>"; };
|
|
||||||
C33FDBAB255A581500E217F9 /* OWSFileSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSFileSystem.h; sourceTree = "<group>"; };
|
C33FDBAB255A581500E217F9 /* OWSFileSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSFileSystem.h; sourceTree = "<group>"; };
|
||||||
C33FDBAE255A581500E217F9 /* SignalAccount.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalAccount.h; sourceTree = "<group>"; };
|
C33FDBAE255A581500E217F9 /* SignalAccount.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SignalAccount.h; sourceTree = "<group>"; };
|
||||||
C33FDBB4255A581600E217F9 /* NSURLSessionDataTask+StatusCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLSessionDataTask+StatusCode.m"; sourceTree = "<group>"; };
|
C33FDBB4255A581600E217F9 /* NSURLSessionDataTask+StatusCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLSessionDataTask+StatusCode.m"; sourceTree = "<group>"; };
|
||||||
|
@ -1472,7 +1472,6 @@
|
||||||
C33FDBBA255A581600E217F9 /* OWSPrimaryStorage+keyFromIntLong.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OWSPrimaryStorage+keyFromIntLong.h"; sourceTree = "<group>"; };
|
C33FDBBA255A581600E217F9 /* OWSPrimaryStorage+keyFromIntLong.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OWSPrimaryStorage+keyFromIntLong.h"; sourceTree = "<group>"; };
|
||||||
C33FDBBB255A581600E217F9 /* OWSPrimaryStorage+Loki.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OWSPrimaryStorage+Loki.h"; sourceTree = "<group>"; };
|
C33FDBBB255A581600E217F9 /* OWSPrimaryStorage+Loki.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OWSPrimaryStorage+Loki.h"; sourceTree = "<group>"; };
|
||||||
C33FDBBC255A581600E217F9 /* SSKKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSKKeychainStorage.swift; sourceTree = "<group>"; };
|
C33FDBBC255A581600E217F9 /* SSKKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSKKeychainStorage.swift; sourceTree = "<group>"; };
|
||||||
C33FDBC1255A581700E217F9 /* General.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = General.swift; sourceTree = "<group>"; };
|
|
||||||
C33FDBC2255A581700E217F9 /* SSKAsserts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKAsserts.h; sourceTree = "<group>"; };
|
C33FDBC2255A581700E217F9 /* SSKAsserts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKAsserts.h; sourceTree = "<group>"; };
|
||||||
C33FDBCA255A581700E217F9 /* LKGroupUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LKGroupUtilities.h; sourceTree = "<group>"; };
|
C33FDBCA255A581700E217F9 /* LKGroupUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LKGroupUtilities.h; sourceTree = "<group>"; };
|
||||||
C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSSignalAddress.swift; sourceTree = "<group>"; };
|
C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSSignalAddress.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1484,7 +1483,6 @@
|
||||||
C33FDBE9255A581A00E217F9 /* TSInteraction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSInteraction.m; sourceTree = "<group>"; };
|
C33FDBE9255A581A00E217F9 /* TSInteraction.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSInteraction.m; sourceTree = "<group>"; };
|
||||||
C33FDBEC255A581B00E217F9 /* OWSRecipientIdentity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSRecipientIdentity.m; sourceTree = "<group>"; };
|
C33FDBEC255A581B00E217F9 /* OWSRecipientIdentity.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSRecipientIdentity.m; sourceTree = "<group>"; };
|
||||||
C33FDBEF255A581B00E217F9 /* TSStorageKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSStorageKeys.h; sourceTree = "<group>"; };
|
C33FDBEF255A581B00E217F9 /* TSStorageKeys.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSStorageKeys.h; sourceTree = "<group>"; };
|
||||||
C33FDBF1255A581B00E217F9 /* OWSIdentityManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSIdentityManager.h; sourceTree = "<group>"; };
|
|
||||||
C33FDBF6255A581C00E217F9 /* NSURLSessionDataTask+StatusCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLSessionDataTask+StatusCode.h"; sourceTree = "<group>"; };
|
C33FDBF6255A581C00E217F9 /* NSURLSessionDataTask+StatusCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLSessionDataTask+StatusCode.h"; sourceTree = "<group>"; };
|
||||||
C33FDBF8255A581C00E217F9 /* NSArray+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+OWS.h"; sourceTree = "<group>"; };
|
C33FDBF8255A581C00E217F9 /* NSArray+OWS.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+OWS.h"; sourceTree = "<group>"; };
|
||||||
C33FDBF9255A581C00E217F9 /* OWSError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSError.h; sourceTree = "<group>"; };
|
C33FDBF9255A581C00E217F9 /* OWSError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSError.h; sourceTree = "<group>"; };
|
||||||
|
@ -1706,7 +1704,6 @@
|
||||||
C3C2A5A2255385C100C340D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
C3C2A5A2255385C100C340D1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
C3C2A5B6255385EC00C340D1 /* SnodeMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeMessage.swift; sourceTree = "<group>"; };
|
C3C2A5B6255385EC00C340D1 /* SnodeMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeMessage.swift; sourceTree = "<group>"; };
|
||||||
C3C2A5B7255385EC00C340D1 /* Snode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Snode.swift; sourceTree = "<group>"; };
|
C3C2A5B7255385EC00C340D1 /* Snode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Snode.swift; sourceTree = "<group>"; };
|
||||||
C3C2A5B8255385EC00C340D1 /* Storage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
|
|
||||||
C3C2A5B9255385ED00C340D1 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
|
C3C2A5B9255385ED00C340D1 /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
|
||||||
C3C2A5BA255385ED00C340D1 /* OnionRequestAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnionRequestAPI.swift; sourceTree = "<group>"; };
|
C3C2A5BA255385ED00C340D1 /* OnionRequestAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnionRequestAPI.swift; sourceTree = "<group>"; };
|
||||||
C3C2A5BB255385ED00C340D1 /* OnionRequestAPI+Encryption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OnionRequestAPI+Encryption.swift"; sourceTree = "<group>"; };
|
C3C2A5BB255385ED00C340D1 /* OnionRequestAPI+Encryption.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OnionRequestAPI+Encryption.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -1764,7 +1761,6 @@
|
||||||
C3F0A5B2255C915C007BE2A3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
C3F0A5B2255C915C007BE2A3 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
C3F0A5EB255C970D007BE2A3 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
|
C3F0A5EB255C970D007BE2A3 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
|
||||||
C3F0A5FD255C988A007BE2A3 /* Storage+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Shared.swift"; sourceTree = "<group>"; };
|
C3F0A5FD255C988A007BE2A3 /* Storage+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+Shared.swift"; sourceTree = "<group>"; };
|
||||||
C3F0A607255C98A6007BE2A3 /* Storage+SnodeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Storage+SnodeAPI.swift"; sourceTree = "<group>"; };
|
|
||||||
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; };
|
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 = "<group>"; };
|
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 = "<group>"; };
|
||||||
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 = "<group>"; };
|
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 = "<group>"; };
|
||||||
|
@ -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 = "<group>"; };
|
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 = "<group>"; };
|
||||||
FC3BD9871A30A790005B96BB /* Social.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Social.framework; path = System/Library/Frameworks/Social.framework; sourceTree = SDKROOT; };
|
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; };
|
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 = "<group>"; };
|
||||||
|
FD09796A27F6C67500936362 /* Failable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Failable.swift; sourceTree = "<group>"; };
|
||||||
FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = "<group>"; };
|
FD17D79527F3E04600122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = "<group>"; };
|
||||||
FD17D79827F40AB800122BE0 /* _002_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_YDBToGRDBMigration.swift; sourceTree = "<group>"; };
|
FD17D79827F40AB800122BE0 /* _002_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_YDBToGRDBMigration.swift; sourceTree = "<group>"; };
|
||||||
FD17D79B27F40B2E00122BE0 /* SMKLegacyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKLegacyModels.swift; sourceTree = "<group>"; };
|
FD17D79B27F40B2E00122BE0 /* SMKLegacyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKLegacyModels.swift; sourceTree = "<group>"; };
|
||||||
|
@ -1805,7 +1803,6 @@
|
||||||
FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeReceivedMessageInfo.swift; sourceTree = "<group>"; };
|
FD17D7AD27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeReceivedMessageInfo.swift; sourceTree = "<group>"; };
|
||||||
FD17D7AF27F4225C00122BE0 /* Set+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Utilities.swift"; sourceTree = "<group>"; };
|
FD17D7AF27F4225C00122BE0 /* Set+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Set+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
FD17D7B227F51E5B00122BE0 /* SSKSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKSetting.swift; sourceTree = "<group>"; };
|
FD17D7B227F51E5B00122BE0 /* SSKSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKSetting.swift; sourceTree = "<group>"; };
|
||||||
FD17D7B527F51E7300122BE0 /* SettingType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingType.swift; sourceTree = "<group>"; };
|
|
||||||
FD17D7B727F51ECA00122BE0 /* Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Migration.swift; sourceTree = "<group>"; };
|
FD17D7B727F51ECA00122BE0 /* Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Migration.swift; sourceTree = "<group>"; };
|
||||||
FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetMigrations.swift; sourceTree = "<group>"; };
|
FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TargetMigrations.swift; sourceTree = "<group>"; };
|
||||||
FD17D7BC27F51F6900122BE0 /* GRDB+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GRDB+Notifications.swift"; sourceTree = "<group>"; };
|
FD17D7BC27F51F6900122BE0 /* GRDB+Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GRDB+Notifications.swift"; sourceTree = "<group>"; };
|
||||||
|
@ -1814,9 +1811,18 @@
|
||||||
FD17D7C227F5204C00122BE0 /* Database+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Database+Utilities.swift"; sourceTree = "<group>"; };
|
FD17D7C227F5204C00122BE0 /* Database+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Database+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
FD17D7C427F5206300122BE0 /* ColumnDefinition+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ColumnDefinition+Utilities.swift"; sourceTree = "<group>"; };
|
FD17D7C427F5206300122BE0 /* ColumnDefinition+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ColumnDefinition+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DatabaseMigrator+Utilities.swift"; sourceTree = "<group>"; };
|
FD17D7C627F5207C00122BE0 /* DatabaseMigrator+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DatabaseMigrator+Utilities.swift"; sourceTree = "<group>"; };
|
||||||
FD28A4F527EAD44C00FF65E7 /* GRDBStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GRDBStorage.swift; sourceTree = "<group>"; };
|
FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _001_InitialSetupMigration.swift; sourceTree = "<group>"; };
|
||||||
|
FD17D7CC27F546FF00122BE0 /* Setting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Setting.swift; sourceTree = "<group>"; };
|
||||||
|
FD17D7D127F5797A00122BE0 /* SSKEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKEndpoint.swift; sourceTree = "<group>"; };
|
||||||
|
FD17D7D327F6584600122BE0 /* SSKError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKError.swift; sourceTree = "<group>"; };
|
||||||
|
FD17D7D727F658E200122BE0 /* SSKDestination.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSKDestination.swift; sourceTree = "<group>"; };
|
||||||
|
FD17D7E027F67BD400122BE0 /* SnodeReceivedMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnodeReceivedMessage.swift; sourceTree = "<group>"; };
|
||||||
|
FD17D7E427F6A09900122BE0 /* Identity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Identity.swift; sourceTree = "<group>"; };
|
||||||
|
FD17D7E627F6A16700122BE0 /* _002_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_YDBToGRDBMigration.swift; sourceTree = "<group>"; };
|
||||||
|
FD17D7E927F6A1C600122BE0 /* SUKLegacyModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SUKLegacyModels.swift; sourceTree = "<group>"; };
|
||||||
FD28A4F127E990E800FF65E7 /* BlockingManagerRemovalMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockingManagerRemovalMigration.swift; sourceTree = "<group>"; };
|
FD28A4F127E990E800FF65E7 /* BlockingManagerRemovalMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockingManagerRemovalMigration.swift; sourceTree = "<group>"; };
|
||||||
FD28A4F327EA79F800FF65E7 /* BlockListUIUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListUIUtils.swift; sourceTree = "<group>"; };
|
FD28A4F327EA79F800FF65E7 /* BlockListUIUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockListUIUtils.swift; sourceTree = "<group>"; };
|
||||||
|
FD28A4F527EAD44C00FF65E7 /* GRDBStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GRDBStorage.swift; sourceTree = "<group>"; };
|
||||||
FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookupMigration.swift; sourceTree = "<group>"; };
|
FD3C907227E8387300CD579F /* OpenGroupServerIdLookupMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookupMigration.swift; sourceTree = "<group>"; };
|
||||||
FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookup.swift; sourceTree = "<group>"; };
|
FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookup.swift; sourceTree = "<group>"; };
|
||||||
FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = "<group>"; };
|
FD5D200E27AA2B6000FEA984 /* MessageRequestResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageRequestResponse.swift; sourceTree = "<group>"; };
|
||||||
|
@ -2062,7 +2068,6 @@
|
||||||
C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */,
|
C31A6C5B247F2CF3001123EF /* CGRect+Utilities.swift */,
|
||||||
C31D1DE8252172D4005D4DA8 /* ContactUtilities.swift */,
|
C31D1DE8252172D4005D4DA8 /* ContactUtilities.swift */,
|
||||||
C35E8AAD2485E51D00ACB629 /* IP2Country.swift */,
|
C35E8AAD2485E51D00ACB629 /* IP2Country.swift */,
|
||||||
C31FFE56254A5FFE00F19441 /* KeyPairUtilities.swift */,
|
|
||||||
B84664F4235022F30083A1CD /* MentionUtilities.swift */,
|
B84664F4235022F30083A1CD /* MentionUtilities.swift */,
|
||||||
B886B4A82398BA1500211ABE /* QRCode.swift */,
|
B886B4A82398BA1500211ABE /* QRCode.swift */,
|
||||||
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */,
|
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */,
|
||||||
|
@ -2271,6 +2276,9 @@
|
||||||
B8A582AB258C64E800AFD84C /* Database */ = {
|
B8A582AB258C64E800AFD84C /* Database */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
FD17D7E827F6A1B800122BE0 /* LegacyDatabase */,
|
||||||
|
FD17D7C827F546CE00122BE0 /* Migrations */,
|
||||||
|
FD17D7CB27F546F500122BE0 /* Models */,
|
||||||
FD17D7B427F51E6700122BE0 /* Types */,
|
FD17D7B427F51E6700122BE0 /* Types */,
|
||||||
FD17D7BB27F51F5C00122BE0 /* Utilities */,
|
FD17D7BB27F51F5C00122BE0 /* Utilities */,
|
||||||
FD28A4F527EAD44C00FF65E7 /* GRDBStorage.swift */,
|
FD28A4F527EAD44C00FF65E7 /* GRDBStorage.swift */,
|
||||||
|
@ -3183,7 +3191,6 @@
|
||||||
C37F5402255BA9ED002AEA92 /* Environment.m */,
|
C37F5402255BA9ED002AEA92 /* Environment.m */,
|
||||||
C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */,
|
C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */,
|
||||||
C33FDB7F255A581100E217F9 /* FullTextSearchFinder.swift */,
|
C33FDB7F255A581100E217F9 /* FullTextSearchFinder.swift */,
|
||||||
C33FDBC1255A581700E217F9 /* General.swift */,
|
|
||||||
B82A0C3726B9098200C1BCE3 /* MessageInvalidator.swift */,
|
B82A0C3726B9098200C1BCE3 /* MessageInvalidator.swift */,
|
||||||
C3A71D0A2558989C0043A11F /* MessageWrapper.swift */,
|
C3A71D0A2558989C0043A11F /* MessageWrapper.swift */,
|
||||||
C3A71D4E25589FF30043A11F /* NSData+messagePadding.h */,
|
C3A71D4E25589FF30043A11F /* NSData+messagePadding.h */,
|
||||||
|
@ -3195,8 +3202,6 @@
|
||||||
C33FDC1B255A581F00E217F9 /* OWSBackgroundTask.m */,
|
C33FDC1B255A581F00E217F9 /* OWSBackgroundTask.m */,
|
||||||
C33FDC05255A581D00E217F9 /* OWSDisappearingMessagesFinder.h */,
|
C33FDC05255A581D00E217F9 /* OWSDisappearingMessagesFinder.h */,
|
||||||
C33FDA86255A57FC00E217F9 /* OWSDisappearingMessagesFinder.m */,
|
C33FDA86255A57FC00E217F9 /* OWSDisappearingMessagesFinder.m */,
|
||||||
C33FDBF1255A581B00E217F9 /* OWSIdentityManager.h */,
|
|
||||||
C33FDBA9255A581500E217F9 /* OWSIdentityManager.m */,
|
|
||||||
C33FDAC0255A580100E217F9 /* OWSIncomingMessageFinder.h */,
|
C33FDAC0255A580100E217F9 /* OWSIncomingMessageFinder.h */,
|
||||||
C33FDB1E255A580900E217F9 /* OWSIncomingMessageFinder.m */,
|
C33FDB1E255A580900E217F9 /* OWSIncomingMessageFinder.m */,
|
||||||
C33FDB67255A580F00E217F9 /* OWSMediaGalleryFinder.h */,
|
C33FDB67255A580F00E217F9 /* OWSMediaGalleryFinder.h */,
|
||||||
|
@ -3213,7 +3218,6 @@
|
||||||
C33FDB91255A581200E217F9 /* ProtoUtils.h */,
|
C33FDB91255A581200E217F9 /* ProtoUtils.h */,
|
||||||
C33FDA6C255A57FA00E217F9 /* ProtoUtils.m */,
|
C33FDA6C255A57FA00E217F9 /* ProtoUtils.m */,
|
||||||
C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */,
|
C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */,
|
||||||
C3E7134E251C867C009649BB /* Sodium+Conversion.swift */,
|
|
||||||
C33FDB31255A580A00E217F9 /* SSKEnvironment.h */,
|
C33FDB31255A580A00E217F9 /* SSKEnvironment.h */,
|
||||||
C33FDAF4255A580600E217F9 /* SSKEnvironment.m */,
|
C33FDAF4255A580600E217F9 /* SSKEnvironment.m */,
|
||||||
C33FDB32255A580A00E217F9 /* SSKIncrementingIdFinder.swift */,
|
C33FDB32255A580A00E217F9 /* SSKIncrementingIdFinder.swift */,
|
||||||
|
@ -3233,15 +3237,14 @@
|
||||||
children = (
|
children = (
|
||||||
C3C2A5B0255385C700C340D1 /* Meta */,
|
C3C2A5B0255385C700C340D1 /* Meta */,
|
||||||
FD17D79D27F40CAA00122BE0 /* Database */,
|
FD17D79D27F40CAA00122BE0 /* Database */,
|
||||||
|
FD17D7DF27F67BC400122BE0 /* Models */,
|
||||||
|
FD17D7D027F5795300122BE0 /* Types */,
|
||||||
C3C2A5B9255385ED00C340D1 /* Configuration.swift */,
|
C3C2A5B9255385ED00C340D1 /* Configuration.swift */,
|
||||||
C3C2A5BD255385EE00C340D1 /* Notification+OnionRequestAPI.swift */,
|
C3C2A5BD255385EE00C340D1 /* Notification+OnionRequestAPI.swift */,
|
||||||
C3C2A5BA255385ED00C340D1 /* OnionRequestAPI.swift */,
|
C3C2A5BA255385ED00C340D1 /* OnionRequestAPI.swift */,
|
||||||
C3C2A5BB255385ED00C340D1 /* OnionRequestAPI+Encryption.swift */,
|
C3C2A5BB255385ED00C340D1 /* OnionRequestAPI+Encryption.swift */,
|
||||||
C3C2A5BE255385EE00C340D1 /* SnodeAPI.swift */,
|
C3C2A5BE255385EE00C340D1 /* SnodeAPI.swift */,
|
||||||
C3C2A5B6255385EC00C340D1 /* SnodeMessage.swift */,
|
C3C2A5B6255385EC00C340D1 /* SnodeMessage.swift */,
|
||||||
C3C2A5B8255385EC00C340D1 /* Storage.swift */,
|
|
||||||
B8D8F1BC25661C6F0092EF10 /* Storage+OnionRequests.swift */,
|
|
||||||
C3F0A607255C98A6007BE2A3 /* Storage+SnodeAPI.swift */,
|
|
||||||
C3C2A5CD255385F300C340D1 /* Utilities */,
|
C3C2A5CD255385F300C340D1 /* Utilities */,
|
||||||
);
|
);
|
||||||
path = SessionSnodeKit;
|
path = SessionSnodeKit;
|
||||||
|
@ -3279,6 +3282,7 @@
|
||||||
B8A582B9258C696200AFD84C /* Messaging */,
|
B8A582B9258C696200AFD84C /* Messaging */,
|
||||||
B8A582AE258C65D000AFD84C /* Networking */,
|
B8A582AE258C65D000AFD84C /* Networking */,
|
||||||
B8A582AD258C655E00AFD84C /* PromiseKit */,
|
B8A582AD258C655E00AFD84C /* PromiseKit */,
|
||||||
|
FD09796527F6B0A800936362 /* Utilities */,
|
||||||
C3D9E43025676D3D0040E4F3 /* Configuration.swift */,
|
C3D9E43025676D3D0040E4F3 /* Configuration.swift */,
|
||||||
);
|
);
|
||||||
path = SessionUtilitiesKit;
|
path = SessionUtilitiesKit;
|
||||||
|
@ -3591,6 +3595,15 @@
|
||||||
path = Session;
|
path = Session;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
FD09796527F6B0A800936362 /* Utilities */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FD09796A27F6C67500936362 /* Failable.swift */,
|
||||||
|
C3E7134E251C867C009649BB /* Sodium+Conversion.swift */,
|
||||||
|
);
|
||||||
|
path = Utilities;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
FD17D79427F3E03300122BE0 /* Migrations */ = {
|
FD17D79427F3E03300122BE0 /* Migrations */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -3659,7 +3672,6 @@
|
||||||
children = (
|
children = (
|
||||||
FD17D7BE27F51F8200122BE0 /* ColumnExpressible.swift */,
|
FD17D7BE27F51F8200122BE0 /* ColumnExpressible.swift */,
|
||||||
FD17D7B727F51ECA00122BE0 /* Migration.swift */,
|
FD17D7B727F51ECA00122BE0 /* Migration.swift */,
|
||||||
FD17D7B527F51E7300122BE0 /* SettingType.swift */,
|
|
||||||
FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */,
|
FD17D7B927F51F2100122BE0 /* TargetMigrations.swift */,
|
||||||
FD17D7C027F5200100122BE0 /* TypedTableDefinition.swift */,
|
FD17D7C027F5200100122BE0 /* TypedTableDefinition.swift */,
|
||||||
);
|
);
|
||||||
|
@ -3677,6 +3689,51 @@
|
||||||
path = Utilities;
|
path = Utilities;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
FD17D7C827F546CE00122BE0 /* Migrations */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FD17D7C927F546D900122BE0 /* _001_InitialSetupMigration.swift */,
|
||||||
|
FD17D7E627F6A16700122BE0 /* _002_YDBToGRDBMigration.swift */,
|
||||||
|
);
|
||||||
|
path = Migrations;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
FD17D7CB27F546F500122BE0 /* Models */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FD17D7E427F6A09900122BE0 /* Identity.swift */,
|
||||||
|
FD17D7CC27F546FF00122BE0 /* Setting.swift */,
|
||||||
|
);
|
||||||
|
path = Models;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
FD17D7D027F5795300122BE0 /* Types */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FD17D7D127F5797A00122BE0 /* SSKEndpoint.swift */,
|
||||||
|
FD17D7D327F6584600122BE0 /* SSKError.swift */,
|
||||||
|
FD17D7D727F658E200122BE0 /* SSKDestination.swift */,
|
||||||
|
);
|
||||||
|
path = Types;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
FD17D7DF27F67BC400122BE0 /* Models */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FD17D7E027F67BD400122BE0 /* SnodeReceivedMessage.swift */,
|
||||||
|
FD09796827F6BEA700936362 /* SwarmSnode.swift */,
|
||||||
|
);
|
||||||
|
path = Models;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
FD17D7E827F6A1B800122BE0 /* LegacyDatabase */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FD17D7E927F6A1C600122BE0 /* SUKLegacyModels.swift */,
|
||||||
|
);
|
||||||
|
path = LegacyDatabase;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
FD659ABE27A7648200F12C02 /* Message Requests */ = {
|
FD659ABE27A7648200F12C02 /* Message Requests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -3843,7 +3900,6 @@
|
||||||
C3A3A15F256E1BB4004D228D /* ProtoUtils.h in Headers */,
|
C3A3A15F256E1BB4004D228D /* ProtoUtils.h in Headers */,
|
||||||
C3A3A145256E1B49004D228D /* OWSMediaGalleryFinder.h in Headers */,
|
C3A3A145256E1B49004D228D /* OWSMediaGalleryFinder.h in Headers */,
|
||||||
B8856E33256F18D5001CE70E /* OWSStorage+Subclass.h in Headers */,
|
B8856E33256F18D5001CE70E /* OWSStorage+Subclass.h in Headers */,
|
||||||
C32C5C0A256DC9B4003C73A2 /* OWSIdentityManager.h in Headers */,
|
|
||||||
B8856E9D256F1C3D001CE70E /* OWSSounds.h in Headers */,
|
B8856E9D256F1C3D001CE70E /* OWSSounds.h in Headers */,
|
||||||
C32C5E64256DDFD6003C73A2 /* OWSStorage.h in Headers */,
|
C32C5E64256DDFD6003C73A2 /* OWSStorage.h in Headers */,
|
||||||
);
|
);
|
||||||
|
@ -4664,27 +4720,29 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
FD09796927F6BEA700936362 /* SwarmSnode.swift in Sources */,
|
||||||
C3C2A5E02553860B00C340D1 /* Threading.swift in Sources */,
|
C3C2A5E02553860B00C340D1 /* Threading.swift in Sources */,
|
||||||
C3C2A5BF255385EE00C340D1 /* SnodeMessage.swift in Sources */,
|
C3C2A5BF255385EE00C340D1 /* SnodeMessage.swift in Sources */,
|
||||||
|
FD17D7E127F67BD400122BE0 /* SnodeReceivedMessage.swift in Sources */,
|
||||||
C3C2A5C0255385EE00C340D1 /* Snode.swift in Sources */,
|
C3C2A5C0255385EE00C340D1 /* Snode.swift in Sources */,
|
||||||
FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */,
|
FD17D7A027F40CC800122BE0 /* _001_InitialSetupMigration.swift in Sources */,
|
||||||
C3C2A5C7255385EE00C340D1 /* SnodeAPI.swift in Sources */,
|
C3C2A5C7255385EE00C340D1 /* SnodeAPI.swift in Sources */,
|
||||||
C32C5CBF256DD282003C73A2 /* Storage+SnodeAPI.swift in Sources */,
|
|
||||||
C3C2A5C6255385EE00C340D1 /* Notification+OnionRequestAPI.swift in Sources */,
|
C3C2A5C6255385EE00C340D1 /* Notification+OnionRequestAPI.swift in Sources */,
|
||||||
FD17D7AA27F41BF500122BE0 /* SnodeSet.swift in Sources */,
|
FD17D7AA27F41BF500122BE0 /* SnodeSet.swift in Sources */,
|
||||||
FD17D7A427F40F8100122BE0 /* _002_YDBToGRDBMigration.swift in Sources */,
|
FD17D7A427F40F8100122BE0 /* _002_YDBToGRDBMigration.swift in Sources */,
|
||||||
C32C5CBE256DD282003C73A2 /* Storage+OnionRequests.swift in Sources */,
|
|
||||||
C3C2A5DC2553860B00C340D1 /* Promise+Threading.swift in Sources */,
|
C3C2A5DC2553860B00C340D1 /* Promise+Threading.swift in Sources */,
|
||||||
C3C2A5C4255385EE00C340D1 /* OnionRequestAPI+Encryption.swift in Sources */,
|
C3C2A5C4255385EE00C340D1 /* OnionRequestAPI+Encryption.swift in Sources */,
|
||||||
|
FD17D7D227F5797A00122BE0 /* SSKEndpoint.swift in Sources */,
|
||||||
C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */,
|
C3C2A5DE2553860B00C340D1 /* String+Trimming.swift in Sources */,
|
||||||
C3C2A5DB2553860B00C340D1 /* Promise+Hashing.swift in Sources */,
|
C3C2A5DB2553860B00C340D1 /* Promise+Hashing.swift in Sources */,
|
||||||
C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */,
|
C3C2A5E42553860B00C340D1 /* Data+Utilities.swift in Sources */,
|
||||||
FD17D7A727F41AF000122BE0 /* SSKLegacyModels.swift in Sources */,
|
FD17D7A727F41AF000122BE0 /* SSKLegacyModels.swift in Sources */,
|
||||||
C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */,
|
C3C2A5C2255385EE00C340D1 /* Configuration.swift in Sources */,
|
||||||
|
FD17D7D827F658E200122BE0 /* SSKDestination.swift in Sources */,
|
||||||
FD17D7B327F51E5B00122BE0 /* SSKSetting.swift in Sources */,
|
FD17D7B327F51E5B00122BE0 /* SSKSetting.swift in Sources */,
|
||||||
FD17D7AE27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift in Sources */,
|
FD17D7AE27F41C4300122BE0 /* SnodeReceivedMessageInfo.swift in Sources */,
|
||||||
C3C2A5C3255385EE00C340D1 /* OnionRequestAPI.swift in Sources */,
|
C3C2A5C3255385EE00C340D1 /* OnionRequestAPI.swift in Sources */,
|
||||||
C3C2A5C1255385EE00C340D1 /* Storage.swift in Sources */,
|
FD17D7D427F6584600122BE0 /* SSKError.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -4694,7 +4752,6 @@
|
||||||
files = (
|
files = (
|
||||||
C3AABDDF2553ECF00042FF4C /* Array+Utilities.swift in Sources */,
|
C3AABDDF2553ECF00042FF4C /* Array+Utilities.swift in Sources */,
|
||||||
7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */,
|
7B1D74AC27BDE7510030B423 /* Promise+Timeout.swift in Sources */,
|
||||||
FD17D7B627F51E7300122BE0 /* SettingType.swift in Sources */,
|
|
||||||
C3AABDDF2553ECF00042FF4C /* Array+Utilities.swift in Sources */,
|
C3AABDDF2553ECF00042FF4C /* Array+Utilities.swift in Sources */,
|
||||||
C32C5A47256DB8F0003C73A2 /* ECKeyPair+Hexadecimal.swift in Sources */,
|
C32C5A47256DB8F0003C73A2 /* ECKeyPair+Hexadecimal.swift in Sources */,
|
||||||
C3D9E41525676C320040E4F3 /* Storage.swift in Sources */,
|
C3D9E41525676C320040E4F3 /* Storage.swift in Sources */,
|
||||||
|
@ -4703,11 +4760,13 @@
|
||||||
C3D9E39B256763C20040E4F3 /* AppContext.m in Sources */,
|
C3D9E39B256763C20040E4F3 /* AppContext.m in Sources */,
|
||||||
C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */,
|
C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */,
|
||||||
FD17D7C127F5200100122BE0 /* TypedTableDefinition.swift in Sources */,
|
FD17D7C127F5200100122BE0 /* TypedTableDefinition.swift in Sources */,
|
||||||
|
FD17D7EA27F6A1C600122BE0 /* SUKLegacyModels.swift in Sources */,
|
||||||
C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */,
|
C352A36D2557858E00338F3E /* NSTimer+Proxying.m in Sources */,
|
||||||
C32C5A2D256DB849003C73A2 /* LKGroupUtilities.m in Sources */,
|
C32C5A2D256DB849003C73A2 /* LKGroupUtilities.m in Sources */,
|
||||||
C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */,
|
C3C2ABD22553C6C900C340D1 /* Data+SecureRandom.swift in Sources */,
|
||||||
B8856E09256F1676001CE70E /* UIDevice+featureSupport.swift in Sources */,
|
B8856E09256F1676001CE70E /* UIDevice+featureSupport.swift in Sources */,
|
||||||
B8856DEF256F161F001CE70E /* NSString+SSK.m in Sources */,
|
B8856DEF256F161F001CE70E /* NSString+SSK.m in Sources */,
|
||||||
|
FD09796727F6B0B600936362 /* Sodium+Conversion.swift in Sources */,
|
||||||
C32C5DC9256DD935003C73A2 /* ProxiedContentDownloader.swift in Sources */,
|
C32C5DC9256DD935003C73A2 /* ProxiedContentDownloader.swift in Sources */,
|
||||||
C3D9E4C02567767F0040E4F3 /* DataSource.m in Sources */,
|
C3D9E4C02567767F0040E4F3 /* DataSource.m in Sources */,
|
||||||
C3D9E43125676D3D0040E4F3 /* Configuration.swift in Sources */,
|
C3D9E43125676D3D0040E4F3 /* Configuration.swift in Sources */,
|
||||||
|
@ -4717,6 +4776,7 @@
|
||||||
C32C5FA1256DFED5003C73A2 /* NSArray+Functional.m in Sources */,
|
C32C5FA1256DFED5003C73A2 /* NSArray+Functional.m in Sources */,
|
||||||
C3A7225E2558C38D0043A11F /* Promise+Retaining.swift in Sources */,
|
C3A7225E2558C38D0043A11F /* Promise+Retaining.swift in Sources */,
|
||||||
FD17D7BD27F51F6900122BE0 /* GRDB+Notifications.swift in Sources */,
|
FD17D7BD27F51F6900122BE0 /* GRDB+Notifications.swift in Sources */,
|
||||||
|
FD17D7CA27F546D900122BE0 /* _001_InitialSetupMigration.swift in Sources */,
|
||||||
C3D9E4D12567777D0040E4F3 /* OWSMediaUtils.swift in Sources */,
|
C3D9E4D12567777D0040E4F3 /* OWSMediaUtils.swift in Sources */,
|
||||||
C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Description.swift in Sources */,
|
C3BBE0AA2554D4DE0050F1E3 /* Dictionary+Description.swift in Sources */,
|
||||||
C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */,
|
C3D9E4DA256778410040E4F3 /* UIImage+OWS.m in Sources */,
|
||||||
|
@ -4727,6 +4787,7 @@
|
||||||
FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */,
|
FD17D7C727F5207C00122BE0 /* DatabaseMigrator+Utilities.swift in Sources */,
|
||||||
C3D9E3C925676AF30040E4F3 /* TSYapDatabaseObject.m in Sources */,
|
C3D9E3C925676AF30040E4F3 /* TSYapDatabaseObject.m in Sources */,
|
||||||
C352A3A62557B60D00338F3E /* TSRequest.m in Sources */,
|
C352A3A62557B60D00338F3E /* TSRequest.m in Sources */,
|
||||||
|
FD09796B27F6C67500936362 /* Failable.swift in Sources */,
|
||||||
FD705A92278D051200F16121 /* ReusableView.swift in Sources */,
|
FD705A92278D051200F16121 /* ReusableView.swift in Sources */,
|
||||||
FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */,
|
FD17D7BA27F51F2100122BE0 /* TargetMigrations.swift in Sources */,
|
||||||
FD17D7C327F5204C00122BE0 /* Database+Utilities.swift in Sources */,
|
FD17D7C327F5204C00122BE0 /* Database+Utilities.swift in Sources */,
|
||||||
|
@ -4753,8 +4814,10 @@
|
||||||
C32C5E0C256DDAFA003C73A2 /* NSRegularExpression+SSK.swift in Sources */,
|
C32C5E0C256DDAFA003C73A2 /* NSRegularExpression+SSK.swift in Sources */,
|
||||||
C3BBE0A92554D4DE0050F1E3 /* HTTP.swift in Sources */,
|
C3BBE0A92554D4DE0050F1E3 /* HTTP.swift in Sources */,
|
||||||
B8856D23256F116B001CE70E /* Weak.swift in Sources */,
|
B8856D23256F116B001CE70E /* Weak.swift in Sources */,
|
||||||
|
FD17D7CD27F546FF00122BE0 /* Setting.swift in Sources */,
|
||||||
C32C5A48256DB8F0003C73A2 /* BuildConfiguration.swift in Sources */,
|
C32C5A48256DB8F0003C73A2 /* BuildConfiguration.swift in Sources */,
|
||||||
FD17D7BF27F51F8200122BE0 /* ColumnExpressible.swift in Sources */,
|
FD17D7BF27F51F8200122BE0 /* ColumnExpressible.swift in Sources */,
|
||||||
|
FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */,
|
||||||
B87EF18126377A1D00124B3C /* Features.swift in Sources */,
|
B87EF18126377A1D00124B3C /* Features.swift in Sources */,
|
||||||
C300A60D2554B31900555489 /* Logging.swift in Sources */,
|
C300A60D2554B31900555489 /* Logging.swift in Sources */,
|
||||||
B8FF8EA625C11FEF004D1F22 /* IPv4.swift in Sources */,
|
B8FF8EA625C11FEF004D1F22 /* IPv4.swift in Sources */,
|
||||||
|
@ -4762,6 +4825,7 @@
|
||||||
FD705A8E278CE29800F16121 /* String+Localization.swift in Sources */,
|
FD705A8E278CE29800F16121 /* String+Localization.swift in Sources */,
|
||||||
C3A7219A2558C1660043A11F /* AnyPromise+Conversion.swift in Sources */,
|
C3A7219A2558C1660043A11F /* AnyPromise+Conversion.swift in Sources */,
|
||||||
C300A6322554B6D100555489 /* NSDate+Timestamp.mm in Sources */,
|
C300A6322554B6D100555489 /* NSDate+Timestamp.mm in Sources */,
|
||||||
|
FD17D7E727F6A16700122BE0 /* _002_YDBToGRDBMigration.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
@ -4848,9 +4912,7 @@
|
||||||
C3D9E3BE25676AD70040E4F3 /* TSAttachmentPointer.m in Sources */,
|
C3D9E3BE25676AD70040E4F3 /* TSAttachmentPointer.m in Sources */,
|
||||||
C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */,
|
C3ECBF7B257056B700EA7FCE /* Threading.swift in Sources */,
|
||||||
C32C5AAB256DBE8F003C73A2 /* TSIncomingMessage+Conversion.swift in Sources */,
|
C32C5AAB256DBE8F003C73A2 /* TSIncomingMessage+Conversion.swift in Sources */,
|
||||||
B866CE112581C1A900535CC4 /* Sodium+Conversion.swift in Sources */,
|
|
||||||
C32C5A88256DBCF9003C73A2 /* MessageReceiver+Handling.swift in Sources */,
|
C32C5A88256DBCF9003C73A2 /* MessageReceiver+Handling.swift in Sources */,
|
||||||
C32C5C1B256DC9E0003C73A2 /* General.swift in Sources */,
|
|
||||||
C32C5A02256DB658003C73A2 /* MessageSender+Convenience.swift in Sources */,
|
C32C5A02256DB658003C73A2 /* MessageSender+Convenience.swift in Sources */,
|
||||||
B8566C6C256F60F50045A0B9 /* OWSUserProfile.m in Sources */,
|
B8566C6C256F60F50045A0B9 /* OWSUserProfile.m in Sources */,
|
||||||
B8D0A25925E367AC00C1835E /* Notification+MessageReceiver.swift in Sources */,
|
B8D0A25925E367AC00C1835E /* Notification+MessageReceiver.swift in Sources */,
|
||||||
|
@ -4905,7 +4967,6 @@
|
||||||
C32C5EBA256DE130003C73A2 /* OWSQuotedReplyModel.m in Sources */,
|
C32C5EBA256DE130003C73A2 /* OWSQuotedReplyModel.m in Sources */,
|
||||||
C32C5B62256DC333003C73A2 /* OWSDisappearingConfigurationUpdateInfoMessage.m in Sources */,
|
C32C5B62256DC333003C73A2 /* OWSDisappearingConfigurationUpdateInfoMessage.m in Sources */,
|
||||||
C352A2F525574B4700338F3E /* Job.swift in Sources */,
|
C352A2F525574B4700338F3E /* Job.swift in Sources */,
|
||||||
C32C5C01256DC9A0003C73A2 /* OWSIdentityManager.m in Sources */,
|
|
||||||
C32C59C4256DB41F003C73A2 /* TSContactThread.m in Sources */,
|
C32C59C4256DB41F003C73A2 /* TSContactThread.m in Sources */,
|
||||||
C32C5AB0256DBE8F003C73A2 /* TSOutgoingMessage.m in Sources */,
|
C32C5AB0256DBE8F003C73A2 /* TSOutgoingMessage.m in Sources */,
|
||||||
B82A0C3826B9098200C1BCE3 /* MessageInvalidator.swift in Sources */,
|
B82A0C3826B9098200C1BCE3 /* MessageInvalidator.swift in Sources */,
|
||||||
|
@ -4975,7 +5036,6 @@
|
||||||
FD659AC027A7649600F12C02 /* MessageRequestsViewController.swift in Sources */,
|
FD659AC027A7649600F12C02 /* MessageRequestsViewController.swift in Sources */,
|
||||||
B8F5F72325F1B4CA003BF8D4 /* DownloadAttachmentModal.swift in Sources */,
|
B8F5F72325F1B4CA003BF8D4 /* DownloadAttachmentModal.swift in Sources */,
|
||||||
C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */,
|
C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */,
|
||||||
C31FFE57254A5FFE00F19441 /* KeyPairUtilities.swift in Sources */,
|
|
||||||
B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */,
|
B8D84EA325DF745A005A043E /* LinkPreviewState.swift in Sources */,
|
||||||
45C0DC1E1E69011F00E04C47 /* UIStoryboard+OWS.swift in Sources */,
|
45C0DC1E1E69011F00E04C47 /* UIStoryboard+OWS.swift in Sources */,
|
||||||
FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */,
|
FD88BAD927A7439C00BBC442 /* MessageRequestsCell.swift in Sources */,
|
||||||
|
@ -5248,7 +5308,7 @@
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist;
|
INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -5326,7 +5386,7 @@
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist;
|
INFOPLIST_FILE = SessionShareExtension/Meta/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -5385,7 +5445,7 @@
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist;
|
INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -5464,7 +5524,7 @@
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist;
|
INFOPLIST_FILE = SessionNotificationServiceExtension/Meta/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -5521,7 +5581,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
INFOPLIST_FILE = SessionUIKit/Meta/Info.plist;
|
INFOPLIST_FILE = SessionUIKit/Meta/Info.plist;
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -5600,7 +5660,7 @@
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
INFOPLIST_FILE = SessionUIKit/Meta/Info.plist;
|
INFOPLIST_FILE = SessionUIKit/Meta/Info.plist;
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -5668,7 +5728,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
INFOPLIST_FILE = SignalUtilitiesKit/Meta/Info.plist;
|
INFOPLIST_FILE = SignalUtilitiesKit/Meta/Info.plist;
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -5755,7 +5815,7 @@
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
INFOPLIST_FILE = SignalUtilitiesKit/Meta/Info.plist;
|
INFOPLIST_FILE = SignalUtilitiesKit/Meta/Info.plist;
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -5815,7 +5875,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist;
|
INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist;
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -5894,7 +5954,7 @@
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist;
|
INFOPLIST_FILE = SessionSnodeKit/Meta/Info.plist;
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -5954,7 +6014,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
INFOPLIST_FILE = SessionUtilitiesKit/Meta/Info.plist;
|
INFOPLIST_FILE = SessionUtilitiesKit/Meta/Info.plist;
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -6042,7 +6102,7 @@
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
INFOPLIST_FILE = SessionUtilitiesKit/Meta/Info.plist;
|
INFOPLIST_FILE = SessionUtilitiesKit/Meta/Info.plist;
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -6111,7 +6171,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
INFOPLIST_FILE = SessionMessagingKit/Meta/Info.plist;
|
INFOPLIST_FILE = SessionMessagingKit/Meta/Info.plist;
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -6190,7 +6250,7 @@
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
INFOPLIST_FILE = SessionMessagingKit/Meta/Info.plist;
|
INFOPLIST_FILE = SessionMessagingKit/Meta/Info.plist;
|
||||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -6407,7 +6467,7 @@
|
||||||
"\"$(SRCROOT)/Libraries\"/**",
|
"\"$(SRCROOT)/Libraries\"/**",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = "Session/Meta/Session-Info.plist";
|
INFOPLIST_FILE = "Session/Meta/Session-Info.plist";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -6478,7 +6538,7 @@
|
||||||
"\"$(SRCROOT)/Libraries\"/**",
|
"\"$(SRCROOT)/Libraries\"/**",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = "Session/Meta/Session-Info.plist";
|
INFOPLIST_FILE = "Session/Meta/Session-Info.plist";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
import SessionUIKit
|
import SessionUIKit
|
||||||
import SessionMessagingKit
|
import SessionMessagingKit
|
||||||
import UIKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// • Slight paging glitch when scrolling up and loading more content
|
// • Slight paging glitch when scrolling up and loading more content
|
||||||
|
@ -69,13 +72,12 @@ final class ConversationVC : BaseVC, ConversationViewModelDelegate, OWSConversat
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy var mnemonic: String = {
|
lazy var mnemonic: String = {
|
||||||
let identityManager = OWSIdentityManager.shared()
|
if let hexEncodedSeed: String = Identity.fetchHexEncodedSeed() {
|
||||||
let databaseConnection = identityManager.value(forKey: "dbConnection") as! YapDatabaseConnection
|
return Mnemonic.encode(hexEncodedString: hexEncodedSeed)
|
||||||
var hexEncodedSeed: String! = databaseConnection.object(forKey: "LKLokiSeed", inCollection: OWSPrimaryStorageIdentityKeyStoreCollection) as! String?
|
|
||||||
if hexEncodedSeed == nil {
|
|
||||||
hexEncodedSeed = identityManager.identityKeyPair()!.hexEncodedPrivateKey // Legacy account
|
|
||||||
}
|
}
|
||||||
return Mnemonic.encode(hexEncodedString: hexEncodedSeed)
|
|
||||||
|
// Legacy account
|
||||||
|
return Mnemonic.encode(hexEncodedString: Identity.fetchUserKeyPair()!.hexEncodedPrivateKey)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
lazy var viewModel = ConversationViewModel(thread: thread, focusMessageIdOnOpen: nil, delegate: self)
|
lazy var viewModel = ConversationViewModel(thread: thread, focusMessageIdOnOpen: nil, delegate: self)
|
||||||
|
|
|
@ -1005,7 +1005,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
|
||||||
|
|
||||||
// If it's an incoming message the user must have moderator status
|
// If it's an incoming message the user must have moderator status
|
||||||
if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) {
|
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; }
|
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 it's an incoming message the user must have moderator status
|
||||||
if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) {
|
if (self.interaction.interactionType == OWSInteractionType_IncomingMessage) {
|
||||||
NSString *userPublicKey = [LKStorage.shared getUserPublicKey];
|
NSString *userPublicKey = [SNGeneralUtilities getUserPublicKey];
|
||||||
if (openGroupV2 != nil) {
|
if (openGroupV2 != nil) {
|
||||||
if (![SNOpenGroupAPIV2 isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; }
|
if (![SNOpenGroupAPIV2 isUserModerator:userPublicKey forRoom:openGroupV2.room onServer:openGroupV2.server]) { return; }
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,10 +114,6 @@ CGFloat kIconViewLength = 24;
|
||||||
|
|
||||||
- (void)observeNotifications
|
- (void)observeNotifications
|
||||||
{
|
{
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
||||||
selector:@selector(identityStateDidChange:)
|
|
||||||
name:kNSNotificationName_IdentityStateDidChange
|
|
||||||
object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
||||||
selector:@selector(otherUsersProfileDidChange:)
|
selector:@selector(otherUsersProfileDidChange:)
|
||||||
name:kNSNotificationName_OtherUsersProfileDidChange
|
name:kNSNotificationName_OtherUsersProfileDidChange
|
||||||
|
@ -1069,13 +1065,6 @@ CGFloat kIconViewLength = 24;
|
||||||
|
|
||||||
#pragma mark - Notifications
|
#pragma mark - Notifications
|
||||||
|
|
||||||
- (void)identityStateDidChange:(NSNotification *)notification
|
|
||||||
{
|
|
||||||
OWSAssertIsOnMainThread();
|
|
||||||
|
|
||||||
[self updateTableContents];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)otherUsersProfileDidChange:(NSNotification *)notification
|
- (void)otherUsersProfileDidChange:(NSNotification *)notification
|
||||||
{
|
{
|
||||||
OWSAssertIsOnMainThread();
|
OWSAssertIsOnMainThread();
|
||||||
|
|
|
@ -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
|
// See https://github.com/yapstudios/YapDatabase/wiki/LongLivedReadTransactions and
|
||||||
// https://github.com/yapstudios/YapDatabase/wiki/YapDatabaseModifiedNotification for
|
// 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
|
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)
|
// 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
|
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||||
appDelegate.startPollerIfNeeded()
|
appDelegate.startPollerIfNeeded()
|
||||||
appDelegate.startClosedGroupPoller()
|
appDelegate.startClosedGroupPoller()
|
||||||
|
|
|
@ -748,7 +748,7 @@ static NSTimeInterval launchStartedAt;
|
||||||
[[LKPushNotificationAPI unregisterToken:deviceToken] retainUntilComplete];
|
[[LKPushNotificationAPI unregisterToken:deviceToken] retainUntilComplete];
|
||||||
}
|
}
|
||||||
[ThreadUtil deleteAllContent];
|
[ThreadUtil deleteAllContent];
|
||||||
[SSKEnvironment.shared.identityManager clearIdentityKey];
|
[SUKIdentity clearUserKeyPair];
|
||||||
[SNSnodeAPI clearSnodePool];
|
[SNSnodeAPI clearSnodePool];
|
||||||
[self stopPoller];
|
[self stopPoller];
|
||||||
[self stopClosedGroupPoller];
|
[self stopClosedGroupPoller];
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import PromiseKit
|
import PromiseKit
|
||||||
import SessionMessagingKit
|
import SessionMessagingKit
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
extension AppDelegate {
|
extension AppDelegate {
|
||||||
|
|
||||||
|
@ -22,7 +26,8 @@ extension AppDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func startClosedGroupPoller() {
|
@objc func startClosedGroupPoller() {
|
||||||
guard OWSIdentityManager.shared().identityKeyPair() != nil else { return }
|
guard Identity.fetchUserKeyPair() != nil else { return }
|
||||||
|
|
||||||
ClosedGroupPoller.shared.start()
|
ClosedGroupPoller.shared.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
#import <SessionMessagingKit/Environment.h>
|
#import <SessionMessagingKit/Environment.h>
|
||||||
#import <SignalUtilitiesKit/OWSProfileManager.h>
|
#import <SignalUtilitiesKit/OWSProfileManager.h>
|
||||||
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
|
#import <SignalUtilitiesKit/SignalUtilitiesKit-Swift.h>
|
||||||
#import <SessionMessagingKit/OWSIdentityManager.h>
|
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,6 @@
|
||||||
#import <SignalUtilitiesKit/OWSDispatch.h>
|
#import <SignalUtilitiesKit/OWSDispatch.h>
|
||||||
#import <SignalUtilitiesKit/OWSError.h>
|
#import <SignalUtilitiesKit/OWSError.h>
|
||||||
#import <SessionUtilitiesKit/OWSFileSystem.h>
|
#import <SessionUtilitiesKit/OWSFileSystem.h>
|
||||||
#import <SessionMessagingKit/OWSIdentityManager.h>
|
|
||||||
#import <SessionMessagingKit/OWSMediaGalleryFinder.h>
|
#import <SessionMessagingKit/OWSMediaGalleryFinder.h>
|
||||||
#import <SessionMessagingKit/OWSRecipientIdentity.h>
|
#import <SessionMessagingKit/OWSRecipientIdentity.h>
|
||||||
#import <SignalUtilitiesKit/SignalAccount.h>
|
#import <SignalUtilitiesKit/SignalAccount.h>
|
||||||
|
|
|
@ -121,10 +121,6 @@ public class NotificationPresenter: NSObject, NotificationsProtocol {
|
||||||
|
|
||||||
// MARK: - Dependencies
|
// MARK: - Dependencies
|
||||||
|
|
||||||
var identityManager: OWSIdentityManager {
|
|
||||||
return OWSIdentityManager.shared()
|
|
||||||
}
|
|
||||||
|
|
||||||
var preferences: OWSPreferences {
|
var preferences: OWSPreferences {
|
||||||
return Environment.shared.preferences
|
return Environment.shared.preferences
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
// MARK: Components
|
||||||
private lazy var fakeChatView: FakeChatView = {
|
private lazy var fakeChatView: FakeChatView = {
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
import PromiseKit
|
import PromiseKit
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
final class LinkDeviceVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate {
|
final class LinkDeviceVC : BaseVC, UIPageViewControllerDataSource, UIPageViewControllerDelegate, OWSQRScannerDelegate {
|
||||||
private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
|
private let pageVC = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
|
||||||
|
@ -130,7 +134,7 @@ final class LinkDeviceVC : BaseVC, UIPageViewControllerDataSource, UIPageViewCon
|
||||||
presentAlert(alert)
|
presentAlert(alert)
|
||||||
return
|
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)
|
Onboarding.Flow.link.preregister(with: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair)
|
||||||
TSAccountManager.sharedInstance().didRegister()
|
TSAccountManager.sharedInstance().didRegister()
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(handleInitialConfigurationMessageReceived), name: .initialConfigurationMessageReceived, object: nil)
|
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) {
|
@objc private func handleInitialConfigurationMessageReceived(_ notification: Notification) {
|
||||||
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey
|
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = getUserHexEncodedPublicKey()
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.navigationController!.dismiss(animated: true) {
|
self.navigationController!.dismiss(animated: true) {
|
||||||
let pnModeVC = PNModeVC()
|
let pnModeVC = PNModeVC()
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import Sodium
|
import Sodium
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
enum Onboarding {
|
enum Onboarding {
|
||||||
|
|
||||||
|
@ -7,7 +11,7 @@ enum Onboarding {
|
||||||
|
|
||||||
func preregister(with seed: Data, ed25519KeyPair: Sign.KeyPair, x25519KeyPair: ECKeyPair) {
|
func preregister(with seed: Data, ed25519KeyPair: Sign.KeyPair, x25519KeyPair: ECKeyPair) {
|
||||||
let userDefaults = UserDefaults.standard
|
let userDefaults = UserDefaults.standard
|
||||||
KeyPairUtilities.store(seed: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair)
|
Identity.store(seed: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair)
|
||||||
let x25519PublicKey = x25519KeyPair.hexEncodedPublicKey
|
let x25519PublicKey = x25519KeyPair.hexEncodedPublicKey
|
||||||
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = x25519PublicKey
|
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = x25519PublicKey
|
||||||
Storage.writeSync { transaction in
|
Storage.writeSync { transaction in
|
||||||
|
@ -16,25 +20,31 @@ enum Onboarding {
|
||||||
user.didApproveMe = true
|
user.didApproveMe = true
|
||||||
Storage.shared.setContact(user, using: transaction)
|
Storage.shared.setContact(user, using: transaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch self {
|
switch self {
|
||||||
case .register:
|
case .register:
|
||||||
userDefaults[.hasViewedSeed] = false
|
userDefaults[.hasViewedSeed] = false
|
||||||
// Set hasSyncedInitialConfiguration to true so that when we hit the home screen a configuration sync
|
// Set hasSyncedInitialConfiguration to true so that when we hit the
|
||||||
// is triggered (yes, the logic is a bit weird). This is needed so that if the user registers and
|
// home screen a configuration sync is triggered (yes, the logic is a
|
||||||
// immediately links a device, there'll be a configuration in their swarm.
|
// bit weird). This is needed so that if the user registers and
|
||||||
userDefaults[.hasSyncedInitialConfiguration] = true
|
// immediately links a device, there'll be a configuration in their swarm.
|
||||||
case .recover, .link:
|
userDefaults[.hasSyncedInitialConfiguration] = true
|
||||||
userDefaults[.hasViewedSeed] = true // No need to show it again if the user is restoring or linking
|
|
||||||
userDefaults[.hasSyncedInitialConfiguration] = false
|
case .recover, .link:
|
||||||
|
// No need to show it again if the user is restoring or linking
|
||||||
|
userDefaults[.hasViewedSeed] = true
|
||||||
|
userDefaults[.hasSyncedInitialConfiguration] = false
|
||||||
}
|
}
|
||||||
|
|
||||||
switch self {
|
switch self {
|
||||||
case .register, .recover:
|
case .register, .recover:
|
||||||
// Set both lastDisplayNameUpdate and lastProfilePictureUpdate to the current date, so that
|
// Set both lastDisplayNameUpdate and lastProfilePictureUpdate to the
|
||||||
// we don't overwrite what the user set in the display name step with whatever we find in
|
// current date, so that we don't overwrite what the user set in the
|
||||||
// their swarm.
|
// display name step with whatever we find in their swarm.
|
||||||
userDefaults[.lastDisplayNameUpdate] = Date()
|
userDefaults[.lastDisplayNameUpdate] = Date()
|
||||||
userDefaults[.lastProfilePictureUpdate] = Date()
|
userDefaults[.lastProfilePictureUpdate] = Date()
|
||||||
case .link: break
|
|
||||||
|
case .link: break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,7 +136,7 @@ final class RegisterVC : BaseVC {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateKeyPair() {
|
private func updateKeyPair() {
|
||||||
(ed25519KeyPair, x25519KeyPair) = KeyPairUtilities.generate(from: seed)
|
(ed25519KeyPair, x25519KeyPair) = try! Identity.generate(from: seed)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatePublicKeyLabel() {
|
private func updatePublicKeyLabel() {
|
||||||
|
|
|
@ -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 spacer1HeightConstraint: NSLayoutConstraint!
|
||||||
private var spacer2HeightConstraint: NSLayoutConstraint!
|
private var spacer2HeightConstraint: NSLayoutConstraint!
|
||||||
private var spacer3HeightConstraint: NSLayoutConstraint!
|
private var spacer3HeightConstraint: NSLayoutConstraint!
|
||||||
|
@ -164,7 +168,7 @@ final class RestoreVC : BaseVC {
|
||||||
do {
|
do {
|
||||||
let hexEncodedSeed = try Mnemonic.decode(mnemonic: mnemonic)
|
let hexEncodedSeed = try Mnemonic.decode(mnemonic: mnemonic)
|
||||||
let seed = Data(hex: hexEncodedSeed)
|
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)
|
Onboarding.Flow.recover.preregister(with: seed, ed25519KeyPair: ed25519KeyPair, x25519KeyPair: x25519KeyPair)
|
||||||
mnemonicTextView.resignFirstResponder()
|
mnemonicTextView.resignFirstResponder()
|
||||||
Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in
|
Timer.scheduledTimer(withTimeInterval: 0.25, repeats: false) { _ in
|
||||||
|
|
|
@ -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 = {
|
private let mnemonic: String = {
|
||||||
let identityManager = OWSIdentityManager.shared()
|
if let hexEncodedSeed: String = Identity.fetchHexEncodedSeed() {
|
||||||
let databaseConnection = identityManager.value(forKey: "dbConnection") as! YapDatabaseConnection
|
return Mnemonic.encode(hexEncodedString: hexEncodedSeed)
|
||||||
var hexEncodedSeed: String! = databaseConnection.object(forKey: "LKLokiSeed", inCollection: OWSPrimaryStorageIdentityKeyStoreCollection) as! String?
|
|
||||||
if hexEncodedSeed == nil {
|
|
||||||
hexEncodedSeed = identityManager.identityKeyPair()!.hexEncodedPrivateKey // Legacy account
|
|
||||||
}
|
}
|
||||||
return Mnemonic.encode(hexEncodedString: hexEncodedSeed)
|
|
||||||
|
// Legacy account
|
||||||
|
return Mnemonic.encode(hexEncodedString: Identity.fetchUserKeyPair()!.hexEncodedPrivateKey)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var redactedMnemonic: String = {
|
private lazy var redactedMnemonic: String = {
|
||||||
|
|
|
@ -19,10 +19,7 @@ final class PathStatusView : UIView {
|
||||||
private func setUpViewHierarchy() {
|
private func setUpViewHierarchy() {
|
||||||
layer.cornerRadius = PathStatusView.size / 2
|
layer.cornerRadius = PathStatusView.size / 2
|
||||||
layer.masksToBounds = false
|
layer.masksToBounds = false
|
||||||
if OnionRequestAPI.paths.isEmpty {
|
let color = (!OnionRequestAPI.paths.isEmpty ? Colors.accent : Colors.pathsBuilding)
|
||||||
OnionRequestAPI.paths = Storage.shared.getOnionRequestPaths()
|
|
||||||
}
|
|
||||||
let color = (!OnionRequestAPI.paths.isEmpty) ? Colors.accent : Colors.pathsBuilding
|
|
||||||
setColor(to: color, isAnimated: false)
|
setColor(to: color, isAnimated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -106,26 +106,49 @@ final class PathVC : BaseVC {
|
||||||
|
|
||||||
private func update() {
|
private func update() {
|
||||||
pathStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
pathStackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
|
||||||
if !OnionRequestAPI.paths.isEmpty {
|
|
||||||
let pathToDisplay = OnionRequestAPI.paths.first!
|
guard let pathToDisplay: [Snode] = OnionRequestAPI.paths.first else {
|
||||||
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 {
|
|
||||||
spinner.startAnimating()
|
spinner.startAnimating()
|
||||||
|
|
||||||
UIView.animate(withDuration: 0.25) {
|
UIView.animate(withDuration: 0.25) {
|
||||||
self.spinner.alpha = 1
|
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
|
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 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: "")
|
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)
|
return getPathRow(title: title, subtitle: country, location: location, dotAnimationStartDelay: dotAnimationStartDelay, dotAnimationRepeatInterval: dotAnimationRepeatInterval)
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
@objc(LKSeedModal)
|
@objc(LKSeedModal)
|
||||||
final class SeedModal : Modal {
|
final class SeedModal: Modal {
|
||||||
|
|
||||||
private let mnemonic: String = {
|
private let mnemonic: String = {
|
||||||
let identityManager = OWSIdentityManager.shared()
|
if let hexEncodedSeed: String = Identity.fetchHexEncodedSeed() {
|
||||||
let databaseConnection = identityManager.value(forKey: "dbConnection") as! YapDatabaseConnection
|
return Mnemonic.encode(hexEncodedString: hexEncodedSeed)
|
||||||
var hexEncodedSeed: String! = databaseConnection.object(forKey: "LKLokiSeed", inCollection: OWSPrimaryStorageIdentityKeyStoreCollection) as! String?
|
|
||||||
if hexEncodedSeed == nil {
|
|
||||||
hexEncodedSeed = identityManager.identityKeyPair()!.hexEncodedPrivateKey // Legacy account
|
|
||||||
}
|
}
|
||||||
return Mnemonic.encode(hexEncodedString: hexEncodedSeed)
|
|
||||||
|
// Legacy account
|
||||||
|
return Mnemonic.encode(hexEncodedString: Identity.fetchUserKeyPair()!.hexEncodedPrivateKey)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// MARK: Lifecycle
|
// MARK: Lifecycle
|
||||||
|
|
|
@ -42,18 +42,29 @@ public final class BackgroundPoller : NSObject {
|
||||||
guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic }
|
guard let snode = swarm.randomElement() else { throw SnodeAPI.Error.generic }
|
||||||
return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) {
|
return attempt(maxRetryCount: 4, recoveringOn: DispatchQueue.main) {
|
||||||
return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise<Void> in
|
return SnodeAPI.getRawMessages(from: snode, associatedWith: publicKey).then(on: DispatchQueue.main) { rawResponse -> Promise<Void> in
|
||||||
let (messages, lastRawMessage) = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey)
|
let messages: [SnodeReceivedMessage] = SnodeAPI.parseRawMessagesResponse(rawResponse, from: snode, associatedWith: publicKey)
|
||||||
let promises = messages.compactMap { json -> Promise<Void>? in
|
let promises = messages.compactMap { message -> Promise<Void>? in
|
||||||
// Use a best attempt approach here; we don't want to fail the entire process if one of the
|
// Use a best attempt approach here; we don't want to fail the entire process
|
||||||
// messages failed to parse.
|
// if one of the messages failed to parse
|
||||||
guard let envelope = SNProtoEnvelope.from(json),
|
guard
|
||||||
let data = try? envelope.serializedData() else { return nil }
|
let envelope = SNProtoEnvelope.from(message),
|
||||||
let job = MessageReceiveJob(data: data, serverHash: json["hash"] as? String, isBackgroundPoll: true)
|
let data = try? envelope.serializedData()
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
|
let job = MessageReceiveJob(
|
||||||
|
data: data,
|
||||||
|
serverHash: message.info.hash,
|
||||||
|
isBackgroundPoll: true
|
||||||
|
)
|
||||||
return job.execute()
|
return job.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that the MessageReceiveJob's have been created we can update the `lastMessageHash` value
|
// Now that the MessageReceiveJob's have been created we can persist the received messages
|
||||||
SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: publicKey, from: lastRawMessage)
|
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
|
return when(fulfilled: promises) // The promise returned by MessageReceiveJob never rejects
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
import Foundation
|
||||||
|
import GRDB
|
||||||
|
import SessionSnodeKit
|
||||||
|
|
||||||
final class IP2Country {
|
final class IP2Country {
|
||||||
var countryNamesCache: [String:String] = [:]
|
var countryNamesCache: [String:String] = [:]
|
||||||
|
@ -53,12 +56,8 @@ final class IP2Country {
|
||||||
}
|
}
|
||||||
|
|
||||||
func populateCacheIfNeeded() -> Bool {
|
func populateCacheIfNeeded() -> Bool {
|
||||||
if OnionRequestAPI.paths.isEmpty {
|
guard let pathToDisplay: [Snode] = OnionRequestAPI.paths.first else { return false }
|
||||||
OnionRequestAPI.paths = Storage.shared.getOnionRequestPaths()
|
|
||||||
}
|
|
||||||
let paths = OnionRequestAPI.paths
|
|
||||||
guard !paths.isEmpty else { return false }
|
|
||||||
let pathToDisplay = paths.first!
|
|
||||||
pathToDisplay.forEach { snode in
|
pathToDisplay.forEach { snode in
|
||||||
let _ = self.cacheCountry(for: snode.ip) // Preload if needed
|
let _ = self.cacheCountry(for: snode.ip) // Preload if needed
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -106,7 +106,7 @@ enum MockDataGenerator {
|
||||||
(0..<dmThreadCount).forEach { threadIndex in
|
(0..<dmThreadCount).forEach { threadIndex in
|
||||||
let data = Data((0..<16).map { _ in UInt8.random(in: (UInt8.min...UInt8.max), using: &dmThreadRandomGenerator) })
|
let data = Data((0..<16).map { _ in UInt8.random(in: (UInt8.min...UInt8.max), using: &dmThreadRandomGenerator) })
|
||||||
|
|
||||||
let randomSessionId: String = KeyPairUtilities.generate(from: data).x25519KeyPair.hexEncodedPublicKey
|
let randomSessionId: String = try! Identity.generate(from: data).x25519KeyPair.hexEncodedPublicKey
|
||||||
let isMessageRequest: Bool = Bool.random(using: &dmThreadRandomGenerator)
|
let isMessageRequest: Bool = Bool.random(using: &dmThreadRandomGenerator)
|
||||||
let contactNameLength: Int = ((5..<20).randomElement(using: &dmThreadRandomGenerator) ?? 0)
|
let contactNameLength: Int = ((5..<20).randomElement(using: &dmThreadRandomGenerator) ?? 0)
|
||||||
let numMessages: Int = ((0..<maxMessagesPerThread).randomElement(using: &dmThreadRandomGenerator) ?? 0)
|
let numMessages: Int = ((0..<maxMessagesPerThread).randomElement(using: &dmThreadRandomGenerator) ?? 0)
|
||||||
|
@ -158,7 +158,7 @@ enum MockDataGenerator {
|
||||||
|
|
||||||
(0..<closedGroupThreadCount).forEach { threadIndex in
|
(0..<closedGroupThreadCount).forEach { threadIndex in
|
||||||
let data = Data((0..<16).map { _ in UInt8.random(in: (UInt8.min...UInt8.max), using: &cgThreadRandomGenerator) })
|
let data = Data((0..<16).map { _ in UInt8.random(in: (UInt8.min...UInt8.max), using: &cgThreadRandomGenerator) })
|
||||||
let randomGroupPublicKey: String = KeyPairUtilities.generate(from: data).x25519KeyPair.hexEncodedPublicKey
|
let randomGroupPublicKey: String = try! Identity.generate(from: data).x25519KeyPair.hexEncodedPublicKey
|
||||||
let groupNameLength: Int = ((5..<20).randomElement(using: &cgThreadRandomGenerator) ?? 0)
|
let groupNameLength: Int = ((5..<20).randomElement(using: &cgThreadRandomGenerator) ?? 0)
|
||||||
let groupName: String = (0..<groupNameLength)
|
let groupName: String = (0..<groupNameLength)
|
||||||
.compactMap { _ in stringContent.randomElement(using: &cgThreadRandomGenerator) }
|
.compactMap { _ in stringContent.randomElement(using: &cgThreadRandomGenerator) }
|
||||||
|
@ -171,7 +171,7 @@ enum MockDataGenerator {
|
||||||
|
|
||||||
(0..<numGroupMembers).forEach { _ in
|
(0..<numGroupMembers).forEach { _ in
|
||||||
let contactData = Data((0..<16).map { _ in UInt8.random(in: (UInt8.min...UInt8.max), using: &cgThreadRandomGenerator) })
|
let contactData = Data((0..<16).map { _ in UInt8.random(in: (UInt8.min...UInt8.max), using: &cgThreadRandomGenerator) })
|
||||||
let randomSessionId: String = KeyPairUtilities.generate(from: contactData).x25519KeyPair.hexEncodedPublicKey
|
let randomSessionId: String = try! Identity.generate(from: contactData).x25519KeyPair.hexEncodedPublicKey
|
||||||
let contactNameLength: Int = ((5..<20).randomElement(using: &cgThreadRandomGenerator) ?? 0)
|
let contactNameLength: Int = ((5..<20).randomElement(using: &cgThreadRandomGenerator) ?? 0)
|
||||||
let contact = Contact(sessionID: randomSessionId)
|
let contact = Contact(sessionID: randomSessionId)
|
||||||
contact.name = (0..<contactNameLength)
|
contact.name = (0..<contactNameLength)
|
||||||
|
@ -229,7 +229,7 @@ enum MockDataGenerator {
|
||||||
|
|
||||||
(0..<openGroupThreadCount).forEach { threadIndex in
|
(0..<openGroupThreadCount).forEach { threadIndex in
|
||||||
let data = Data((0..<16).map { _ in UInt8.random(in: (UInt8.min...UInt8.max), using: &ogThreadRandomGenerator) })
|
let data = Data((0..<16).map { _ in UInt8.random(in: (UInt8.min...UInt8.max), using: &ogThreadRandomGenerator) })
|
||||||
let randomGroupPublicKey: String = KeyPairUtilities.generate(from: data).x25519KeyPair.hexEncodedPublicKey
|
let randomGroupPublicKey: String = try! Identity.generate(from: data).x25519KeyPair.hexEncodedPublicKey
|
||||||
let serverNameLength: Int = ((5..<20).randomElement(using: &ogThreadRandomGenerator) ?? 0)
|
let serverNameLength: Int = ((5..<20).randomElement(using: &ogThreadRandomGenerator) ?? 0)
|
||||||
let roomNameLength: Int = ((5..<20).randomElement(using: &ogThreadRandomGenerator) ?? 0)
|
let roomNameLength: Int = ((5..<20).randomElement(using: &ogThreadRandomGenerator) ?? 0)
|
||||||
let serverName: String = (0..<serverNameLength)
|
let serverName: String = (0..<serverNameLength)
|
||||||
|
|
|
@ -17,30 +17,10 @@ struct Place: Codable, FetchableRecord, PersistableRecord, ColumnExpressible {
|
||||||
let name: String
|
let name: String
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Setting: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
|
|
||||||
static var databaseTableName: String { "settings" }
|
|
||||||
|
|
||||||
public enum Columns: String, CodingKey, ColumnExpression {
|
|
||||||
case key
|
|
||||||
case value
|
|
||||||
}
|
|
||||||
|
|
||||||
let key: String
|
|
||||||
let value: Data
|
|
||||||
}
|
|
||||||
|
|
||||||
enum _001_InitialSetupMigration: Migration {
|
enum _001_InitialSetupMigration: Migration {
|
||||||
static let identifier: String = "initialSetup"
|
static let identifier: String = "initialSetup"
|
||||||
|
|
||||||
static func migrate(_ db: Database) throws {
|
static func migrate(_ db: Database) throws {
|
||||||
try db.create(table: Setting.self) { t in
|
|
||||||
t.column(.key, .text)
|
|
||||||
.notNull()
|
|
||||||
.unique(onConflict: .abort)
|
|
||||||
.primaryKey()
|
|
||||||
t.column(.value, .blob).notNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
try db.create(table: Place.self) { t in
|
try db.create(table: Place.self) { t in
|
||||||
t.column(.id, .text).notNull().primaryKey()
|
t.column(.id, .text).notNull().primaryKey()
|
||||||
t.column(.name, .text).notNull()
|
t.column(.name, .text).notNull()
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Curve25519Kit
|
||||||
|
|
||||||
extension Storage {
|
extension Storage {
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ extension Storage {
|
||||||
guard let threadID = getOrCreateThread(for: message.syncTarget ?? message.sender!, groupPublicKey: groupPublicKey, openGroupID: openGroupID, using: transaction),
|
guard let threadID = getOrCreateThread(for: message.syncTarget ?? message.sender!, groupPublicKey: groupPublicKey, openGroupID: openGroupID, using: transaction),
|
||||||
let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return nil }
|
let thread = TSThread.fetch(uniqueId: threadID, transaction: transaction) else { return nil }
|
||||||
let tsMessage: TSMessage
|
let tsMessage: TSMessage
|
||||||
if message.sender == getUserPublicKey() {
|
if message.sender == getUserHexEncodedPublicKey() {
|
||||||
if TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) != nil { return nil }
|
if TSOutgoingMessage.find(withTimestamp: message.sentTimestamp!) != nil { return nil }
|
||||||
let tsOutgoingMessage = TSOutgoingMessage.from(message, associatedWith: thread, using: transaction)
|
let tsOutgoingMessage = TSOutgoingMessage.from(message, associatedWith: thread, using: transaction)
|
||||||
var recipients: [String] = []
|
var recipients: [String] = []
|
||||||
|
|
|
@ -17,24 +17,6 @@ extension Storage {
|
||||||
Storage.writeSync { block($0) }
|
Storage.writeSync { block($0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc public func getUserPublicKey() -> 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? {
|
@objc public func getUser() -> Contact? {
|
||||||
return getUser(using: nil)
|
return getUser(using: nil)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Curve25519Kit
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
public final class ClosedGroupControlMessage : ControlMessage {
|
public final class ClosedGroupControlMessage : ControlMessage {
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Curve25519Kit
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
@objc(SNConfigurationMessage)
|
@objc(SNConfigurationMessage)
|
||||||
|
|
|
@ -14,7 +14,6 @@ FOUNDATION_EXPORT const unsigned char SessionMessagingKitVersionString[];
|
||||||
#import <SessionMessagingKit/OWSDisappearingMessagesConfiguration.h>
|
#import <SessionMessagingKit/OWSDisappearingMessagesConfiguration.h>
|
||||||
#import <SessionMessagingKit/OWSDisappearingMessagesFinder.h>
|
#import <SessionMessagingKit/OWSDisappearingMessagesFinder.h>
|
||||||
#import <SessionMessagingKit/OWSDisappearingMessagesJob.h>
|
#import <SessionMessagingKit/OWSDisappearingMessagesJob.h>
|
||||||
#import <SessionMessagingKit/OWSIdentityManager.h>
|
|
||||||
#import <SessionMessagingKit/OWSIncomingMessageFinder.h>
|
#import <SessionMessagingKit/OWSIncomingMessageFinder.h>
|
||||||
#import <SessionMessagingKit/OWSMediaGalleryFinder.h>
|
#import <SessionMessagingKit/OWSMediaGalleryFinder.h>
|
||||||
#import <SessionMessagingKit/OWSOutgoingReceiptManager.h>
|
#import <SessionMessagingKit/OWSOutgoingReceiptManager.h>
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import PromiseKit
|
import PromiseKit
|
||||||
|
import Curve25519Kit
|
||||||
import SessionSnodeKit
|
import SessionSnodeKit
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
@objc(SNOpenGroupAPIV2)
|
@objc(SNOpenGroupAPIV2)
|
||||||
public final class OpenGroupAPIV2 : NSObject {
|
public final class OpenGroupAPIV2 : NSObject {
|
||||||
private static var authTokenPromises: [String:Promise<String>] = [:]
|
private static var authTokenPromises: [String: Promise<String>] = [:]
|
||||||
private static var hasPerformedInitialPoll: [String:Bool] = [:]
|
private static var hasPerformedInitialPoll: [String: Bool] = [:]
|
||||||
private static var hasUpdatedLastOpenDate = false
|
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 let workQueue = DispatchQueue(label: "OpenGroupAPIV2.workQueue", qos: .userInitiated) // It's important that this is a serial queue
|
||||||
public static var moderators: [String:[String:Set<String>]] = [:] // Server URL to room ID to set of moderator IDs
|
public static var moderators: [String:[String:Set<String>]] = [:] // 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<String> {
|
public static func requestNewAuthToken(for room: String, on server: String) -> Promise<String> {
|
||||||
SNLog("Requesting auth token for server: \(server).")
|
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 queryParameters = [ "public_key" : getUserHexEncodedPublicKey() ]
|
||||||
let request = Request(verb: .get, room: room, server: server, endpoint: "auth_token_challenge", queryParameters: queryParameters, isAuthRequired: false)
|
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
|
return send(request).map(on: OpenGroupAPIV2.workQueue) { json in
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Curve25519Kit
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
public struct OpenGroupMessageV2 {
|
public struct OpenGroupMessageV2 {
|
||||||
public let serverID: Int64?
|
public let serverID: Int64?
|
||||||
|
@ -10,8 +15,11 @@ public struct OpenGroupMessageV2 {
|
||||||
public let base64EncodedSignature: String?
|
public let base64EncodedSignature: String?
|
||||||
|
|
||||||
public func sign() -> OpenGroupMessageV2? {
|
public func sign() -> OpenGroupMessageV2? {
|
||||||
let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair()!
|
guard
|
||||||
let data = Data(base64Encoded: base64EncodedData)!
|
let userKeyPair = Identity.fetchUserKeyPair(),
|
||||||
|
let data: Data = Data(base64Encoded: base64EncodedData)
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
guard let signature = try? Ed25519.sign(data, with: userKeyPair) else {
|
guard let signature = try? Ed25519.sign(data, with: userKeyPair) else {
|
||||||
SNLog("Failed to sign open group message.")
|
SNLog("Failed to sign open group message.")
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import CryptoSwift
|
import CryptoSwift
|
||||||
import SessionUtilitiesKit
|
|
||||||
import Sodium
|
import Sodium
|
||||||
|
import Curve25519Kit
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
extension MessageReceiver {
|
extension MessageReceiver {
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Curve25519Kit
|
||||||
import SignalCoreKit
|
import SignalCoreKit
|
||||||
import SessionSnodeKit
|
import SessionSnodeKit
|
||||||
|
|
||||||
|
@ -556,7 +560,7 @@ extension MessageReceiver {
|
||||||
let groupPublicKey = explicitGroupPublicKey?.toHexString() ?? message.groupPublicKey else { return }
|
let groupPublicKey = explicitGroupPublicKey?.toHexString() ?? message.groupPublicKey else { return }
|
||||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||||
let userPublicKey = getUserHexEncodedPublicKey()
|
let userPublicKey = getUserHexEncodedPublicKey()
|
||||||
guard let userKeyPair = SNMessagingKitConfiguration.shared.storage.getUserKeyPair() else {
|
guard let userKeyPair = Identity.fetchUserKeyPair() else {
|
||||||
return SNLog("Couldn't find user X25519 key pair.")
|
return SNLog("Couldn't find user X25519 key pair.")
|
||||||
}
|
}
|
||||||
let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey)
|
let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey)
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
public enum MessageReceiver {
|
public enum MessageReceiver {
|
||||||
private static var lastEncryptionKeyPairRequest: [String:Date] = [:]
|
private static var lastEncryptionKeyPairRequest: [String: Date] = [:]
|
||||||
|
|
||||||
public enum Error : LocalizedError {
|
public enum Error : LocalizedError {
|
||||||
case duplicateMessage
|
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) {
|
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)
|
let isOpenGroupMessage = (openGroupMessageServerID != nil)
|
||||||
// Parse the envelope
|
// Parse the envelope
|
||||||
let envelope = try SNProtoEnvelope.parseData(data)
|
let envelope = try SNProtoEnvelope.parseData(data)
|
||||||
|
@ -64,7 +67,7 @@ public enum MessageReceiver {
|
||||||
} else {
|
} else {
|
||||||
switch envelope.type {
|
switch envelope.type {
|
||||||
case .sessionMessage:
|
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)
|
(plaintext, sender) = try decryptWithSessionProtocol(ciphertext: ciphertext, using: userX25519KeyPair)
|
||||||
case .closedGroupMessage:
|
case .closedGroupMessage:
|
||||||
guard let hexEncodedGroupPublicKey = envelope.source, SNMessagingKitConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey) else { throw Error.invalidGroupPublicKey }
|
guard let hexEncodedGroupPublicKey = envelope.source, SNMessagingKitConfiguration.shared.storage.isClosedGroup(hexEncodedGroupPublicKey) else { throw Error.invalidGroupPublicKey }
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Curve25519Kit
|
||||||
import PromiseKit
|
import PromiseKit
|
||||||
|
|
||||||
extension MessageSender {
|
extension MessageSender {
|
||||||
public static var distributingClosedGroupEncryptionKeyPairs: [String:[ECKeyPair]] = [:]
|
public static var distributingClosedGroupEncryptionKeyPairs: [String: [ECKeyPair]] = [:]
|
||||||
|
|
||||||
public static func createClosedGroup(name: String, members: Set<String>, transaction: YapDatabaseReadWriteTransaction) -> Promise<TSGroupThread> {
|
public static func createClosedGroup(name: String, members: Set<String>, transaction: YapDatabaseReadWriteTransaction) -> Promise<TSGroupThread> {
|
||||||
// Prepare
|
// Prepare
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import SessionUtilitiesKit
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import Sodium
|
import Sodium
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
extension MessageSender {
|
extension MessageSender {
|
||||||
|
|
||||||
internal static func encryptWithSessionProtocol(_ plaintext: Data, for recipientHexEncodedX25519PublicKey: String) throws -> Data {
|
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 recipientX25519PublicKey = Data(hex: recipientHexEncodedX25519PublicKey.removing05PrefixIfNeeded())
|
||||||
let sodium = Sodium()
|
let sodium = Sodium()
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,7 @@ public final class MessageSender : NSObject {
|
||||||
let (promise, seal) = Promise<Void>.pending()
|
let (promise, seal) = Promise<Void>.pending()
|
||||||
let storage = SNMessagingKitConfiguration.shared.storage
|
let storage = SNMessagingKitConfiguration.shared.storage
|
||||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||||
let userPublicKey = storage.getUserPublicKey()
|
let userPublicKey = getUserHexEncodedPublicKey()
|
||||||
var isMainAppAndActive = false
|
var isMainAppAndActive = false
|
||||||
if let sharedUserDefaults = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") {
|
if let sharedUserDefaults = UserDefaults(suiteName: "group.com.loki-project.loki-messenger") {
|
||||||
isMainAppAndActive = sharedUserDefaults.bool(forKey: "isMainAppActive")
|
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
|
if message.sentTimestamp == nil { // Visible messages will already have their sent timestamp set
|
||||||
message.sentTimestamp = NSDate.millisecondTimestamp()
|
message.sentTimestamp = NSDate.millisecondTimestamp()
|
||||||
}
|
}
|
||||||
message.sender = storage.getUserPublicKey()
|
message.sender = getUserHexEncodedPublicKey()
|
||||||
switch destination {
|
switch destination {
|
||||||
case .contact(_): preconditionFailure()
|
case .contact(_): preconditionFailure()
|
||||||
case .closedGroup(_): preconditionFailure()
|
case .closedGroup(_): preconditionFailure()
|
||||||
|
|
|
@ -100,26 +100,29 @@ public final class ClosedGroupPoller : NSObject {
|
||||||
|
|
||||||
private func poll(_ groupPublicKey: String) -> Promise<Void> {
|
private func poll(_ groupPublicKey: String) -> Promise<Void> {
|
||||||
guard isPolling(for: groupPublicKey) else { return Promise.value(()) }
|
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
|
// 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 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 {
|
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 }
|
guard let self = self, self.isPolling(for: groupPublicKey) else { return }
|
||||||
if !rawMessages.isEmpty {
|
if !messages.isEmpty {
|
||||||
SNLog("Received \(rawMessages.count) new message(s) in closed group with public key: \(groupPublicKey).")
|
SNLog("Received \(messages.count) new message(s) in closed group with public key: \(groupPublicKey).")
|
||||||
}
|
}
|
||||||
rawMessages.forEach { json in
|
messages.forEach { message in
|
||||||
guard let envelope = SNProtoEnvelope.from(json) else { return }
|
guard let envelope = SNProtoEnvelope.from(message) else { return }
|
||||||
do {
|
do {
|
||||||
let data = try envelope.serializedData()
|
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
|
SNMessagingKitConfiguration.shared.storage.write { transaction in
|
||||||
SessionMessagingKit.JobQueue.shared.add(job, using: transaction)
|
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
|
// Now that the MessageReceiveJob's have been created we can persist the received messages
|
||||||
SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: groupPublicKey, from: lastRawMessage)
|
if !messages.isEmpty {
|
||||||
|
GRDBStorage.shared.write { db in
|
||||||
|
messages.forEach { try? $0.info.save(db) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
promise.catch2 { error in
|
promise.catch2 { error in
|
||||||
SNLog("Polling failed for closed group with public key: \(groupPublicKey) due to error: \(error).")
|
SNLog("Polling failed for closed group with public key: \(groupPublicKey) due to error: \(error).")
|
||||||
|
|
|
@ -5,7 +5,7 @@ import PromiseKit
|
||||||
public final class Poller : NSObject {
|
public final class Poller : NSObject {
|
||||||
private let storage = OWSPrimaryStorage.shared()
|
private let storage = OWSPrimaryStorage.shared()
|
||||||
private var isPolling = false
|
private var isPolling = false
|
||||||
private var usedSnodes = Set<SessionSnodeKit.Legacy.Snode>()
|
private var usedSnodes = Set<Snode>()
|
||||||
private var pollCount = 0
|
private var pollCount = 0
|
||||||
|
|
||||||
// MARK: Settings
|
// MARK: Settings
|
||||||
|
@ -66,7 +66,7 @@ public final class Poller : NSObject {
|
||||||
private func pollNextSnode(seal: Resolver<Void>) {
|
private func pollNextSnode(seal: Resolver<Void>) {
|
||||||
let userPublicKey = getUserHexEncodedPublicKey()
|
let userPublicKey = getUserHexEncodedPublicKey()
|
||||||
let swarm = SnodeAPI.swarmCache[userPublicKey] ?? []
|
let swarm = SnodeAPI.swarmCache[userPublicKey] ?? []
|
||||||
let unusedSnodes = Set(swarm).subtracting(usedSnodes)
|
let unusedSnodes = swarm.subtracting(usedSnodes)
|
||||||
if !unusedSnodes.isEmpty {
|
if !unusedSnodes.isEmpty {
|
||||||
// randomElement() uses the system's default random generator, which is cryptographically secure
|
// randomElement() uses the system's default random generator, which is cryptographically secure
|
||||||
let nextSnode = unusedSnodes.randomElement()!
|
let nextSnode = unusedSnodes.randomElement()!
|
||||||
|
@ -89,20 +89,20 @@ public final class Poller : NSObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func poll(_ snode: SessionSnodeKit.Legacy.Snode, seal longTermSeal: Resolver<Void>) -> Promise<Void> {
|
private func poll(_ snode: Snode, seal longTermSeal: Resolver<Void>) -> Promise<Void> {
|
||||||
guard isPolling else { return Promise { $0.fulfill(()) } }
|
guard isPolling else { return Promise { $0.fulfill(()) } }
|
||||||
let userPublicKey = getUserHexEncodedPublicKey()
|
let userPublicKey = getUserHexEncodedPublicKey()
|
||||||
return SnodeAPI.getRawMessages(from: snode, associatedWith: userPublicKey).then(on: Threading.pollerQueue) { [weak self] rawResponse -> Promise<Void> in
|
return SnodeAPI.getRawMessages(from: snode, associatedWith: userPublicKey).then(on: Threading.pollerQueue) { [weak self] rawResponse -> Promise<Void> in
|
||||||
guard let strongSelf = self, strongSelf.isPolling else { return Promise { $0.fulfill(()) } }
|
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 {
|
if !messages.isEmpty {
|
||||||
SNLog("Received \(messages.count) new message(s).")
|
SNLog("Received \(messages.count) new message(s).")
|
||||||
}
|
}
|
||||||
messages.forEach { json in
|
messages.forEach { message in
|
||||||
guard let envelope = SNProtoEnvelope.from(json) else { return }
|
guard let envelope = SNProtoEnvelope.from(message) else { return }
|
||||||
do {
|
do {
|
||||||
let data = try envelope.serializedData()
|
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
|
SNMessagingKitConfiguration.shared.storage.write { transaction in
|
||||||
SessionMessagingKit.JobQueue.shared.add(job, using: transaction)
|
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
|
// Now that the MessageReceiveJob's have been created we can persist the received messages
|
||||||
SnodeAPI.updateLastMessageHashValueIfPossible(for: snode, associatedWith: userPublicKey, from: lastRawMessage)
|
if !messages.isEmpty {
|
||||||
|
GRDBStorage.shared.write { db in
|
||||||
|
messages.forEach { try? $0.info.save(db) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.pollCount += 1
|
strongSelf.pollCount += 1
|
||||||
if strongSelf.pollCount == Poller.maxPollCount {
|
|
||||||
|
guard strongSelf.pollCount < Poller.maxPollCount else {
|
||||||
throw Error.pollLimitReached
|
throw Error.pollLimitReached
|
||||||
} else {
|
}
|
||||||
return withDelay(Poller.pollInterval, completionQueue: Threading.pollerQueue) {
|
|
||||||
guard let strongSelf = self, strongSelf.isPolling else { return Promise { $0.fulfill(()) } }
|
return withDelay(Poller.pollInterval, completionQueue: Threading.pollerQueue) {
|
||||||
return strongSelf.poll(snode, seal: longTermSeal)
|
guard let strongSelf = self, strongSelf.isPolling else { return Promise { $0.fulfill(()) } }
|
||||||
}
|
return strongSelf.poll(snode, seal: longTermSeal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,6 @@ public protocol SessionMessagingKitStorageProtocol {
|
||||||
|
|
||||||
// MARK: - General
|
// MARK: - General
|
||||||
|
|
||||||
func getUserPublicKey() -> String?
|
|
||||||
func getUserKeyPair() -> ECKeyPair?
|
|
||||||
func getUserED25519KeyPair() -> Box.KeyPair?
|
|
||||||
func getUser() -> Contact?
|
func getUser() -> Contact?
|
||||||
func getUser(using transaction: YapDatabaseReadTransaction?) -> Contact?
|
func getUser(using transaction: YapDatabaseReadTransaction?) -> Contact?
|
||||||
func getAllContacts() -> Set<Contact>
|
func getAllContacts() -> Set<Contact>
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "OWSRecipientIdentity.h"
|
#import "OWSRecipientIdentity.h"
|
||||||
#import "OWSIdentityManager.h"
|
|
||||||
#import "OWSPrimaryStorage.h"
|
#import "OWSPrimaryStorage.h"
|
||||||
#import <SignalCoreKit/Cryptography.h>
|
#import <SignalCoreKit/Cryptography.h>
|
||||||
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
|
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import Foundation
|
|
||||||
|
|
||||||
public enum General {
|
|
||||||
public enum Cache {
|
|
||||||
public static var cachedEncodedPublicKey: Atomic<String?> = 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 ""
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
//
|
|
||||||
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
#import <Curve25519Kit/Curve25519.h>
|
|
||||||
#import <YapDatabase/YapDatabase.h>
|
|
||||||
|
|
||||||
@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
|
|
|
@ -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 <Curve25519Kit/Curve25519.h>
|
|
||||||
#import <SignalCoreKit/NSDate+OWS.h>
|
|
||||||
#import <SessionMessagingKit/SessionMessagingKit-Swift.h>
|
|
||||||
#import <SessionUtilitiesKit/SessionUtilitiesKit.h>
|
|
||||||
#import <YapDatabase/YapDatabase.h>
|
|
||||||
|
|
||||||
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<NSString *> *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
|
|
|
@ -1,15 +1,15 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import SessionSnodeKit
|
||||||
|
|
||||||
public extension SNProtoEnvelope {
|
public extension SNProtoEnvelope {
|
||||||
|
static func from(_ message: SnodeReceivedMessage) -> SNProtoEnvelope? {
|
||||||
static func from(_ json: JSON) -> SNProtoEnvelope? {
|
guard let result = try? MessageWrapper.unwrap(data: message.data) else {
|
||||||
guard let base64EncodedData = json["data"] as? String, let data = Data(base64Encoded: base64EncodedData) else {
|
SNLog("Failed to unwrap data for message: \(String(reflecting: message)).")
|
||||||
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).")
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
- (instancetype)initWithProfileManager:(id<ProfileManagerProtocol>)profileManager
|
- (instancetype)initWithProfileManager:(id<ProfileManagerProtocol>)profileManager
|
||||||
primaryStorage:(OWSPrimaryStorage *)primaryStorage
|
primaryStorage:(OWSPrimaryStorage *)primaryStorage
|
||||||
identityManager:(OWSIdentityManager *)identityManager
|
|
||||||
tsAccountManager:(TSAccountManager *)tsAccountManager
|
tsAccountManager:(TSAccountManager *)tsAccountManager
|
||||||
disappearingMessagesJob:(OWSDisappearingMessagesJob *)disappearingMessagesJob
|
disappearingMessagesJob:(OWSDisappearingMessagesJob *)disappearingMessagesJob
|
||||||
readReceiptManager:(OWSReadReceiptManager *)readReceiptManager
|
readReceiptManager:(OWSReadReceiptManager *)readReceiptManager
|
||||||
|
|
|
@ -14,7 +14,6 @@ static SSKEnvironment *sharedSSKEnvironment;
|
||||||
|
|
||||||
@property (nonatomic) id<ProfileManagerProtocol> profileManager;
|
@property (nonatomic) id<ProfileManagerProtocol> profileManager;
|
||||||
@property (nonatomic) OWSPrimaryStorage *primaryStorage;
|
@property (nonatomic) OWSPrimaryStorage *primaryStorage;
|
||||||
@property (nonatomic) OWSIdentityManager *identityManager;
|
|
||||||
@property (nonatomic) TSAccountManager *tsAccountManager;
|
@property (nonatomic) TSAccountManager *tsAccountManager;
|
||||||
@property (nonatomic) OWSDisappearingMessagesJob *disappearingMessagesJob;
|
@property (nonatomic) OWSDisappearingMessagesJob *disappearingMessagesJob;
|
||||||
@property (nonatomic) OWSReadReceiptManager *readReceiptManager;
|
@property (nonatomic) OWSReadReceiptManager *readReceiptManager;
|
||||||
|
@ -36,7 +35,6 @@ static SSKEnvironment *sharedSSKEnvironment;
|
||||||
|
|
||||||
- (instancetype)initWithProfileManager:(id<ProfileManagerProtocol>)profileManager
|
- (instancetype)initWithProfileManager:(id<ProfileManagerProtocol>)profileManager
|
||||||
primaryStorage:(OWSPrimaryStorage *)primaryStorage
|
primaryStorage:(OWSPrimaryStorage *)primaryStorage
|
||||||
identityManager:(OWSIdentityManager *)identityManager
|
|
||||||
tsAccountManager:(TSAccountManager *)tsAccountManager
|
tsAccountManager:(TSAccountManager *)tsAccountManager
|
||||||
disappearingMessagesJob:(OWSDisappearingMessagesJob *)disappearingMessagesJob
|
disappearingMessagesJob:(OWSDisappearingMessagesJob *)disappearingMessagesJob
|
||||||
readReceiptManager:(OWSReadReceiptManager *)readReceiptManager
|
readReceiptManager:(OWSReadReceiptManager *)readReceiptManager
|
||||||
|
@ -52,7 +50,6 @@ static SSKEnvironment *sharedSSKEnvironment;
|
||||||
|
|
||||||
_profileManager = profileManager;
|
_profileManager = profileManager;
|
||||||
_primaryStorage = primaryStorage;
|
_primaryStorage = primaryStorage;
|
||||||
_identityManager = identityManager;
|
|
||||||
_tsAccountManager = tsAccountManager;
|
_tsAccountManager = tsAccountManager;
|
||||||
_disappearingMessagesJob = disappearingMessagesJob;
|
_disappearingMessagesJob = disappearingMessagesJob;
|
||||||
_readReceiptManager = readReceiptManager;
|
_readReceiptManager = readReceiptManager;
|
||||||
|
|
|
@ -28,7 +28,7 @@ public class NSENotificationPresenter: NSObject, NotificationsProtocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
let senderPublicKey = incomingMessage.authorId
|
let senderPublicKey = incomingMessage.authorId
|
||||||
let userPublicKey = GeneralUtilities.getUserPublicKey()
|
let userPublicKey = getUserHexEncodedPublicKey()
|
||||||
guard senderPublicKey != userPublicKey else {
|
guard senderPublicKey != userPublicKey else {
|
||||||
// Ignore PNs for messages sent by the current user
|
// Ignore PNs for messages sent by the current user
|
||||||
// after handling the message. Otherwise the closed
|
// after handling the message. Otherwise the closed
|
||||||
|
|
|
@ -2,8 +2,6 @@ import Foundation
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
public struct SNSnodeKitConfiguration {
|
public struct SNSnodeKitConfiguration {
|
||||||
public let storage: SessionSnodeKitStorageProtocol
|
|
||||||
|
|
||||||
internal static var shared: SNSnodeKitConfiguration!
|
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) {
|
public static func configure() {
|
||||||
SNSnodeKitConfiguration.shared = SNSnodeKitConfiguration(storage: storage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,12 @@ public enum Legacy {
|
||||||
// MARK: - Collections and Keys
|
// MARK: - Collections and Keys
|
||||||
|
|
||||||
internal static let swarmCollectionPrefix = "LokiSwarmCollection-"
|
internal static let swarmCollectionPrefix = "LokiSwarmCollection-"
|
||||||
|
internal static let lastSnodePoolRefreshDateKey = "lastSnodePoolRefreshDate"
|
||||||
internal static let snodePoolCollection = "LokiSnodePoolCollection"
|
internal static let snodePoolCollection = "LokiSnodePoolCollection"
|
||||||
internal static let onionRequestPathCollection = "LokiOnionRequestPathCollection"
|
internal static let onionRequestPathCollection = "LokiOnionRequestPathCollection"
|
||||||
internal static let lastSnodePoolRefreshDateCollection = "LokiLastSnodePoolRefreshDateCollection"
|
internal static let lastSnodePoolRefreshDateCollection = "LokiLastSnodePoolRefreshDateCollection"
|
||||||
internal static let lastMessageHashCollection = "LokiLastMessageHashCollection" // TODO: Remove this one? (make it a query??)
|
internal static let lastMessageHashCollection = "LokiLastMessageHashCollection" // TODO: Remove this one? (make it a query??)
|
||||||
internal static let receivedMessagesCollection = "LokiReceivedMessagesCollection"
|
internal static let receivedMessagesCollection = "LokiReceivedMessagesCollection"
|
||||||
// TODO: - "lastSnodePoolRefreshDate"
|
|
||||||
|
|
||||||
// MARK: - Types
|
// MARK: - Types
|
||||||
|
|
||||||
|
@ -29,15 +29,6 @@ public enum Legacy {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Nested Types
|
// 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 struct KeySet {
|
||||||
public let ed25519Key: String
|
public let ed25519Key: String
|
||||||
|
|
|
@ -14,8 +14,15 @@ enum _002_YDBToGRDBMigration: Migration {
|
||||||
// Note: Want to exclude the Snode's we already added from the 'onionRequestPathResult'
|
// Note: Want to exclude the Snode's we already added from the 'onionRequestPathResult'
|
||||||
var snodeResult: Set<Legacy.Snode> = []
|
var snodeResult: Set<Legacy.Snode> = []
|
||||||
var snodeSetResult: [String: Set<Legacy.Snode>] = [:]
|
var snodeSetResult: [String: Set<Legacy.Snode>] = [:]
|
||||||
|
var lastSnodePoolRefreshDate: Date? = nil
|
||||||
|
|
||||||
Storage.read { transaction in
|
Storage.read { transaction in
|
||||||
|
// Process the lastSnodePoolRefreshDate
|
||||||
|
lastSnodePoolRefreshDate = transaction.object(
|
||||||
|
forKey: Legacy.lastSnodePoolRefreshDateKey,
|
||||||
|
inCollection: Legacy.lastSnodePoolRefreshDateCollection
|
||||||
|
) as? Date
|
||||||
|
|
||||||
// Process the OnionRequestPaths
|
// Process the OnionRequestPaths
|
||||||
if
|
if
|
||||||
let path0Snode0 = transaction.object(forKey: "0-0", inCollection: Legacy.onionRequestPathCollection) as? Legacy.Snode,
|
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 snodeResult.forEach { legacySnode in
|
||||||
try Snode(
|
try Snode(
|
||||||
address: legacySnode.address,
|
address: legacySnode.address,
|
||||||
|
@ -79,20 +90,13 @@ enum _002_YDBToGRDBMigration: Migration {
|
||||||
// Note: In this case the 'nodeIndex' is irrelivant
|
// Note: In this case the 'nodeIndex' is irrelivant
|
||||||
try SnodeSet(
|
try SnodeSet(
|
||||||
key: key,
|
key: key,
|
||||||
nodeIndex: UInt(nodeIndex),
|
nodeIndex: nodeIndex,
|
||||||
address: legacySnode.address,
|
address: legacySnode.address,
|
||||||
port: legacySnode.port
|
port: legacySnode.port
|
||||||
).insert(db)
|
).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
|
// MARK: - Received Messages & Last Message Hash
|
||||||
|
|
||||||
var lastMessageResults: [String: (hash: String, json: JSON)] = [:]
|
var lastMessageResults: [String: (hash: String, json: JSON)] = [:]
|
||||||
|
|
|
@ -4,18 +4,100 @@ import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
import SessionUtilitiesKit
|
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" }
|
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 {
|
public typealias Columns = CodingKeys
|
||||||
case address
|
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
||||||
case port
|
case address = "public_ip"
|
||||||
case ed25519PublicKey
|
case port = "storage_port"
|
||||||
case x25519PublicKey
|
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..<address.endIndex])
|
||||||
}
|
}
|
||||||
|
|
||||||
let address: String
|
public var snodeSet: QueryInterfaceRequest<SnodeSet> {
|
||||||
let port: UInt16
|
request(for: Snode.snodeSet)
|
||||||
let ed25519PublicKey: String
|
}
|
||||||
let x25519PublicKey: String
|
|
||||||
|
public var description: String { return "\(address):\(port)" }
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Decoder
|
||||||
|
|
||||||
|
extension Snode {
|
||||||
|
public init(from decoder: Decoder) throws {
|
||||||
|
let container: KeyedDecodingContainer<CodingKeys> = 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<Snode> {
|
||||||
|
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)")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,56 @@ import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
struct SnodeReceivedMessageInfo: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
|
public struct SnodeReceivedMessageInfo: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
|
||||||
static var databaseTableName: String { "snodeReceivedMessageInfo" }
|
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 key
|
||||||
case hash
|
case hash
|
||||||
case expirationDateMs
|
case expirationDateMs
|
||||||
}
|
}
|
||||||
|
|
||||||
let key: String
|
public let key: String
|
||||||
let hash: String
|
public let hash: String
|
||||||
let expirationDateMs: Int64
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,24 +4,59 @@ import Foundation
|
||||||
import GRDB
|
import GRDB
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
struct SnodeSet: Codable, FetchableRecord, PersistableRecord, TableRecord, ColumnExpressible {
|
public struct SnodeSet: Codable, FetchableRecord, EncodableRecord, PersistableRecord, TableRecord, ColumnExpressible {
|
||||||
static var databaseTableName: String { "snodeSet" }
|
public static let onionRequestPathPrefix = "OnionRequestPath-"
|
||||||
static let nodes = hasMany(Snode.self)
|
public static var databaseTableName: String { "snodeSetAssociation" }
|
||||||
static let onionRequestPathPrefix = "OnionRequestPath-"
|
static let node = hasOne(Snode.self, using: Snode.snodeSetForeignKey)
|
||||||
|
|
||||||
public enum Columns: String, CodingKey, ColumnExpression {
|
public typealias Columns = CodingKeys
|
||||||
|
public enum CodingKeys: String, CodingKey, ColumnExpression {
|
||||||
case key
|
case key
|
||||||
case nodeIndex
|
case nodeIndex
|
||||||
case address
|
case address
|
||||||
case port
|
case port
|
||||||
}
|
}
|
||||||
|
|
||||||
let key: String
|
public let key: String
|
||||||
let nodeIndex: UInt
|
public let nodeIndex: Int
|
||||||
let address: String
|
public let address: String
|
||||||
let port: UInt16
|
public let port: UInt16
|
||||||
|
|
||||||
var nodes: QueryInterfaceRequest<Snode> {
|
public var node: QueryInterfaceRequest<Snode> {
|
||||||
request(for: SnodeSet.nodes)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
|
extension Setting.DateKey {
|
||||||
|
static let lastSnodePoolRefreshDate: Setting.DateKey = "lastSnodePoolRefreshDate"
|
||||||
|
}
|
||||||
|
|
33
SessionSnodeKit/Models/SnodeReceivedMessage.swift
Normal file
33
SessionSnodeKit/Models/SnodeReceivedMessage.swift
Normal file
|
@ -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())\"}"
|
||||||
|
}
|
||||||
|
}
|
58
SessionSnodeKit/Models/SwarmSnode.swift
Normal file
58
SessionSnodeKit/Models/SwarmSnode.swift
Normal file
|
@ -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<CodingKeys> = 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ internal extension OnionRequestAPI {
|
||||||
// Wrapping isn't needed for file server or open group onion requests
|
// Wrapping isn't needed for file server or open group onion requests
|
||||||
switch destination {
|
switch destination {
|
||||||
case .snode(let snode):
|
case .snode(let snode):
|
||||||
let snodeX25519PublicKey = snode.publicKeySet.x25519Key
|
let snodeX25519PublicKey = snode.x25519PublicKey
|
||||||
let payloadAsData = try JSONSerialization.data(withJSONObject: payload, options: [ .fragmentsAllowed ])
|
let payloadAsData = try JSONSerialization.data(withJSONObject: payload, options: [ .fragmentsAllowed ])
|
||||||
let plaintext = try encode(ciphertext: payloadAsData, json: [ "headers" : "" ])
|
let plaintext = try encode(ciphertext: payloadAsData, json: [ "headers" : "" ])
|
||||||
let result = try AESGCM.encrypt(plaintext, for: snodeX25519PublicKey)
|
let result = try AESGCM.encrypt(plaintext, for: snodeX25519PublicKey)
|
||||||
|
@ -46,7 +46,7 @@ internal extension OnionRequestAPI {
|
||||||
var parameters: JSON
|
var parameters: JSON
|
||||||
switch rhs {
|
switch rhs {
|
||||||
case .snode(let snode):
|
case .snode(let snode):
|
||||||
let snodeED25519PublicKey = snode.publicKeySet.ed25519Key
|
let snodeED25519PublicKey = snode.ed25519PublicKey
|
||||||
parameters = [ "destination" : snodeED25519PublicKey ]
|
parameters = [ "destination" : snodeED25519PublicKey ]
|
||||||
case .server(let host, let target, _, let scheme, let port):
|
case .server(let host, let target, _, let scheme, let port):
|
||||||
let scheme = scheme ?? "https"
|
let scheme = scheme ?? "https"
|
||||||
|
@ -57,7 +57,7 @@ internal extension OnionRequestAPI {
|
||||||
let x25519PublicKey: String
|
let x25519PublicKey: String
|
||||||
switch lhs {
|
switch lhs {
|
||||||
case .snode(let snode):
|
case .snode(let snode):
|
||||||
let snodeX25519PublicKey = snode.publicKeySet.x25519Key
|
let snodeX25519PublicKey = snode.x25519PublicKey
|
||||||
x25519PublicKey = snodeX25519PublicKey
|
x25519PublicKey = snodeX25519PublicKey
|
||||||
case .server(_, _, let serverX25519PublicKey, _, _):
|
case .server(_, _, let serverX25519PublicKey, _, _):
|
||||||
x25519PublicKey = serverX25519PublicKey
|
x25519PublicKey = serverX25519PublicKey
|
||||||
|
|
|
@ -1,19 +1,40 @@
|
||||||
|
import Foundation
|
||||||
import CryptoSwift
|
import CryptoSwift
|
||||||
|
import GRDB
|
||||||
import PromiseKit
|
import PromiseKit
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
/// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information.
|
/// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information.
|
||||||
public enum OnionRequestAPI {
|
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.
|
/// - 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.
|
/// - 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.
|
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
|
||||||
public static var guardSnodes: Set<Legacy.Snode> = []
|
public static var guardSnodes: Set<Snode> = []
|
||||||
public static var paths: [Path] = [] // Not a set to ensure we consistently show the same path to the user
|
|
||||||
|
// 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
|
public static let maxRequestSize = 10_000_000 // 10 MB
|
||||||
/// The number of snodes (including the guard snode) in a path.
|
/// The number of snodes (including the guard snode) in a path.
|
||||||
private static let pathSize: UInt = 3
|
private static let pathSize: UInt = 3
|
||||||
|
@ -27,97 +48,80 @@ public enum OnionRequestAPI {
|
||||||
/// The number of guard snodes required to maintain `targetPathCount` paths.
|
/// The number of guard snodes required to maintain `targetPathCount` paths.
|
||||||
private static var targetGuardSnodeCount: UInt { return targetPathCount } // One per path
|
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
|
// 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
|
// MARK: Private API
|
||||||
/// Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise.
|
/// 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<Void> {
|
private static func testSnode(_ snode: Snode) -> Promise<Void> {
|
||||||
let (promise, seal) = Promise<Void>.pending()
|
let (promise, seal) = Promise<Void>.pending()
|
||||||
|
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
let url = "\(snode.address):\(snode.port)/get_stats/v1"
|
let url = "\(snode.address):\(snode.port)/get_stats/v1"
|
||||||
let timeout: TimeInterval = 3 // Use a shorter timeout for testing
|
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) }
|
HTTP.execute(.get, url, timeout: timeout)
|
||||||
if version >= "2.0.7" {
|
.done2 { json in
|
||||||
seal.fulfill(())
|
guard let version = json["version"] as? String else {
|
||||||
} else {
|
return seal.reject(Error.missingSnodeVersion)
|
||||||
SNLog("Unsupported snode version: \(version).")
|
}
|
||||||
seal.reject(Error.unsupportedSnodeVersion(version))
|
|
||||||
|
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
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with `Error.insufficientSnodes`
|
/// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with `Error.insufficientSnodes`
|
||||||
/// if not enough (reliable) snodes are available.
|
/// if not enough (reliable) snodes are available.
|
||||||
private static func getGuardSnodes(reusing reusableGuardSnodes: [Legacy.Snode]) -> Promise<Set<Legacy.Snode>> {
|
private static func getGuardSnodes(reusing reusableGuardSnodes: [Snode]) -> Promise<Set<Snode>> {
|
||||||
if guardSnodes.count >= targetGuardSnodeCount {
|
if guardSnodes.count >= targetGuardSnodeCount {
|
||||||
return Promise<Set<Legacy.Snode>> { $0.fulfill(guardSnodes) }
|
return Promise<Set<Snode>> { $0.fulfill(guardSnodes) }
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
SNLog("Populating guard snode cache.")
|
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)
|
let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count)
|
||||||
guard unusedSnodes.count >= (targetGuardSnodeCount - reusableGuardSnodeCount) else { return Promise(error: Error.insufficientSnodes) }
|
|
||||||
func getGuardSnode() -> Promise<Legacy.Snode> {
|
guard unusedSnodes.count >= (targetGuardSnodeCount - reusableGuardSnodeCount) else {
|
||||||
// randomElement() uses the system's default random generator, which is cryptographically secure
|
return Promise(error: Error.insufficientSnodes)
|
||||||
guard let candidate = unusedSnodes.randomElement() else { return Promise<Legacy.Snode> { $0.reject(Error.insufficientSnodes) } }
|
}
|
||||||
|
|
||||||
|
func getGuardSnode() -> Promise<Snode> {
|
||||||
|
// randomElement() uses the system's default random generator, which
|
||||||
|
// is cryptographically secure
|
||||||
|
guard let candidate = unusedSnodes.randomElement() else {
|
||||||
|
return Promise<Snode> { $0.reject(Error.insufficientSnodes) }
|
||||||
|
}
|
||||||
|
|
||||||
unusedSnodes.remove(candidate) // All used snodes should be unique
|
unusedSnodes.remove(candidate) // All used snodes should be unique
|
||||||
SNLog("Testing guard snode: \(candidate).")
|
SNLog("Testing guard snode: \(candidate).")
|
||||||
|
|
||||||
// Loop until a reliable guard snode is found
|
// Loop until a reliable guard snode is found
|
||||||
return testSnode(candidate).map2 { candidate }.recover(on: DispatchQueue.main) { _ in
|
return testSnode(candidate).map2 { candidate }.recover(on: DispatchQueue.main) { _ in
|
||||||
withDelay(0.1, completionQueue: Threading.workQueue) { getGuardSnode() }
|
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
|
return when(fulfilled: promises).map2 { guardSnodes in
|
||||||
let guardSnodesAsSet = Set(guardSnodes + reusableGuardSnodes)
|
let guardSnodesAsSet = Set(guardSnodes + reusableGuardSnodes)
|
||||||
OnionRequestAPI.guardSnodes = guardSnodesAsSet
|
OnionRequestAPI.guardSnodes = guardSnodesAsSet
|
||||||
|
|
||||||
return guardSnodesAsSet
|
return guardSnodesAsSet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,40 +130,50 @@ public enum OnionRequestAPI {
|
||||||
/// Builds and returns `targetPathCount` paths. The returned promise errors out with `Error.insufficientSnodes`
|
/// Builds and returns `targetPathCount` paths. The returned promise errors out with `Error.insufficientSnodes`
|
||||||
/// if not enough (reliable) snodes are available.
|
/// if not enough (reliable) snodes are available.
|
||||||
@discardableResult
|
@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 }
|
if let existingBuildPathsPromise = buildPathsPromise { return existingBuildPathsPromise }
|
||||||
SNLog("Building onion request paths.")
|
SNLog("Building onion request paths.")
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
NotificationCenter.default.post(name: .buildingPaths, object: nil)
|
NotificationCenter.default.post(name: .buildingPaths, object: nil)
|
||||||
}
|
}
|
||||||
let reusableGuardSnodes = reusablePaths.map { $0[0] }
|
let reusableGuardSnodes = reusablePaths.map { $0[0] }
|
||||||
let promise: Promise<[Path]> = getGuardSnodes(reusing: reusableGuardSnodes).map2 { guardSnodes -> [Path] in
|
let promise: Promise<[[Snode]]> = getGuardSnodes(reusing: reusableGuardSnodes)
|
||||||
var unusedSnodes = SnodeAPI.snodePool.subtracting(guardSnodes).subtracting(reusablePaths.flatMap { $0 })
|
.map2 { guardSnodes -> [[Snode]] in
|
||||||
let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count)
|
var unusedSnodes = SnodeAPI.snodePool
|
||||||
let pathSnodeCount = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount)
|
.subtracting(guardSnodes)
|
||||||
guard unusedSnodes.count >= pathSnodeCount else { throw Error.insufficientSnodes }
|
.subtracting(reusablePaths.flatMap { $0 })
|
||||||
// Don't test path snodes as this would reveal the user's IP to them
|
let reusableGuardSnodeCount = UInt(reusableGuardSnodes.count)
|
||||||
return guardSnodes.subtracting(reusableGuardSnodes).map { guardSnode in
|
let pathSnodeCount = (targetGuardSnodeCount - reusableGuardSnodeCount) * pathSize - (targetGuardSnodeCount - reusableGuardSnodeCount)
|
||||||
let result = [ guardSnode ] + (0..<(pathSize - 1)).map { _ in
|
|
||||||
// randomElement() uses the system's default random generator, which is cryptographically secure
|
guard unusedSnodes.count >= pathSnodeCount else { throw Error.insufficientSnodes }
|
||||||
let pathSnode = unusedSnodes.randomElement()! // Safe because of the pathSnodeCount check above
|
|
||||||
unusedSnodes.remove(pathSnode) // All used snodes should be unique
|
// Don't test path snodes as this would reveal the user's IP to them
|
||||||
return pathSnode
|
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
|
.map2 { paths in
|
||||||
OnionRequestAPI.paths = paths + reusablePaths
|
OnionRequestAPI.paths = paths + reusablePaths
|
||||||
SNSnodeKitConfiguration.shared.storage.writeSync { transaction in
|
|
||||||
SNLog("Persisting onion request paths to database.")
|
GRDBStorage.shared.write { db in
|
||||||
SNSnodeKitConfiguration.shared.storage.setOnionRequestPaths(to: paths, using: transaction)
|
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.done2 { _ in buildPathsPromise = nil }
|
||||||
promise.catch2 { _ in buildPathsPromise = nil }
|
promise.catch2 { _ in buildPathsPromise = nil }
|
||||||
buildPathsPromise = promise
|
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.
|
/// 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<Path> {
|
private static func getPath(excluding snode: Snode?) -> Promise<[Snode]> {
|
||||||
guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") }
|
guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") }
|
||||||
var paths = OnionRequestAPI.paths
|
|
||||||
if paths.isEmpty {
|
let paths: [[Snode]] = OnionRequestAPI.paths
|
||||||
paths = SNSnodeKitConfiguration.shared.storage.getOnionRequestPaths()
|
|
||||||
OnionRequestAPI.paths = paths
|
if !paths.isEmpty {
|
||||||
if !paths.isEmpty {
|
guardSnodes.formUnion([ paths[0][0] ])
|
||||||
guardSnodes.formUnion([ paths[0][0] ])
|
|
||||||
if paths.count >= 2 {
|
if paths.count >= 2 {
|
||||||
guardSnodes.formUnion([ paths[1][0] ])
|
guardSnodes.formUnion([ paths[1][0] ])
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// randomElement() uses the system's default random generator, which is cryptographically secure
|
// randomElement() uses the system's default random generator, which is cryptographically secure
|
||||||
if paths.count >= targetPathCount {
|
if paths.count >= targetPathCount {
|
||||||
if let snode = snode {
|
if let snode: Snode = snode {
|
||||||
return Promise { $0.fulfill(paths.filter { !$0.contains(snode) }.randomElement()!) }
|
return Promise { $0.fulfill(paths.filter { !$0.contains(snode) }.randomElement()!) }
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return Promise { $0.fulfill(paths.randomElement()!) }
|
return Promise { $0.fulfill(paths.randomElement()!) }
|
||||||
}
|
}
|
||||||
} else if !paths.isEmpty {
|
}
|
||||||
|
else if !paths.isEmpty {
|
||||||
if let snode = snode {
|
if let snode = snode {
|
||||||
if let path = paths.first(where: { !$0.contains(snode) }) {
|
if let path = paths.first(where: { !$0.contains(snode) }) {
|
||||||
buildPaths(reusing: paths) // Re-build paths in the background
|
buildPaths(reusing: paths) // Re-build paths in the background
|
||||||
return Promise { $0.fulfill(path) }
|
return Promise { $0.fulfill(path) }
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return buildPaths(reusing: paths).map2 { paths in
|
return buildPaths(reusing: paths).map2 { paths in
|
||||||
return paths.filter { !$0.contains(snode) }.randomElement()!
|
return paths.filter { !$0.contains(snode) }.randomElement()!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
buildPaths(reusing: paths) // Re-build paths in the background
|
buildPaths(reusing: paths) // Re-build paths in the background
|
||||||
return Promise { $0.fulfill(paths.randomElement()!) }
|
return Promise { $0.fulfill(paths.randomElement()!) }
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
return buildPaths(reusing: []).map2 { paths in
|
return buildPaths(reusing: []).map2 { paths in
|
||||||
if let snode = snode {
|
if let snode = snode {
|
||||||
if let path = paths.filter({ !$0.contains(snode) }).randomElement() {
|
if let path = paths.filter({ !$0.contains(snode) }).randomElement() {
|
||||||
return path
|
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
|
#if DEBUG
|
||||||
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
||||||
#endif
|
#endif
|
||||||
guardSnodes = guardSnodes.filter { $0 != snode }
|
guardSnodes = guardSnodes.filter { $0 != snode }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func drop(_ snode: Legacy.Snode) throws {
|
private static func drop(_ snode: Snode) throws {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
||||||
#endif
|
#endif
|
||||||
|
@ -244,13 +263,14 @@ public enum OnionRequestAPI {
|
||||||
oldPaths.remove(at: pathIndex)
|
oldPaths.remove(at: pathIndex)
|
||||||
let newPaths = oldPaths + [ path ]
|
let newPaths = oldPaths + [ path ]
|
||||||
paths = newPaths
|
paths = newPaths
|
||||||
SNSnodeKitConfiguration.shared.storage.writeSync { transaction in
|
|
||||||
|
GRDBStorage.shared.write { db in
|
||||||
SNLog("Persisting onion request paths to database.")
|
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
|
#if DEBUG
|
||||||
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
||||||
#endif
|
#endif
|
||||||
|
@ -259,56 +279,69 @@ public enum OnionRequestAPI {
|
||||||
guard let pathIndex = paths.firstIndex(of: path) else { return }
|
guard let pathIndex = paths.firstIndex(of: path) else { return }
|
||||||
paths.remove(at: pathIndex)
|
paths.remove(at: pathIndex)
|
||||||
OnionRequestAPI.paths = paths
|
OnionRequestAPI.paths = paths
|
||||||
SNSnodeKitConfiguration.shared.storage.writeSync { transaction in
|
|
||||||
if !paths.isEmpty {
|
GRDBStorage.shared.write { db in
|
||||||
SNLog("Persisting onion request paths to database.")
|
guard !paths.isEmpty else {
|
||||||
SNSnodeKitConfiguration.shared.storage.setOnionRequestPaths(to: paths, using: transaction)
|
|
||||||
} else {
|
|
||||||
SNLog("Clearing onion request paths.")
|
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.
|
/// Builds an onion around `payload` and returns the result.
|
||||||
private static func buildOnion(around payload: JSON, targetedAt destination: Destination) -> Promise<OnionBuildingResult> {
|
private static func buildOnion(around payload: JSON, targetedAt destination: Destination) -> Promise<OnionBuildingResult> {
|
||||||
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 targetSnodeSymmetricKey: Data! // Needed by invoke(_:on:with:) to decrypt the response sent back by the destination
|
||||||
var encryptionResult: AESGCM.EncryptionResult!
|
var encryptionResult: AESGCM.EncryptionResult!
|
||||||
var snodeToExclude: Legacy.Snode?
|
var snodeToExclude: Snode?
|
||||||
|
|
||||||
if case .snode(let snode) = destination { snodeToExclude = snode }
|
if case .snode(let snode) = destination { snodeToExclude = snode }
|
||||||
return getPath(excluding: snodeToExclude).then2 { path -> Promise<AESGCM.EncryptionResult> in
|
|
||||||
guardSnode = path.first!
|
return getPath(excluding: snodeToExclude)
|
||||||
// Encrypt in reverse order, i.e. the destination first
|
.then2 { path -> Promise<AESGCM.EncryptionResult> in
|
||||||
return encrypt(payload, for: destination).then2 { r -> Promise<AESGCM.EncryptionResult> in
|
guardSnode = path.first!
|
||||||
targetSnodeSymmetricKey = r.symmetricKey
|
// Encrypt in reverse order, i.e. the destination first
|
||||||
// Recursively encrypt the layers of the onion (again in reverse order)
|
return encrypt(payload, for: destination)
|
||||||
encryptionResult = r
|
.then2 { r -> Promise<AESGCM.EncryptionResult> in
|
||||||
var path = path
|
targetSnodeSymmetricKey = r.symmetricKey
|
||||||
var rhs = destination
|
|
||||||
func addLayer() -> Promise<AESGCM.EncryptionResult> {
|
// Recursively encrypt the layers of the onion (again in reverse order)
|
||||||
if path.isEmpty {
|
encryptionResult = r
|
||||||
return Promise<AESGCM.EncryptionResult> { $0.fulfill(encryptionResult) }
|
var path = path
|
||||||
} else {
|
var rhs = destination
|
||||||
let lhs = Destination.snode(path.removeLast())
|
|
||||||
return OnionRequestAPI.encryptHop(from: lhs, to: rhs, using: encryptionResult).then2 { r -> Promise<AESGCM.EncryptionResult> in
|
func addLayer() -> Promise<AESGCM.EncryptionResult> {
|
||||||
encryptionResult = r
|
guard !path.isEmpty else {
|
||||||
rhs = lhs
|
return Promise<AESGCM.EncryptionResult> { $0.fulfill(encryptionResult) }
|
||||||
return addLayer()
|
}
|
||||||
|
|
||||||
|
let lhs = Destination.snode(path.removeLast())
|
||||||
|
return OnionRequestAPI
|
||||||
|
.encryptHop(from: lhs, to: rhs, using: encryptionResult)
|
||||||
|
.then2 { r -> Promise<AESGCM.EncryptionResult> in
|
||||||
|
encryptionResult = r
|
||||||
|
rhs = lhs
|
||||||
|
return addLayer()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return addLayer()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return addLayer()
|
|
||||||
}
|
}
|
||||||
}.map2 { _ in (guardSnode, encryptionResult, targetSnodeSymmetricKey) }
|
.map2 { _ in (guardSnode, encryptionResult, targetSnodeSymmetricKey) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Public API
|
// MARK: Public API
|
||||||
/// Sends an onion request to `snode`. Builds new paths as needed.
|
/// 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<JSON> {
|
public static func sendOnionRequest(to snode: Snode, invoking method: SnodeAPI.Endpoint, with parameters: JSON, associatedWith publicKey: String? = nil) -> Promise<JSON> {
|
||||||
let payload: JSON = [ "method" : method.rawValue, "params" : parameters ]
|
let payload: JSON = [ "method" : method.rawValue, "params" : parameters ]
|
||||||
return sendOnionRequest(with: payload, to: Destination.snode(snode)).recover2 { error -> Promise<JSON> in
|
return sendOnionRequest(with: payload, to: Destination.snode(snode)).recover2 { error -> Promise<JSON> in
|
||||||
guard case OnionRequestAPI.Error.httpRequestFailedAtDestination(let statusCode, let json, _) = error else { throw error }
|
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
|
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<JSON> {
|
public static func sendOnionRequest(with payload: JSON, to destination: Destination) -> Promise<JSON> {
|
||||||
let (promise, seal) = Promise<JSON>.pending()
|
let (promise, seal) = Promise<JSON>.pending()
|
||||||
var guardSnode: Legacy.Snode?
|
var guardSnode: Snode?
|
||||||
Threading.workQueue.async { // Avoid race conditions on `guardSnodes` and `paths`
|
Threading.workQueue.async { // Avoid race conditions on `guardSnodes` and `paths`
|
||||||
buildOnion(around: payload, targetedAt: destination).done2 { intermediate in
|
buildOnion(around: payload, targetedAt: destination).done2 { intermediate in
|
||||||
guardSnode = intermediate.guardSnode
|
guardSnode = intermediate.guardSnode
|
||||||
|
@ -442,7 +475,7 @@ public enum OnionRequestAPI {
|
||||||
let prefix = "Next node not found: "
|
let prefix = "Next node not found: "
|
||||||
if let message = json?["result"] as? String, message.hasPrefix(prefix) {
|
if let message = json?["result"] as? String, message.hasPrefix(prefix) {
|
||||||
let ed25519PublicKey = message[message.index(message.startIndex, offsetBy: prefix.count)..<message.endIndex]
|
let ed25519PublicKey = message[message.index(message.startIndex, offsetBy: prefix.count)..<message.endIndex]
|
||||||
if let path = path, let snode = path.first(where: { $0.publicKeySet.ed25519Key == ed25519PublicKey }) {
|
if let path = path, let snode = path.first(where: { $0.ed25519PublicKey == ed25519PublicKey }) {
|
||||||
var snodeFailureCount = OnionRequestAPI.snodeFailureCount[snode] ?? 0
|
var snodeFailureCount = OnionRequestAPI.snodeFailureCount[snode] ?? 0
|
||||||
snodeFailureCount += 1
|
snodeFailureCount += 1
|
||||||
if snodeFailureCount >= snodeFailureThreshold {
|
if snodeFailureCount >= snodeFailureThreshold {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import PromiseKit
|
import PromiseKit
|
||||||
import SessionUtilitiesKit
|
|
||||||
import Sodium
|
import Sodium
|
||||||
|
import GRDB
|
||||||
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
@objc(SNSnodeAPI)
|
@objc(SNSnodeAPI)
|
||||||
public final class SnodeAPI : NSObject {
|
public final class SnodeAPI : NSObject {
|
||||||
|
@ -8,12 +9,12 @@ public final class SnodeAPI : NSObject {
|
||||||
|
|
||||||
private static var hasLoadedSnodePool = false
|
private static var hasLoadedSnodePool = false
|
||||||
private static var loadedSwarms: Set<String> = []
|
private static var loadedSwarms: Set<String> = []
|
||||||
private static var getSnodePoolPromise: Promise<Set<Legacy.Snode>>?
|
private static var getSnodePoolPromise: Promise<Set<Snode>>?
|
||||||
|
|
||||||
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
|
/// - 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.
|
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
|
||||||
internal static var snodePool: Set<Legacy.Snode> = []
|
internal static var snodePool: Set<Snode> = []
|
||||||
|
|
||||||
/// The offset between the user's clock and the Service Node's clock. Used in cases where the
|
/// The offset between the user's clock and the Service Node's clock. Used in cases where the
|
||||||
/// user's clock is incorrect.
|
/// 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.
|
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
|
||||||
public static var clockOffset: Int64 = 0
|
public static var clockOffset: Int64 = 0
|
||||||
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
|
/// - Note: Should only be accessed from `Threading.workQueue` to avoid race conditions.
|
||||||
public static var swarmCache: [String:Set<Legacy.Snode>] = [:]
|
public static var swarmCache: [String: Set<Snode>] = [:]
|
||||||
|
|
||||||
// MARK: Settings
|
// MARK: Settings
|
||||||
private static let maxRetryCount: UInt = 8
|
private static let maxRetryCount: UInt = 8
|
||||||
|
@ -30,35 +31,6 @@ public final class SnodeAPI : NSObject {
|
||||||
private static let snodeFailureThreshold = 3
|
private static let snodeFailureThreshold = 3
|
||||||
private static let targetSwarmSnodeCount = 2
|
private static let targetSwarmSnodeCount = 2
|
||||||
private static let minSnodePoolCount = 12
|
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
|
// MARK: Type Aliases
|
||||||
public typealias MessageListPromise = Promise<[JSON]>
|
public typealias MessageListPromise = Promise<[JSON]>
|
||||||
|
@ -68,23 +40,30 @@ public final class SnodeAPI : NSObject {
|
||||||
// MARK: Snode Pool Interaction
|
// MARK: Snode Pool Interaction
|
||||||
private static func loadSnodePoolIfNeeded() {
|
private static func loadSnodePoolIfNeeded() {
|
||||||
guard !hasLoadedSnodePool else { return }
|
guard !hasLoadedSnodePool else { return }
|
||||||
snodePool = SNSnodeKitConfiguration.shared.storage.getSnodePool()
|
|
||||||
|
GRDBStorage.shared.read { db in
|
||||||
|
snodePool = ((try? Snode.fetchSet(db)) ?? Set())
|
||||||
|
}
|
||||||
|
|
||||||
hasLoadedSnodePool = true
|
hasLoadedSnodePool = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func setSnodePool(to newValue: Set<Legacy.Snode>, using transaction: Any? = nil) {
|
private static func setSnodePool(to newValue: Set<Snode>, db: Database? = nil) {
|
||||||
snodePool = newValue
|
snodePool = newValue
|
||||||
let storage = SNSnodeKitConfiguration.shared.storage
|
|
||||||
if let transaction = transaction {
|
if let db: Database = db {
|
||||||
storage.setSnodePool(to: newValue, using: transaction)
|
_ = try? Snode.deleteAll(db)
|
||||||
} else {
|
newValue.forEach { try? $0.save(db) }
|
||||||
storage.writeSync { transaction in
|
}
|
||||||
storage.setSnodePool(to: newValue, using: transaction)
|
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
|
#if DEBUG
|
||||||
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
||||||
#endif
|
#endif
|
||||||
|
@ -95,6 +74,7 @@ public final class SnodeAPI : NSObject {
|
||||||
|
|
||||||
@objc public static func clearSnodePool() {
|
@objc public static func clearSnodePool() {
|
||||||
snodePool.removeAll()
|
snodePool.removeAll()
|
||||||
|
|
||||||
Threading.workQueue.async {
|
Threading.workQueue.async {
|
||||||
setSnodePool(to: [])
|
setSnodePool(to: [])
|
||||||
}
|
}
|
||||||
|
@ -103,22 +83,27 @@ public final class SnodeAPI : NSObject {
|
||||||
// MARK: Swarm Interaction
|
// MARK: Swarm Interaction
|
||||||
private static func loadSwarmIfNeeded(for publicKey: String) {
|
private static func loadSwarmIfNeeded(for publicKey: String) {
|
||||||
guard !loadedSwarms.contains(publicKey) else { return }
|
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)
|
loadedSwarms.insert(publicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func setSwarm(to newValue: Set<Legacy.Snode>, for publicKey: String, persist: Bool = true) {
|
private static func setSwarm(to newValue: Set<Snode>, for publicKey: String, persist: Bool = true) {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
||||||
#endif
|
#endif
|
||||||
swarmCache[publicKey] = newValue
|
swarmCache[publicKey] = newValue
|
||||||
guard persist else { return }
|
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
|
#if DEBUG
|
||||||
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
||||||
#endif
|
#endif
|
||||||
|
@ -129,19 +114,21 @@ public final class SnodeAPI : NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Internal API
|
// 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 {
|
if Features.useOnionRequests {
|
||||||
return OnionRequestAPI.sendOnionRequest(to: snode, invoking: method, with: parameters, associatedWith: publicKey).map2 { $0 as Any }
|
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"
|
let url = "\(snode.address):\(snode.port)/storage_rpc/v1"
|
||||||
return HTTP.execute(.post, url, parameters: parameters).map2 { $0 as Any }.recover2 { error -> Promise<Any> in
|
return HTTP.execute(.post, url, parameters: parameters).map2 { $0 as Any }.recover2 { error -> Promise<Any> in
|
||||||
guard case HTTP.Error.httpRequestFailed(let statusCode, let json) = error else { throw error }
|
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
|
throw SnodeAPI.handleError(withStatusCode: statusCode, json: json, forSnode: snode, associatedWith: publicKey) ?? error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getNetworkTime(from snode: Legacy.Snode) -> Promise<UInt64> {
|
private static func getNetworkTime(from snode: Snode) -> Promise<UInt64> {
|
||||||
return invoke(.getInfo, on: snode, parameters: [:]).map2 { rawResponse in
|
return invoke(.getInfo, on: snode, parameters: [:]).map2 { rawResponse in
|
||||||
guard let json = rawResponse as? JSON,
|
guard let json = rawResponse as? JSON,
|
||||||
let timestamp = json["timestamp"] as? UInt64 else { throw HTTP.Error.invalidJSON }
|
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<Legacy.Snode> {
|
internal static func getRandomSnode() -> Promise<Snode> {
|
||||||
// randomElement() uses the system's default random generator, which is cryptographically secure
|
// randomElement() uses the system's default random generator, which is cryptographically secure
|
||||||
return getSnodePool().map2 { $0.randomElement()! }
|
return getSnodePool().map2 { $0.randomElement()! }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getSnodePoolFromSeedNode() -> Promise<Set<Legacy.Snode>> {
|
private static func getSnodePoolFromSeedNode() -> Promise<Set<Snode>> {
|
||||||
let target = seedNodePool.randomElement()!
|
let target = seedNodePool.randomElement()!
|
||||||
let url = "\(target)/json_rpc"
|
let url = "\(target)/json_rpc"
|
||||||
let parameters: JSON = [
|
let parameters: JSON = [
|
||||||
"method" : "get_n_service_nodes",
|
"method": "get_n_service_nodes",
|
||||||
"params" : [
|
"params": [
|
||||||
"active_only" : true,
|
"active_only": true,
|
||||||
"limit" : 256,
|
"limit": 256,
|
||||||
"fields" : [
|
"fields": [
|
||||||
"public_ip" : true, "storage_port" : true, "pubkey_ed25519" : true, "pubkey_x25519" : true
|
"public_ip": true,
|
||||||
|
"storage_port": true,
|
||||||
|
"pubkey_ed25519": true,
|
||||||
|
"pubkey_x25519": true
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
SNLog("Populating snode pool using seed node: \(target).")
|
SNLog("Populating snode pool using seed node: \(target).")
|
||||||
let (promise, seal) = Promise<Set<Legacy.Snode>>.pending()
|
let (promise, seal) = Promise<Set<Snode>>.pending()
|
||||||
|
|
||||||
Threading.workQueue.async {
|
Threading.workQueue.async {
|
||||||
attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) {
|
attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) {
|
||||||
HTTP.execute(.post, url, parameters: parameters, useSeedNodeURLSession: true).map2 { json -> Set<Legacy.Snode> in
|
HTTP.execute(.post, url, parameters: parameters, useSeedNodeURLSession: true)
|
||||||
guard let intermediate = json["result"] as? JSON, let rawSnodes = intermediate["service_node_states"] as? [JSON] else { throw Error.snodePoolUpdatingFailed }
|
.map2 { json -> Set<Snode> in
|
||||||
return Set(rawSnodes.compactMap { rawSnode in
|
guard
|
||||||
guard let address = rawSnode["public_ip"] as? String, let port = rawSnode["storage_port"] as? Int,
|
let intermediate = json["result"] as? JSON,
|
||||||
let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else {
|
let rawSnodes = intermediate["service_node_states"] as? [JSON],
|
||||||
SNLog("Failed to parse snode from: \(rawSnode).")
|
let snodeData: Data = try? JSONSerialization.data(withJSONObject: rawSnodes, options: [])
|
||||||
return nil
|
else {
|
||||||
|
throw Error.snodePoolUpdatingFailed
|
||||||
}
|
}
|
||||||
return Legacy.Snode(address: "https://\(address)", port: UInt16(port), publicKeySet: Legacy.Snode.KeySet(ed25519Key: ed25519PublicKey, x25519Key: x25519PublicKey))
|
|
||||||
})
|
return ((try? JSONDecoder().decode([Failable<Snode>].self, from: snodeData)) ?? [])
|
||||||
}
|
.compactMap { $0.value }
|
||||||
}.done2 { snodePool in
|
.asSet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.done2 { snodePool in
|
||||||
SNLog("Got snode pool from seed node: \(target).")
|
SNLog("Got snode pool from seed node: \(target).")
|
||||||
seal.fulfill(snodePool)
|
seal.fulfill(snodePool)
|
||||||
}.catch2 { error in
|
}
|
||||||
|
.catch2 { error in
|
||||||
SNLog("Failed to contact seed node at: \(target).")
|
SNLog("Failed to contact seed node at: \(target).")
|
||||||
seal.reject(error)
|
seal.reject(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getSnodePoolFromSnode() -> Promise<Set<Legacy.Snode>> {
|
private static func getSnodePoolFromSnode() -> Promise<Set<Snode>> {
|
||||||
var snodePool = SnodeAPI.snodePool
|
var snodePool = SnodeAPI.snodePool
|
||||||
var snodes: Set<Legacy.Snode> = []
|
var snodes: Set<Snode> = []
|
||||||
(0..<3).forEach { _ in
|
(0..<3).forEach { _ in
|
||||||
let snode = snodePool.randomElement()!
|
guard let snode = snodePool.randomElement() else { return }
|
||||||
|
|
||||||
snodePool.remove(snode)
|
snodePool.remove(snode)
|
||||||
snodes.insert(snode)
|
snodes.insert(snode)
|
||||||
}
|
}
|
||||||
let snodePoolPromises: [Promise<Set<Legacy.Snode>>] = snodes.map { snode in
|
|
||||||
|
let snodePoolPromises: [Promise<Set<Snode>>] = snodes.map { snode in
|
||||||
return attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) {
|
return attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) {
|
||||||
// Don't specify a limit in the request. Service nodes return a shuffled
|
// 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
|
// list of nodes so if we specify a limit the 3 responses we get might have
|
||||||
// very little overlap.
|
// very little overlap.
|
||||||
let parameters: JSON = [
|
let parameters: JSON = [
|
||||||
"endpoint" : "get_service_nodes",
|
"endpoint": "get_service_nodes",
|
||||||
"params" : [
|
"params": [
|
||||||
"active_only" : true,
|
"active_only": true,
|
||||||
"fields" : [
|
"fields": [
|
||||||
"public_ip" : true, "storage_port" : true, "pubkey_ed25519" : true, "pubkey_x25519" : true
|
"public_ip": true,
|
||||||
|
"storage_port": true,
|
||||||
|
"pubkey_ed25519": true,
|
||||||
|
"pubkey_x25519": true
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
return invoke(.oxenDaemonRPCCall, on: snode, parameters: parameters).map2 { rawResponse in
|
return invoke(.oxenDaemonRPCCall, on: snode, parameters: parameters).map2 { rawResponse in
|
||||||
guard let json = rawResponse as? JSON, let intermediate = json["result"] as? JSON,
|
guard
|
||||||
let rawSnodes = intermediate["service_node_states"] as? [JSON] else {
|
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
|
throw Error.snodePoolUpdatingFailed
|
||||||
}
|
}
|
||||||
return Set(rawSnodes.compactMap { rawSnode in
|
|
||||||
guard let address = rawSnode["public_ip"] as? String, let port = rawSnode["storage_port"] as? Int,
|
return ((try? JSONDecoder().decode([Failable<Snode>].self, from: snodeData)) ?? [])
|
||||||
let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else {
|
.compactMap { $0.value }
|
||||||
SNLog("Failed to parse snode from: \(rawSnode).")
|
.asSet()
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return Legacy.Snode(address: "https://\(address)", port: UInt16(port), publicKeySet: Legacy.Snode.KeySet(ed25519Key: ed25519PublicKey, x25519Key: x25519PublicKey))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let promise = when(fulfilled: snodePoolPromises).map2 { results -> Set<Legacy.Snode> in
|
|
||||||
var result: Set<Legacy.Snode> = results[0]
|
let promise = when(fulfilled: snodePoolPromises).map2 { results -> Set<Snode> in
|
||||||
results.forEach { result = result.intersection($0) }
|
let result: Set<Snode> = results.reduce(Set()) { prev, next in prev.intersection(next) }
|
||||||
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
|
// We want the snodes to agree on at least this many snodes
|
||||||
// refreshing it
|
guard result.count > 24 else { throw Error.inconsistentSnodePools }
|
||||||
return (result.count > 256) ? Set([Legacy.Snode](result)[0..<256]) : result
|
|
||||||
} else {
|
// Limit the snode pool size to 256 so that we don't go too long without
|
||||||
throw Error.inconsistentSnodePools
|
// refreshing it
|
||||||
}
|
return Set(result.prefix(256))
|
||||||
}
|
}
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Public API
|
// MARK: Public API
|
||||||
|
|
||||||
@objc(getSnodePool)
|
@objc(getSnodePool)
|
||||||
public static func objc_getSnodePool() -> AnyPromise {
|
public static func objc_getSnodePool() -> AnyPromise {
|
||||||
AnyPromise.from(getSnodePool())
|
AnyPromise.from(getSnodePool())
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getSnodePool() -> Promise<Set<Legacy.Snode>> {
|
public static func getSnodePool() -> Promise<Set<Snode>> {
|
||||||
loadSnodePoolIfNeeded()
|
loadSnodePoolIfNeeded()
|
||||||
let now = Date()
|
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 snodePool = SnodeAPI.snodePool
|
||||||
let hasInsufficientSnodes = (snodePool.count < minSnodePoolCount)
|
let hasInsufficientSnodes = (snodePool.count < minSnodePoolCount)
|
||||||
|
|
||||||
if hasInsufficientSnodes || hasSnodePoolExpired {
|
if hasInsufficientSnodes || hasSnodePoolExpired {
|
||||||
if let getSnodePoolPromise = getSnodePoolPromise { return getSnodePoolPromise }
|
if let getSnodePoolPromise = getSnodePoolPromise { return getSnodePoolPromise }
|
||||||
let promise: Promise<Set<Legacy.Snode>>
|
|
||||||
|
let promise: Promise<Set<Snode>>
|
||||||
if snodePool.count < minSnodePoolCount {
|
if snodePool.count < minSnodePoolCount {
|
||||||
promise = getSnodePoolFromSeedNode()
|
promise = getSnodePoolFromSeedNode()
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
promise = getSnodePoolFromSnode().recover2 { _ in
|
promise = getSnodePoolFromSnode().recover2 { _ in
|
||||||
getSnodePoolFromSeedNode()
|
getSnodePoolFromSeedNode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getSnodePoolPromise = promise
|
getSnodePoolPromise = promise
|
||||||
promise.map2 { snodePool -> Set<Legacy.Snode> in
|
promise.map2 { snodePool -> Set<Snode> in
|
||||||
if snodePool.isEmpty {
|
guard !snodePool.isEmpty else { throw Error.snodePoolUpdatingFailed }
|
||||||
throw Error.snodePoolUpdatingFailed
|
|
||||||
} else {
|
return snodePool
|
||||||
return snodePool
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
promise.then2 { snodePool -> Promise<Set<Legacy.Snode>> in
|
|
||||||
let (promise, seal) = Promise<Set<Legacy.Snode>>.pending()
|
promise.then2 { snodePool -> Promise<Set<Snode>> in
|
||||||
SNSnodeKitConfiguration.shared.storage.write(with: { transaction in
|
let (promise, seal) = Promise<Set<Snode>>.pending()
|
||||||
Storage.shared.setLastSnodePoolRefreshDate(to: now, using: transaction)
|
|
||||||
setSnodePool(to: snodePool, using: transaction)
|
GRDBStorage.shared.writeAsync(
|
||||||
}, completion: {
|
updates: { db in
|
||||||
seal.fulfill(snodePool)
|
db[.lastSnodePoolRefreshDate] = now
|
||||||
})
|
setSnodePool(to: snodePool, db: db)
|
||||||
|
},
|
||||||
|
completion: { _, _ in
|
||||||
|
seal.fulfill(snodePool)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
promise.done2 { _ in
|
promise.done2 { _ in
|
||||||
|
@ -291,10 +303,11 @@ public final class SnodeAPI : NSObject {
|
||||||
promise.catch2 { _ in
|
promise.catch2 { _ in
|
||||||
getSnodePoolPromise = nil
|
getSnodePoolPromise = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
} else {
|
|
||||||
return Promise.value(snodePool)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.value(snodePool)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getSessionID(for onsName: String) -> Promise<String> {
|
public static func getSessionID(for onsName: String) -> Promise<String> {
|
||||||
|
@ -366,49 +379,55 @@ public final class SnodeAPI : NSObject {
|
||||||
return promise
|
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
|
// shuffled() uses the system's default random generator, which is cryptographically secure
|
||||||
return getSwarm(for: publicKey).map2 { Array($0.shuffled().prefix(targetSwarmSnodeCount)) }
|
return getSwarm(for: publicKey).map2 { Array($0.shuffled().prefix(targetSwarmSnodeCount)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getSwarm(for publicKey: String) -> Promise<Set<Legacy.Snode>> {
|
public static func getSwarm(for publicKey: String) -> Promise<Set<Snode>> {
|
||||||
loadSwarmIfNeeded(for: publicKey)
|
loadSwarmIfNeeded(for: publicKey)
|
||||||
|
|
||||||
if let cachedSwarm = swarmCache[publicKey], cachedSwarm.count >= minSwarmSnodeCount {
|
if let cachedSwarm = swarmCache[publicKey], cachedSwarm.count >= minSwarmSnodeCount {
|
||||||
return Promise<Set<Legacy.Snode>> { $0.fulfill(cachedSwarm) }
|
return Promise<Set<Snode>> { $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 ]
|
SNLog("Getting swarm for: \((publicKey == getUserHexEncodedPublicKey()) ? "self" : publicKey).")
|
||||||
return getRandomSnode().then2 { snode in
|
let parameters: [String: Any] = [
|
||||||
|
"pubKey": Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey
|
||||||
|
]
|
||||||
|
|
||||||
|
return getRandomSnode()
|
||||||
|
.then2 { snode in
|
||||||
attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) {
|
attempt(maxRetryCount: 4, recoveringOn: Threading.workQueue) {
|
||||||
invoke(.getSwarm, on: snode, associatedWith: publicKey, parameters: parameters)
|
invoke(.getSwarm, on: snode, associatedWith: publicKey, parameters: parameters)
|
||||||
}
|
}
|
||||||
}.map2 { rawSnodes in
|
}
|
||||||
|
.map2 { rawSnodes in
|
||||||
let swarm = parseSnodes(from: rawSnodes)
|
let swarm = parseSnodes(from: rawSnodes)
|
||||||
setSwarm(to: swarm, for: publicKey)
|
setSwarm(to: swarm, for: publicKey)
|
||||||
return swarm
|
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()
|
let (promise, seal) = RawResponsePromise.pending()
|
||||||
Threading.workQueue.async {
|
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
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getMessagesInternal(from snode: Legacy.Snode, associatedWith publicKey: String) -> RawResponsePromise {
|
private static func getMessagesInternal(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise {
|
||||||
let storage = SNSnodeKitConfiguration.shared.storage
|
|
||||||
|
|
||||||
// NOTE: All authentication logic is currently commented out, the reason being that we can't currently support
|
// 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
|
// it yet for closed groups. The Storage Server requires an ed25519 key pair, but we don't have that for our
|
||||||
// closed groups.
|
// closed groups.
|
||||||
|
|
||||||
// guard let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) }
|
// guard let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) }
|
||||||
// Get last message hash
|
// Get last message hash
|
||||||
storage.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey)
|
SnodeReceivedMessageInfo.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey)
|
||||||
let lastHash = storage.getLastMessageHash(for: snode, associatedWith: publicKey) ?? ""
|
let lastHash = SnodeReceivedMessageInfo.fetchLastNotExpired(for: snode, associatedWith: publicKey)?.hash ?? ""
|
||||||
// Construct signature
|
// Construct signature
|
||||||
// let timestamp = UInt64(Int64(NSDate.millisecondTimestamp()) + SnodeAPI.clockOffset)
|
// let timestamp = UInt64(Int64(NSDate.millisecondTimestamp()) + SnodeAPI.clockOffset)
|
||||||
// let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString()
|
// let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString()
|
||||||
|
@ -427,17 +446,28 @@ public final class SnodeAPI : NSObject {
|
||||||
|
|
||||||
public static func sendMessage(_ message: SnodeMessage) -> Promise<Set<RawResponsePromise>> {
|
public static func sendMessage(_ message: SnodeMessage) -> Promise<Set<RawResponsePromise>> {
|
||||||
let (promise, seal) = Promise<Set<RawResponsePromise>>.pending()
|
let (promise, seal) = Promise<Set<RawResponsePromise>>.pending()
|
||||||
let publicKey = Features.useTestnet ? message.recipient.removing05PrefixIfNeeded() : message.recipient
|
let publicKey = (Features.useTestnet ?
|
||||||
|
message.recipient.removing05PrefixIfNeeded() :
|
||||||
|
message.recipient
|
||||||
|
)
|
||||||
|
|
||||||
Threading.workQueue.async {
|
Threading.workQueue.async {
|
||||||
getTargetSnodes(for: publicKey).map2 { targetSnodes in
|
getTargetSnodes(for: publicKey)
|
||||||
let parameters = message.toJSON()
|
.map2 { targetSnodes in
|
||||||
return Set(targetSnodes.map { targetSnode in
|
let parameters = message.toJSON()
|
||||||
attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
|
||||||
invoke(.sendMessage, on: targetSnode, associatedWith: publicKey, parameters: parameters)
|
return targetSnodes
|
||||||
}
|
.map { targetSnode in
|
||||||
})
|
attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
||||||
}.done2 { seal.fulfill($0) }.catch2 { seal.reject($0) }
|
invoke(.sendMessage, on: targetSnode, associatedWith: publicKey, parameters: parameters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.asSet()
|
||||||
|
}
|
||||||
|
.done2 { seal.fulfill($0) }
|
||||||
|
.catch2 { seal.reject($0) }
|
||||||
}
|
}
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -446,75 +476,123 @@ public final class SnodeAPI : NSObject {
|
||||||
AnyPromise.from(deleteMessage(publicKey: publicKey, serverHashes: serverHashes))
|
AnyPromise.from(deleteMessage(publicKey: publicKey, serverHashes: serverHashes))
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func deleteMessage(publicKey: String, serverHashes: [String]) -> Promise<[String:Bool]> {
|
public static func deleteMessage(publicKey: String, serverHashes: [String]) -> Promise<[String: Bool]> {
|
||||||
let storage = SNSnodeKitConfiguration.shared.storage
|
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||||
guard let userX25519PublicKey = storage.getUserPublicKey(),
|
return Promise(error: Error.noKeyPair)
|
||||||
let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) }
|
}
|
||||||
let publicKey = Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey
|
|
||||||
|
let publicKey = (Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey)
|
||||||
|
let userX25519PublicKey: String = getUserHexEncodedPublicKey()
|
||||||
|
|
||||||
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
||||||
getSwarm(for: publicKey).then2 { swarm -> Promise<[String:Bool]> in
|
getSwarm(for: publicKey)
|
||||||
let snode = swarm.randomElement()!
|
.then2 { swarm -> Promise<[String: Bool]> in
|
||||||
let verificationData = (Legacy.Snode.Method.deleteMessage.rawValue + serverHashes.joined(separator: "")).data(using: String.Encoding.utf8)!
|
guard
|
||||||
guard let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) else { throw Error.signingFailed }
|
let snode = swarm.randomElement(),
|
||||||
let parameters: JSON = [
|
let verificationData = (Endpoint.deleteMessage.rawValue + serverHashes.joined()).data(using: String.Encoding.utf8),
|
||||||
"pubkey" : userX25519PublicKey,
|
let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey)
|
||||||
"pubkey_ed25519" : userED25519KeyPair.publicKey.toHexString(),
|
else {
|
||||||
"messages": serverHashes,
|
throw Error.signingFailed
|
||||||
"signature": signature.toBase64()
|
}
|
||||||
]
|
|
||||||
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
let parameters: JSON = [
|
||||||
invoke(.deleteMessage, on: snode, associatedWith: publicKey, parameters: parameters).map2{ rawResponse -> [String:Bool] in
|
"pubkey" : userX25519PublicKey,
|
||||||
guard let json = rawResponse as? JSON, let swarm = json["swarm"] as? JSON else { throw HTTP.Error.invalidJSON }
|
"pubkey_ed25519" : userED25519KeyPair.publicKey.toHexString(),
|
||||||
var result: [String:Bool] = [:]
|
"messages": serverHashes,
|
||||||
for (snodePublicKey, rawJSON) in swarm {
|
"signature": signature.toBase64()
|
||||||
guard let json = rawJSON as? JSON else { throw HTTP.Error.invalidJSON }
|
]
|
||||||
let isFailed = json["failed"] as? Bool ?? false
|
|
||||||
if !isFailed {
|
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
||||||
guard let hashes = json["deleted"] as? [String], let signature = json["signature"] as? String else { throw HTTP.Error.invalidJSON }
|
invoke(.deleteMessage, on: snode, associatedWith: publicKey, parameters: parameters)
|
||||||
// The signature format is ( PUBKEY_HEX || RMSG[0] || ... || RMSG[N] || DMSG[0] || ... || DMSG[M] )
|
.map2 { rawResponse -> [String: Bool] in
|
||||||
let verificationData = (userX25519PublicKey + serverHashes.joined(separator: "") + hashes.joined(separator: "")).data(using: String.Encoding.utf8)!
|
guard let json = rawResponse as? JSON, let swarm = json["swarm"] as? JSON else {
|
||||||
let isValid = sodium.sign.verify(message: Bytes(verificationData), publicKey: Bytes(Data(hex: snodePublicKey)), signature: Bytes(Data(base64Encoded: signature)!))
|
throw HTTP.Error.invalidJSON
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
/// 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]> {
|
public static func clearAllData() -> Promise<[String:Bool]> {
|
||||||
let storage = SNSnodeKitConfiguration.shared.storage
|
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||||
guard let userX25519PublicKey = storage.getUserPublicKey(),
|
return Promise(error: Error.noKeyPair)
|
||||||
let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) }
|
}
|
||||||
|
|
||||||
|
let userX25519PublicKey: String = getUserHexEncodedPublicKey()
|
||||||
|
|
||||||
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
||||||
getSwarm(for: userX25519PublicKey).then2 { swarm -> Promise<[String:Bool]> in
|
getSwarm(for: userX25519PublicKey).then2 { swarm -> Promise<[String:Bool]> in
|
||||||
let snode = swarm.randomElement()!
|
let snode = swarm.randomElement()!
|
||||||
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
||||||
getNetworkTime(from: snode).then2 { timestamp -> Promise<[String:Bool]> in
|
getNetworkTime(from: snode).then2 { timestamp -> Promise<[String:Bool]> in
|
||||||
let verificationData = (Legacy.Snode.Method.clearAllData.rawValue + String(timestamp)).data(using: String.Encoding.utf8)!
|
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 }
|
guard let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey) else {
|
||||||
|
throw Error.signingFailed
|
||||||
|
}
|
||||||
|
|
||||||
let parameters: JSON = [
|
let parameters: JSON = [
|
||||||
"pubkey" : userX25519PublicKey,
|
"pubkey": userX25519PublicKey,
|
||||||
"pubkey_ed25519" : userED25519KeyPair.publicKey.toHexString(),
|
"pubkey_ed25519": userED25519KeyPair.publicKey.toHexString(),
|
||||||
"timestamp" : timestamp,
|
"timestamp": timestamp,
|
||||||
"signature" : signature.toBase64()
|
"signature": signature.toBase64()
|
||||||
]
|
]
|
||||||
|
|
||||||
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
|
||||||
invoke(.clearAllData, on: snode, parameters: parameters).map2 { rawResponse -> [String:Bool] in
|
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 }
|
guard
|
||||||
var result: [String:Bool] = [:]
|
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 {
|
for (snodePublicKey, rawJSON) in swarm {
|
||||||
guard let json = rawJSON as? JSON else { throw HTTP.Error.invalidJSON }
|
guard let json = rawJSON as? JSON else { throw HTTP.Error.invalidJSON }
|
||||||
|
|
||||||
let isFailed = json["failed"] as? Bool ?? false
|
let isFailed = json["failed"] as? Bool ?? false
|
||||||
if !isFailed {
|
if !isFailed {
|
||||||
guard let hashes = json["deleted"] as? [String], let signature = json["signature"] as? String else { throw HTTP.Error.invalidJSON }
|
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
|
result[snodePublicKey] = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
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.
|
// 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<Legacy.Snode> {
|
private static func parseSnodes(from rawResponse: Any) -> Set<Snode> {
|
||||||
guard let json = rawResponse as? JSON, let rawSnodes = json["snodes"] as? [JSON] else {
|
guard let json = rawResponse as? JSON, let rawSnodes = json["snodes"] as? [JSON] else {
|
||||||
SNLog("Failed to parse snodes from: \(rawResponse).")
|
SNLog("Failed to parse snodes from: \(rawResponse).")
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return Set(rawSnodes.compactMap { rawSnode in
|
|
||||||
guard let address = rawSnode["ip"] as? String, let portAsString = rawSnode["port"] as? String, let port = UInt16(portAsString),
|
guard let snodeData: Data = try? JSONSerialization.data(withJSONObject: rawSnodes, options: []) else {
|
||||||
let ed25519PublicKey = rawSnode["pubkey_ed25519"] as? String, let x25519PublicKey = rawSnode["pubkey_x25519"] as? String, address != "0.0.0.0" else {
|
return []
|
||||||
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)).")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Hopefully at some point this different Snode structure will be deprecated and can be removed
|
||||||
|
if
|
||||||
|
let swarmSnodes: [SwarmSnode] = try? JSONDecoder().decode([Failable<SwarmSnode>].self, from: snodeData).compactMap({ $0.value }),
|
||||||
|
!swarmSnodes.isEmpty
|
||||||
|
{
|
||||||
|
return swarmSnodes.map { $0.toSnode() }.asSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((try? JSONDecoder().decode([Failable<Snode>].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] {
|
private static func removeDuplicates(from rawMessages: [JSON], associatedWith publicKey: String) -> [JSON] {
|
||||||
let oldReceivedMessages = SNSnodeKitConfiguration.shared.storage.getReceivedMessages(for: publicKey)
|
var oldReceivedMessages: [SnodeReceivedMessageInfo] = []
|
||||||
var newReceivedMessages = oldReceivedMessages
|
|
||||||
let result = rawMessages.filter { rawMessage in
|
GRDBStorage.shared.read { db in
|
||||||
guard let hash = rawMessage["hash"] as? String else {
|
oldReceivedMessages = oldReceivedMessages.appending(
|
||||||
SNLog("Missing hash value for message: \(rawMessage).")
|
contentsOf: try? SnodeReceivedMessageInfo
|
||||||
return false
|
.filter(SnodeReceivedMessageInfo.Columns.key.like("%\(publicKey)"))
|
||||||
}
|
.fetchAll(db)
|
||||||
let isDuplicate = newReceivedMessages.contains(hash)
|
)
|
||||||
newReceivedMessages.insert(hash)
|
|
||||||
return !isDuplicate
|
|
||||||
}
|
}
|
||||||
// Avoid the sync write transaction if possible
|
|
||||||
if oldReceivedMessages != newReceivedMessages {
|
let oldMessageHashes: Set<String> = oldReceivedMessages.map { $0.hash }.asSet()
|
||||||
SNSnodeKitConfiguration.shared.storage.writeSync { transaction in
|
|
||||||
SNSnodeKitConfiguration.shared.storage.setReceivedMessages(to: newReceivedMessages, for: publicKey, using: transaction)
|
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
|
// MARK: Error Handling
|
||||||
/// - Note: Should only be invoked from `Threading.workQueue` to avoid race conditions.
|
/// - Note: Should only be invoked from `Threading.workQueue` to avoid race conditions.
|
||||||
@discardableResult
|
@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
|
#if DEBUG
|
||||||
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
dispatchPrecondition(condition: .onQueue(Threading.workQueue))
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Legacy.Snode> {
|
|
||||||
var result: Set<Legacy.Snode> = []
|
|
||||||
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<Legacy.Snode>, 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<Legacy.Snode> {
|
|
||||||
var result: Set<Legacy.Snode> = []
|
|
||||||
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<Legacy.Snode>, 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<String> {
|
|
||||||
var result: Set<String>?
|
|
||||||
Storage.read { transaction in
|
|
||||||
result = transaction.object(forKey: publicKey, inCollection: Storage.receivedMessagesCollection) as? Set<String>
|
|
||||||
}
|
|
||||||
return result ?? []
|
|
||||||
}
|
|
||||||
|
|
||||||
public func setReceivedMessages(to receivedMessages: Set<String>, for publicKey: String, using transaction: Any) {
|
|
||||||
(transaction as! YapDatabaseReadWriteTransaction).setObject(receivedMessages, forKey: publicKey, inCollection: Storage.receivedMessagesCollection)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
import SessionUtilitiesKit
|
|
||||||
import PromiseKit
|
|
||||||
import Sodium
|
|
||||||
|
|
||||||
public protocol SessionSnodeKitStorageProtocol {
|
|
||||||
|
|
||||||
@discardableResult
|
|
||||||
func write(with block: @escaping (Any) -> Void) -> Promise<Void>
|
|
||||||
@discardableResult
|
|
||||||
func write(with block: @escaping (Any) -> Void, completion: @escaping () -> Void) -> Promise<Void>
|
|
||||||
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<Legacy.Snode>
|
|
||||||
func setSnodePool(to snodePool: Set<Legacy.Snode>, using transaction: Any)
|
|
||||||
func getLastSnodePoolRefreshDate() -> Date?
|
|
||||||
func setLastSnodePoolRefreshDate(to date: Date, using transaction: Any)
|
|
||||||
func getSwarm(for publicKey: String) -> Set<Legacy.Snode>
|
|
||||||
func setSwarm(to swarm: Set<Legacy.Snode>, 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<String>
|
|
||||||
func setReceivedMessages(to receivedMessages: Set<String>, for publicKey: String, using transaction: Any)
|
|
||||||
}
|
|
17
SessionSnodeKit/Types/SSKDestination.swift
Normal file
17
SessionSnodeKit/Types/SSKDestination.swift
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
SessionSnodeKit/Types/SSKEndpoint.swift
Normal file
15
SessionSnodeKit/Types/SSKEndpoint.swift
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
63
SessionSnodeKit/Types/SSKError.swift
Normal file
63
SessionSnodeKit/Types/SSKError.swift
Normal file
|
@ -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."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,17 @@ public final class SNUtilitiesKitConfiguration : NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SNUtilitiesKit { // Just to make the external API nice
|
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) {
|
public static func configure(owsPrimaryStorage: OWSPrimaryStorageProtocol, maxFileSize: UInt) {
|
||||||
SNUtilitiesKitConfiguration.shared = SNUtilitiesKitConfiguration(owsPrimaryStorage: owsPrimaryStorage, maxFileSize: maxFileSize)
|
SNUtilitiesKitConfiguration.shared = SNUtilitiesKitConfiguration(owsPrimaryStorage: owsPrimaryStorage, maxFileSize: maxFileSize)
|
||||||
|
|
|
@ -210,15 +210,15 @@ public final class GRDBStorage {
|
||||||
|
|
||||||
// MARK: - Functions
|
// MARK: - Functions
|
||||||
|
|
||||||
public func write<T>(updates: (Database) throws -> T) throws -> T {
|
@discardableResult public func write<T>(updates: (Database) throws -> T?) -> T? {
|
||||||
return try dbPool.write(updates)
|
return try? dbPool.write(updates)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func writeAsync<T>(updates: @escaping (Database) throws -> T, completion: @escaping (Database, Result<T, Error>) -> Void) {
|
public func writeAsync<T>(updates: @escaping (Database) throws -> T, completion: @escaping (Database, Result<T, Error>) -> Void) {
|
||||||
dbPool.asyncWrite(updates, completion: completion)
|
dbPool.asyncWrite(updates, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func read<T>(_ value: (Database) throws -> T) throws -> T {
|
@discardableResult public func read<T>(_ value: (Database) throws -> T?) -> T? {
|
||||||
return try dbPool.read(value)
|
return try? dbPool.read(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
129
SessionUtilitiesKit/Database/Models/Identity.swift
Normal file
129
SessionUtilitiesKit/Database/Models/Identity.swift
Normal file
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
149
SessionUtilitiesKit/Database/Models/Setting.swift
Normal file
149
SessionUtilitiesKit/Database/Models/Setting.swift
Normal file
|
@ -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?<T>(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<T>(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 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +0,0 @@
|
||||||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
||||||
|
|
||||||
import Foundation
|
|
|
@ -16,6 +16,7 @@ public struct TargetMigrations: Comparable {
|
||||||
public enum Identifier: String, CaseIterable, Comparable {
|
public enum Identifier: String, CaseIterable, Comparable {
|
||||||
// WARNING: The string version of these cases are used as migration identifiers so
|
// WARNING: The string version of these cases are used as migration identifiers so
|
||||||
// changing them will result in the migrations running again
|
// changing them will result in the migrations running again
|
||||||
|
case utilitiesKit
|
||||||
case snodeKit
|
case snodeKit
|
||||||
case messagingKit
|
case messagingKit
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,14 @@ public class TypedTableDefinition<T> where T: TableRecord, T: ColumnExpressible
|
||||||
definition.primaryKey(columns.map { $0.name }, onConflict: onConflict)
|
definition.primaryKey(columns.map { $0.name }, onConflict: onConflict)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func foreignKey<Other>(_ 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<Other>(
|
||||||
|
_ 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(
|
return definition.foreignKey(
|
||||||
columns.map { $0.name },
|
columns.map { $0.name },
|
||||||
references: table.databaseTableName,
|
references: table.databaseTableName,
|
||||||
|
|
|
@ -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 {
|
public extension Array where Element: Hashable {
|
||||||
func asSet() -> Set<Element> {
|
func asSet() -> Set<Element> {
|
||||||
return Set(self)
|
return Set(self)
|
||||||
|
|
|
@ -10,4 +10,14 @@ public extension Dictionary {
|
||||||
return keyDescription + " : " + truncatedValueDescription
|
return keyDescription + " : " + truncatedValueDescription
|
||||||
}.joined(separator: ", ") + " ]"
|
}.joined(separator: ", ") + " ]"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func asArray() -> [(key: Key, value: Value)] {
|
||||||
|
return Array(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension Dictionary.Values {
|
||||||
|
func asArray() -> [Value] {
|
||||||
|
return Array(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<String?> = 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.
|
/// Does nothing, but is never inlined and thus evaluating its argument will never be optimized away.
|
||||||
///
|
///
|
||||||
|
|
26
SessionUtilitiesKit/Utilities/Failable.swift
Normal file
26
SessionUtilitiesKit/Utilities/Failable.swift
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// The `Failable<T>` 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<T: Codable>: 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,6 @@
|
||||||
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
import Clibsodium
|
import Clibsodium
|
||||||
import Sodium
|
import Sodium
|
||||||
|
|
|
@ -24,6 +24,7 @@ public final class Configuration : NSObject {
|
||||||
//DispatchQueue.main.once
|
//DispatchQueue.main.once
|
||||||
let storage: GRDBStorage? = try? GRDBStorage(
|
let storage: GRDBStorage? = try? GRDBStorage(
|
||||||
migrations: [
|
migrations: [
|
||||||
|
SNUtilitiesKit.migrations(),
|
||||||
SNSnodeKit.migrations(),
|
SNSnodeKit.migrations(),
|
||||||
SNMessagingKit.migrations()
|
SNMessagingKit.migrations()
|
||||||
]
|
]
|
||||||
|
@ -31,6 +32,6 @@ public final class Configuration : NSObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
SNMessagingKit.configure(storage: Storage.shared)
|
SNMessagingKit.configure(storage: Storage.shared)
|
||||||
SNSnodeKit.configure(storage: Storage.shared)
|
SNSnodeKit.configure()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<String>, using transaction: Any) {
|
public func updateMessageIDCollectionByPruningMessagesWithIDs(_ messageIDs: Set<String>, using transaction: Any) {
|
||||||
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
let transaction = transaction as! YapDatabaseReadWriteTransaction
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
#ifndef Signal_TSStorageHeaders_h
|
#ifndef Signal_TSStorageHeaders_h
|
||||||
#define Signal_TSStorageHeaders_h
|
#define Signal_TSStorageHeaders_h
|
||||||
#import <SessionMessagingKit/OWSIdentityManager.h>
|
|
||||||
#import <SignalUtilitiesKit/OWSPrimaryStorage+keyFromIntLong.h>
|
#import <SignalUtilitiesKit/OWSPrimaryStorage+keyFromIntLong.h>
|
||||||
#import <SessionMessagingKit/OWSPrimaryStorage.h>
|
#import <SessionMessagingKit/OWSPrimaryStorage.h>
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#import "OWSPrimaryStorage+Loki.h"
|
#import "OWSPrimaryStorage+Loki.h"
|
||||||
#import "OWSPrimaryStorage+keyFromIntLong.h"
|
#import "OWSPrimaryStorage+keyFromIntLong.h"
|
||||||
#import "OWSIdentityManager.h"
|
|
||||||
#import "NSDate+OWS.h"
|
#import "NSDate+OWS.h"
|
||||||
#import "TSAccountManager.h"
|
#import "TSAccountManager.h"
|
||||||
#import "YapDatabaseConnection+OWS.h"
|
#import "YapDatabaseConnection+OWS.h"
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
#import <SignalUtilitiesKit/OWSProfileManager.h>
|
#import <SignalUtilitiesKit/OWSProfileManager.h>
|
||||||
#import <SessionMessagingKit/OWSBackgroundTask.h>
|
#import <SessionMessagingKit/OWSBackgroundTask.h>
|
||||||
#import <SessionMessagingKit/OWSDisappearingMessagesJob.h>
|
#import <SessionMessagingKit/OWSDisappearingMessagesJob.h>
|
||||||
#import <SessionMessagingKit/OWSIdentityManager.h>
|
|
||||||
#import <SessionMessagingKit/OWSOutgoingReceiptManager.h>
|
#import <SessionMessagingKit/OWSOutgoingReceiptManager.h>
|
||||||
#import <SessionMessagingKit/OWSReadReceiptManager.h>
|
#import <SessionMessagingKit/OWSReadReceiptManager.h>
|
||||||
#import <SessionMessagingKit/OWSSounds.h>
|
#import <SessionMessagingKit/OWSSounds.h>
|
||||||
|
@ -51,7 +50,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
OWSPreferences *preferences = [OWSPreferences new];
|
OWSPreferences *preferences = [OWSPreferences new];
|
||||||
|
|
||||||
OWSProfileManager *profileManager = [[OWSProfileManager alloc] initWithPrimaryStorage:primaryStorage];
|
OWSProfileManager *profileManager = [[OWSProfileManager alloc] initWithPrimaryStorage:primaryStorage];
|
||||||
OWSIdentityManager *identityManager = [[OWSIdentityManager alloc] initWithPrimaryStorage:primaryStorage];
|
|
||||||
TSAccountManager *tsAccountManager = [[TSAccountManager alloc] initWithPrimaryStorage:primaryStorage];
|
TSAccountManager *tsAccountManager = [[TSAccountManager alloc] initWithPrimaryStorage:primaryStorage];
|
||||||
OWSDisappearingMessagesJob *disappearingMessagesJob =
|
OWSDisappearingMessagesJob *disappearingMessagesJob =
|
||||||
[[OWSDisappearingMessagesJob alloc] initWithPrimaryStorage:primaryStorage];
|
[[OWSDisappearingMessagesJob alloc] initWithPrimaryStorage:primaryStorage];
|
||||||
|
@ -75,7 +73,6 @@ NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
[SSKEnvironment setShared:[[SSKEnvironment alloc] initWithProfileManager:profileManager
|
[SSKEnvironment setShared:[[SSKEnvironment alloc] initWithProfileManager:profileManager
|
||||||
primaryStorage:primaryStorage
|
primaryStorage:primaryStorage
|
||||||
identityManager:identityManager
|
|
||||||
tsAccountManager:tsAccountManager
|
tsAccountManager:tsAccountManager
|
||||||
disappearingMessagesJob:disappearingMessagesJob
|
disappearingMessagesJob:disappearingMessagesJob
|
||||||
readReceiptManager:readReceiptManager
|
readReceiptManager:readReceiptManager
|
||||||
|
|
Loading…
Reference in a new issue