Fixed the broken tests
Cleaned up the Dependencies so that tests can run synchronously without having to custom set queues as much Sorted out the crypto and network dependencies to avoid needing weird dependency inheritance Fixed the flaky tests so they are no longer flaky Fixed some unexpected JobRunner behaviours Updated the CI config to use a local build directory for derivedData (now works with build tweaks)
This commit is contained in:
parent
e768bebe6d
commit
a41f1c1366
|
@ -71,7 +71,7 @@ local update_cocoapods_cache = {
|
|||
name: 'Run Unit Tests',
|
||||
commands: [
|
||||
'mkdir build',
|
||||
'NSUnbufferedIO=YES set -o pipefail && xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14" -destination "platform=iOS Simulator,name=iPhone 14 Pro Max" -parallel-testing-enabled YES -test-timeouts-enabled YES -maximum-test-execution-time-allowance 2 -collect-test-diagnostics never 2>&1 | ./Pods/xcbeautify/xcbeautify --is-ci --report junit --report-path ./build/reports --junit-report-filename junit2.xml'
|
||||
'NSUnbufferedIO=YES set -o pipefail && xcodebuild test -workspace Session.xcworkspace -scheme Session -derivedDataPath ./build/derivedData -destination "platform=iOS Simulator,name=iPhone 14" -destination "platform=iOS Simulator,name=iPhone 14 Pro Max" -parallel-testing-enabled YES -test-timeouts-enabled YES -maximum-test-execution-time-allowance 2 -collect-test-diagnostics never 2>&1 | ./Pods/xcbeautify/xcbeautify --is-ci --report junit --report-path ./build/reports --junit-report-filename junit2.xml'
|
||||
],
|
||||
},
|
||||
update_cocoapods_cache
|
||||
|
@ -91,7 +91,7 @@ local update_cocoapods_cache = {
|
|||
name: 'Build',
|
||||
commands: [
|
||||
'mkdir build',
|
||||
'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | ./Pods/xcbeautify/xcbeautify --is-ci'
|
||||
'xcodebuild archive -workspace Session.xcworkspace -scheme Session -derivedDataPath ./build/derivedData -configuration "App Store Release" -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | ./Pods/xcbeautify/xcbeautify --is-ci'
|
||||
],
|
||||
},
|
||||
update_cocoapods_cache,
|
||||
|
@ -118,7 +118,7 @@ local update_cocoapods_cache = {
|
|||
name: 'Build',
|
||||
commands: [
|
||||
'mkdir build',
|
||||
'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates'
|
||||
'xcodebuild archive -workspace Session.xcworkspace -scheme Session -derivedDataPath ./build/derivedData -configuration "App Store Release" -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates CODE_SIGNING_ALLOWED=NO'
|
||||
],
|
||||
},
|
||||
update_cocoapods_cache,
|
||||
|
|
|
@ -28,13 +28,13 @@ else
|
|||
base="session-ios-$(date --date=@$DRONE_BUILD_CREATED +%Y%m%dT%H%M%SZ)-${DRONE_COMMIT:0:9}"
|
||||
fi
|
||||
|
||||
mkdir -v "$base"
|
||||
mkdir -vp "$base"
|
||||
|
||||
# Copy over the build products
|
||||
prod_path="build/Session.xcarchive"
|
||||
sim_path="build/Session_sim.xcarchive/Products/Applications/Session.app"
|
||||
|
||||
mkdir build
|
||||
mkdir -p build
|
||||
echo "Test" > "build/test.txt"
|
||||
|
||||
if [ ! -d $prod_path ]; then
|
||||
|
|
|
@ -470,11 +470,10 @@
|
|||
FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4727E02561000769AF /* CommonMockedExtensions.swift */; };
|
||||
FD078E4927E02576000769AF /* CommonMockedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4727E02561000769AF /* CommonMockedExtensions.swift */; };
|
||||
FD078E4D27E17156000769AF /* MockOGMCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4C27E17156000769AF /* MockOGMCache.swift */; };
|
||||
FD078E4F27E175F1000769AF /* DependencyExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4E27E175F1000769AF /* DependencyExtensions.swift */; };
|
||||
FD078E5227E1760A000769AF /* OGMDependencyExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E5127E1760A000769AF /* OGMDependencyExtensions.swift */; };
|
||||
FD078E5427E197CA000769AF /* OpenGroupManagerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */; };
|
||||
FD078E5A27E29F09000769AF /* MockNonce16Generator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E5927E29F09000769AF /* MockNonce16Generator.swift */; };
|
||||
FD078E5C27E29F78000769AF /* MockNonce24Generator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E5B27E29F78000769AF /* MockNonce24Generator.swift */; };
|
||||
FD0969F92A69FFE700C5C365 /* Mocked.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0969F82A69FFE700C5C365 /* Mocked.swift */; };
|
||||
FD0969FA2A6A00B000C5C365 /* Mocked.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0969F82A69FFE700C5C365 /* Mocked.swift */; };
|
||||
FD0969FB2A6A00B100C5C365 /* Mocked.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0969F82A69FFE700C5C365 /* Mocked.swift */; };
|
||||
FD09796B27F6C67500936362 /* Failable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796A27F6C67500936362 /* Failable.swift */; };
|
||||
FD09796E27FA6D0000936362 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796D27FA6D0000936362 /* Contact.swift */; };
|
||||
FD09797027FA6FF300936362 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796F27FA6FF300936362 /* Profile.swift */; };
|
||||
|
@ -532,6 +531,21 @@
|
|||
FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */; };
|
||||
FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FD2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift */; };
|
||||
FD1C98E4282E3C5B00B76F9E /* UINavigationBar+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */; };
|
||||
FD23CE1B2A651E6D0000B97C /* NetworkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1A2A651E6D0000B97C /* NetworkType.swift */; };
|
||||
FD23CE1F2A65269C0000B97C /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1E2A65269C0000B97C /* Crypto.swift */; };
|
||||
FD23CE222A661D000000B97C /* OpenGroupAPI+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */; };
|
||||
FD23CE242A675C440000B97C /* Crypto+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE232A675C440000B97C /* Crypto+SessionMessagingKit.swift */; };
|
||||
FD23CE262A676B5B0000B97C /* DependenciesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE252A676B5B0000B97C /* DependenciesSpec.swift */; };
|
||||
FD23CE282A67755C0000B97C /* MockCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE272A67755C0000B97C /* MockCrypto.swift */; };
|
||||
FD23CE292A6775650000B97C /* MockCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE272A67755C0000B97C /* MockCrypto.swift */; };
|
||||
FD23CE2A2A6775660000B97C /* MockCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE272A67755C0000B97C /* MockCrypto.swift */; };
|
||||
FD23CE2C2A678DF80000B97C /* MockCaches.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE2B2A678DF80000B97C /* MockCaches.swift */; };
|
||||
FD23CE2D2A678E1E0000B97C /* MockCaches.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE2B2A678DF80000B97C /* MockCaches.swift */; };
|
||||
FD23CE2E2A678E1E0000B97C /* MockCaches.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE2B2A678DF80000B97C /* MockCaches.swift */; };
|
||||
FD23CE302A67B8820000B97C /* Caches.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE2F2A67B8820000B97C /* Caches.swift */; };
|
||||
FD23CE332A67C4D90000B97C /* MockNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE312A67C38D0000B97C /* MockNetwork.swift */; };
|
||||
FD23CE342A67C4D90000B97C /* MockNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE312A67C38D0000B97C /* MockNetwork.swift */; };
|
||||
FD23CE352A67C4DA0000B97C /* MockNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE312A67C38D0000B97C /* MockNetwork.swift */; };
|
||||
FD23EA5C28ED00F80058676E /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; };
|
||||
FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; };
|
||||
FD23EA5E28ED00FD0058676E /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; };
|
||||
|
@ -615,9 +629,8 @@
|
|||
FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */; };
|
||||
FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */; };
|
||||
FD3C906727E416AF00CD579F /* BlindedIdLookupSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906627E416AF00CD579F /* BlindedIdLookupSpec.swift */; };
|
||||
FD3C906A27E417CE00CD579F /* SodiumUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906927E417CE00CD579F /* SodiumUtilitiesSpec.swift */; };
|
||||
FD3C906A27E417CE00CD579F /* CryptoSMKSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906927E417CE00CD579F /* CryptoSMKSpec.swift */; };
|
||||
FD3C906D27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906C27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift */; };
|
||||
FD3C906F27E43E8700CD579F /* MockBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906E27E43E8700CD579F /* MockBox.swift */; };
|
||||
FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907027E445E500CD579F /* MessageReceiverDecryptionSpec.swift */; };
|
||||
FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */; };
|
||||
FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; };
|
||||
|
@ -634,6 +647,7 @@
|
|||
FD52090528B4915F006098F6 /* PrivacySettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */; };
|
||||
FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090828B59411006098F6 /* ScreenLockUI.swift */; };
|
||||
FD52090B28B59BB4006098F6 /* ScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */; };
|
||||
FD559DF52A7368CB00C7C62A /* DispatchQueue+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */; };
|
||||
FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */; };
|
||||
FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */; };
|
||||
FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */; };
|
||||
|
@ -696,7 +710,6 @@
|
|||
FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */; };
|
||||
FD7728962849E7E90018502F /* String+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728952849E7E90018502F /* String+Utilities.swift */; };
|
||||
FD7728982849E8110018502F /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728972849E8110018502F /* UITableView+ReusableView.swift */; };
|
||||
FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD772899284AF1BD0018502F /* Sodium+Utilities.swift */; };
|
||||
FD77289E284EF1C50018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD77289D284EF1C50018502F /* Sodium+Utilities.swift */; };
|
||||
FD778B6429B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */; };
|
||||
FD83B9B327CF200A005E1583 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; platformFilter = ios; };
|
||||
|
@ -708,6 +721,7 @@
|
|||
FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */; };
|
||||
FD83B9CC27D179BC005E1583 /* FSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9CB27D179BC005E1583 /* FSEndpoint.swift */; };
|
||||
FD83B9D227D59495005E1583 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; };
|
||||
FD83DCDD2A739D350065FFAE /* RetryWithDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */; };
|
||||
FD848B8B283DC509000E298B /* PagedDatabaseObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8A283DC509000E298B /* PagedDatabaseObserver.swift */; };
|
||||
FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8C283E0B26000E298B /* MessageInputTypes.swift */; };
|
||||
FD848B8F283EF2A8000E298B /* UIScrollView+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8E283EF2A8000E298B /* UIScrollView+Utilities.swift */; };
|
||||
|
@ -716,11 +730,6 @@
|
|||
FD848B9828422F1A000E298B /* Date+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9728422F1A000E298B /* Date+Utilities.swift */; };
|
||||
FD848B9A28442CE6000E298B /* StorageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9928442CE6000E298B /* StorageError.swift */; };
|
||||
FD848B9C284435D7000E298B /* AppSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9B284435D7000E298B /* AppSetup.swift */; };
|
||||
FD859EF427C2F49200510D0C /* MockSodium.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF327C2F49200510D0C /* MockSodium.swift */; };
|
||||
FD859EF627C2F52C00510D0C /* MockSign.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF527C2F52C00510D0C /* MockSign.swift */; };
|
||||
FD859EF827C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF727C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift */; };
|
||||
FD859EFA27C2F5C500510D0C /* MockGenericHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF927C2F5C500510D0C /* MockGenericHash.swift */; };
|
||||
FD859EFC27C2F60700510D0C /* MockEd25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EFB27C2F60700510D0C /* MockEd25519.swift */; };
|
||||
FD87DCFA28B74DB300AF0F98 /* ConversationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCF928B74DB300AF0F98 /* ConversationSettingsViewModel.swift */; };
|
||||
FD87DCFE28B7582C00AF0F98 /* BlockedContactsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */; };
|
||||
FD87DD0028B820F200AF0F98 /* BlockedContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFF28B820F200AF0F98 /* BlockedContactCell.swift */; };
|
||||
|
@ -745,6 +754,9 @@
|
|||
FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */; };
|
||||
FD9BDE002A5D22B7005F1EBC /* libSessionUtil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */; };
|
||||
FD9BDE012A5D24EA005F1EBC /* SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C331FF1B2558F9D300070591 /* SessionUIKit.framework */; };
|
||||
FD9DD2712A72516D00ECB68E /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9DD2702A72516D00ECB68E /* TestExtensions.swift */; };
|
||||
FD9DD2722A72516D00ECB68E /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9DD2702A72516D00ECB68E /* TestExtensions.swift */; };
|
||||
FD9DD2732A72516D00ECB68E /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9DD2702A72516D00ECB68E /* TestExtensions.swift */; };
|
||||
FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */; };
|
||||
FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */; };
|
||||
FDA1E83B29A5F2D500C5C3BD /* SessionUtil+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */; };
|
||||
|
@ -769,14 +781,11 @@
|
|||
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */; };
|
||||
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */; };
|
||||
FDC2909A27D71376005DAE71 /* NonceGeneratorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909927D71376005DAE71 /* NonceGeneratorSpec.swift */; };
|
||||
FDC2909C27D713D2005DAE71 /* SodiumProtocolsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909B27D713D2005DAE71 /* SodiumProtocolsSpec.swift */; };
|
||||
FDC290A627D860CE005DAE71 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; };
|
||||
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; };
|
||||
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; };
|
||||
FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; };
|
||||
FDC290B327DFF9F5005DAE71 /* TestOnionRequestAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290B227DFF9F5005DAE71 /* TestOnionRequestAPI.swift */; };
|
||||
FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */; };
|
||||
FDC4381527B329CE00C60D73 /* NonceGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381427B329CE00C60D73 /* NonceGenerator.swift */; };
|
||||
FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381627B32EC700C60D73 /* Personalization.swift */; };
|
||||
FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */; };
|
||||
FDC4382F27B383AF00C60D73 /* PushServerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382E27B383AF00C60D73 /* PushServerResponse.swift */; };
|
||||
|
@ -796,8 +805,6 @@
|
|||
FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */; };
|
||||
FDC438AA27BB12BB00C60D73 /* UserModeratorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */; };
|
||||
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438BC27BB2AB400C60D73 /* Mockable.swift */; };
|
||||
FDC438C127BB4E6800C60D73 /* SMKDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C027BB4E6800C60D73 /* SMKDependencies.swift */; };
|
||||
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C227BB512200C60D73 /* SodiumProtocols.swift */; };
|
||||
FDC438C727BB6DF000C60D73 /* DirectMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C627BB6DF000C60D73 /* DirectMessage.swift */; };
|
||||
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; };
|
||||
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */; };
|
||||
|
@ -816,8 +823,8 @@
|
|||
FDDCBDAB29E776BF00303C38 /* seed2-2023-2y.der in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA529E776BF00303C38 /* seed2-2023-2y.der */; };
|
||||
FDDCBDAC29E776BF00303C38 /* seed3-2023-2y.crt in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA629E776BF00303C38 /* seed3-2023-2y.crt */; };
|
||||
FDDCBDAD29E776BF00303C38 /* seed3-2023-2y.der in Resources */ = {isa = PBXBuildFile; fileRef = FDDCBDA729E776BF00303C38 /* seed3-2023-2y.der */; };
|
||||
FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */; };
|
||||
FDDF074429C3E3D000E5E8B5 /* FetchRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */; };
|
||||
FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */; };
|
||||
FDE658A129418C7900A33BC1 /* CryptoKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */; };
|
||||
FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A229418E2F00A33BC1 /* KeyPair.swift */; };
|
||||
FDE6E99829F8E63A00F93C5D /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE6E99729F8E63A00F93C5D /* Accessibility.swift */; };
|
||||
|
@ -852,7 +859,6 @@
|
|||
FDF8488629405A61007DCAE5 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488529405A60007DCAE5 /* Request.swift */; };
|
||||
FDF8488829405A9A007DCAE5 /* SOGSBatchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488729405A9A007DCAE5 /* SOGSBatchRequest.swift */; };
|
||||
FDF8488929405B27007DCAE5 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */; };
|
||||
FDF8488B29405BF2007DCAE5 /* SSKDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488A29405BF2007DCAE5 /* SSKDependencies.swift */; };
|
||||
FDF8488E29405C04007DCAE5 /* GetSnodePoolJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488D29405C04007DCAE5 /* GetSnodePoolJob.swift */; };
|
||||
FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */; };
|
||||
FDF8489429405C1B007DCAE5 /* SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */; };
|
||||
|
@ -911,6 +917,7 @@
|
|||
FDFDE128282D05530098B17F /* MediaPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE127282D05530098B17F /* MediaPresentationContext.swift */; };
|
||||
FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */; };
|
||||
FDFF61D729F2600300F95FB0 /* Identity+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */; };
|
||||
FDFF9FDF2A787F57005E0628 /* JSONEncoder+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */; };
|
||||
FE5FDED6D91BB4B3FA5C104D /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A9C113D2086D3C8A68A371C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -1626,10 +1633,7 @@
|
|||
FCB11D8B1A129A76002F93FB /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
|
||||
FD078E4727E02561000769AF /* CommonMockedExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonMockedExtensions.swift; sourceTree = "<group>"; };
|
||||
FD078E4C27E17156000769AF /* MockOGMCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockOGMCache.swift; sourceTree = "<group>"; };
|
||||
FD078E4E27E175F1000769AF /* DependencyExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyExtensions.swift; sourceTree = "<group>"; };
|
||||
FD078E5127E1760A000769AF /* OGMDependencyExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OGMDependencyExtensions.swift; sourceTree = "<group>"; };
|
||||
FD078E5927E29F09000769AF /* MockNonce16Generator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNonce16Generator.swift; sourceTree = "<group>"; };
|
||||
FD078E5B27E29F78000769AF /* MockNonce24Generator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNonce24Generator.swift; sourceTree = "<group>"; };
|
||||
FD0969F82A69FFE700C5C365 /* Mocked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mocked.swift; sourceTree = "<group>"; };
|
||||
FD09796A27F6C67500936362 /* Failable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Failable.swift; sourceTree = "<group>"; };
|
||||
FD09796D27FA6D0000936362 /* Contact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = "<group>"; };
|
||||
FD09796F27FA6FF300936362 /* Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = "<group>"; };
|
||||
|
@ -1685,6 +1689,15 @@
|
|||
FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PersistableRecord+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD1A94FD2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistableRecordUtilitiesSpec.swift; sourceTree = "<group>"; };
|
||||
FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD23CE1A2A651E6D0000B97C /* NetworkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkType.swift; sourceTree = "<group>"; };
|
||||
FD23CE1E2A65269C0000B97C /* Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = "<group>"; };
|
||||
FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenGroupAPI+Crypto.swift"; sourceTree = "<group>"; };
|
||||
FD23CE232A675C440000B97C /* Crypto+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+SessionMessagingKit.swift"; sourceTree = "<group>"; };
|
||||
FD23CE252A676B5B0000B97C /* DependenciesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependenciesSpec.swift; sourceTree = "<group>"; };
|
||||
FD23CE272A67755C0000B97C /* MockCrypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCrypto.swift; sourceTree = "<group>"; };
|
||||
FD23CE2B2A678DF80000B97C /* MockCaches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCaches.swift; sourceTree = "<group>"; };
|
||||
FD23CE2F2A67B8820000B97C /* Caches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Caches.swift; sourceTree = "<group>"; };
|
||||
FD23CE312A67C38D0000B97C /* MockNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNetwork.swift; sourceTree = "<group>"; };
|
||||
FD23EA6028ED0B260058676E /* CombineExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineExtensions.swift; sourceTree = "<group>"; };
|
||||
FD245C612850664300B966DD /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
|
||||
FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
|
||||
|
@ -1736,9 +1749,8 @@
|
|||
FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchRequestInfoSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadResponseSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C906627E416AF00CD579F /* BlindedIdLookupSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlindedIdLookupSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C906927E417CE00CD579F /* SodiumUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SodiumUtilitiesSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C906927E417CE00CD579F /* CryptoSMKSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSMKSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C906C27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSenderEncryptionSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C906E27E43E8700CD579F /* MockBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBox.swift; sourceTree = "<group>"; };
|
||||
FD3C907027E445E500CD579F /* MessageReceiverDecryptionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReceiverDecryptionSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookup.swift; sourceTree = "<group>"; };
|
||||
FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -1754,6 +1766,7 @@
|
|||
FD52090628B49738006098F6 /* ConfirmationModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationModal.swift; sourceTree = "<group>"; };
|
||||
FD52090828B59411006098F6 /* ScreenLockUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockUI.swift; sourceTree = "<group>"; };
|
||||
FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockViewController.swift; sourceTree = "<group>"; };
|
||||
FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ReadReceipts.swift"; sourceTree = "<group>"; };
|
||||
FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+TypingIndicators.swift"; sourceTree = "<group>"; };
|
||||
FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+DataExtractionNotification.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1811,7 +1824,6 @@
|
|||
FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModelSpec.swift; sourceTree = "<group>"; };
|
||||
FD7728952849E7E90018502F /* String+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD7728972849E8110018502F /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = "<group>"; };
|
||||
FD772899284AF1BD0018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD77289D284EF1C50018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _014_GenerateInitialUserConfigDumps.swift; sourceTree = "<group>"; };
|
||||
FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionUtilitiesKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -1822,6 +1834,7 @@
|
|||
FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageResponse.swift; sourceTree = "<group>"; };
|
||||
FD83B9CB27D179BC005E1583 /* FSEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSEndpoint.swift; sourceTree = "<group>"; };
|
||||
FD83B9D127D59495005E1583 /* MockUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserDefaults.swift; sourceTree = "<group>"; };
|
||||
FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryWithDependencies.swift; sourceTree = "<group>"; };
|
||||
FD848B86283B844B000E298B /* MessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewModel.swift; sourceTree = "<group>"; };
|
||||
FD848B8A283DC509000E298B /* PagedDatabaseObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedDatabaseObserver.swift; sourceTree = "<group>"; };
|
||||
FD848B8C283E0B26000E298B /* MessageInputTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageInputTypes.swift; sourceTree = "<group>"; };
|
||||
|
@ -1833,11 +1846,6 @@
|
|||
FD859EEF27BF207700510D0C /* SessionProtos.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = SessionProtos.proto; sourceTree = "<group>"; };
|
||||
FD859EF027BF207C00510D0C /* WebSocketResources.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = WebSocketResources.proto; sourceTree = "<group>"; };
|
||||
FD859EF127BF6BA200510D0C /* Data+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD859EF327C2F49200510D0C /* MockSodium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSodium.swift; sourceTree = "<group>"; };
|
||||
FD859EF527C2F52C00510D0C /* MockSign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSign.swift; sourceTree = "<group>"; };
|
||||
FD859EF727C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAeadXChaCha20Poly1305Ietf.swift; sourceTree = "<group>"; };
|
||||
FD859EF927C2F5C500510D0C /* MockGenericHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGenericHash.swift; sourceTree = "<group>"; };
|
||||
FD859EFB27C2F60700510D0C /* MockEd25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockEd25519.swift; sourceTree = "<group>"; };
|
||||
FD87DCF928B74DB300AF0F98 /* ConversationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactsViewModel.swift; sourceTree = "<group>"; };
|
||||
FD87DCFF28B820F200AF0F98 /* BlockedContactCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactCell.swift; sourceTree = "<group>"; };
|
||||
|
@ -1858,6 +1866,7 @@
|
|||
FD97B2412A3FEBF30027DD57 /* UnreadMarkerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnreadMarkerCell.swift; sourceTree = "<group>"; };
|
||||
FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchResponseSpec.swift; sourceTree = "<group>"; };
|
||||
FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSessionUtil.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
FD9DD2702A72516D00ECB68E /* TestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestExtensions.swift; sourceTree = "<group>"; };
|
||||
FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserGroupsSpec.swift; sourceTree = "<group>"; };
|
||||
FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionSpec.swift; sourceTree = "<group>"; };
|
||||
FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Shared.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1882,13 +1891,10 @@
|
|||
FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSErrorSpec.swift; sourceTree = "<group>"; };
|
||||
FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalizationSpec.swift; sourceTree = "<group>"; };
|
||||
FDC2909927D71376005DAE71 /* NonceGeneratorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceGeneratorSpec.swift; sourceTree = "<group>"; };
|
||||
FDC2909B27D713D2005DAE71 /* SodiumProtocolsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SodiumProtocolsSpec.swift; sourceTree = "<group>"; };
|
||||
FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupManagerSpec.swift; sourceTree = "<group>"; };
|
||||
FDC290A527D860CE005DAE71 /* Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mock.swift; sourceTree = "<group>"; };
|
||||
FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NimbleExtensions.swift; sourceTree = "<group>"; };
|
||||
FDC290B227DFF9F5005DAE71 /* TestOnionRequestAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestOnionRequestAPI.swift; sourceTree = "<group>"; };
|
||||
FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPIError.swift; sourceTree = "<group>"; };
|
||||
FDC4381427B329CE00C60D73 /* NonceGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceGenerator.swift; sourceTree = "<group>"; };
|
||||
FDC4381627B32EC700C60D73 /* Personalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Personalization.swift; sourceTree = "<group>"; };
|
||||
FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSEndpoint.swift; sourceTree = "<group>"; };
|
||||
FDC4382E27B383AF00C60D73 /* PushServerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushServerResponse.swift; sourceTree = "<group>"; };
|
||||
|
@ -1910,8 +1916,6 @@
|
|||
FDC438B027BB159600C60D73 /* RequestInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestInfo.swift; sourceTree = "<group>"; };
|
||||
FDC438B227BB15B400C60D73 /* ResponseInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseInfo.swift; sourceTree = "<group>"; };
|
||||
FDC438BC27BB2AB400C60D73 /* Mockable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mockable.swift; sourceTree = "<group>"; };
|
||||
FDC438C027BB4E6800C60D73 /* SMKDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKDependencies.swift; sourceTree = "<group>"; };
|
||||
FDC438C227BB512200C60D73 /* SodiumProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SodiumProtocols.swift; sourceTree = "<group>"; };
|
||||
FDC438C627BB6DF000C60D73 /* DirectMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessage.swift; sourceTree = "<group>"; };
|
||||
FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageRequest.swift; sourceTree = "<group>"; };
|
||||
FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMessageRequest.swift; sourceTree = "<group>"; };
|
||||
|
@ -1929,8 +1933,8 @@
|
|||
FDDCBDA529E776BF00303C38 /* seed2-2023-2y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed2-2023-2y.der"; sourceTree = "<group>"; };
|
||||
FDDCBDA629E776BF00303C38 /* seed3-2023-2y.crt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "seed3-2023-2y.crt"; sourceTree = "<group>"; };
|
||||
FDDCBDA729E776BF00303C38 /* seed3-2023-2y.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "seed3-2023-2y.der"; sourceTree = "<group>"; };
|
||||
FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerSpec.swift; sourceTree = "<group>"; };
|
||||
FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FetchRequest+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerSpec.swift; sourceTree = "<group>"; };
|
||||
FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CryptoKit+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FDE658A229418E2F00A33BC1 /* KeyPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPair.swift; sourceTree = "<group>"; };
|
||||
FDE6E99729F8E63A00F93C5D /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = "<group>"; };
|
||||
|
@ -1968,7 +1972,6 @@
|
|||
FDF8488229405A12007DCAE5 /* BatchResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatchResponse.swift; sourceTree = "<group>"; };
|
||||
FDF8488529405A60007DCAE5 /* Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
|
||||
FDF8488729405A9A007DCAE5 /* SOGSBatchRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SOGSBatchRequest.swift; sourceTree = "<group>"; };
|
||||
FDF8488A29405BF2007DCAE5 /* SSKDependencies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSKDependencies.swift; sourceTree = "<group>"; };
|
||||
FDF8488D29405C04007DCAE5 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = "<group>"; };
|
||||
FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPINamespace.swift; sourceTree = "<group>"; };
|
||||
FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPI.swift; sourceTree = "<group>"; };
|
||||
|
@ -2026,6 +2029,7 @@
|
|||
FDFDE127282D05530098B17F /* MediaPresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPresentationContext.swift; sourceTree = "<group>"; };
|
||||
FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaZoomAnimationController.swift; sourceTree = "<group>"; };
|
||||
FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identity+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONEncoder+Utilities.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -2556,6 +2560,7 @@
|
|||
FDC438B227BB15B400C60D73 /* ResponseInfo.swift */,
|
||||
C33FDAF2255A580500E217F9 /* ProxiedContentDownloader.swift */,
|
||||
C3C2A5BC255385EE00C340D1 /* HTTP.swift */,
|
||||
FD23CE1A2A651E6D0000B97C /* NetworkType.swift */,
|
||||
FDF848EE294067E4007DCAE5 /* URLResponse+Utilities.swift */,
|
||||
);
|
||||
path = Networking;
|
||||
|
@ -2583,14 +2588,15 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
7BD477A727EC39F5004E2822 /* Atomic.swift */,
|
||||
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */,
|
||||
7BAF54D527ACD0E2003D12F8 /* ReusableView.swift */,
|
||||
C33FDB8A255A581200E217F9 /* AppContext.h */,
|
||||
C33FDB85255A581100E217F9 /* AppContext.m */,
|
||||
C3C2A5D12553860800C340D1 /* Array+Utilities.swift */,
|
||||
FDC4383D27B4708600C60D73 /* Atomic.swift */,
|
||||
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */,
|
||||
C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */,
|
||||
B8F5F58225EC94A6003BF8D4 /* Collection+Utilities.swift */,
|
||||
FD23CE2F2A67B8820000B97C /* Caches.swift */,
|
||||
FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */,
|
||||
FDC6D75F2862B3F600B04575 /* Dependencies.swift */,
|
||||
C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */,
|
||||
|
@ -3180,6 +3186,7 @@
|
|||
C3A721332558BDDF0043A11F /* Open Groups */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD23CE202A661CE80000B97C /* Crypto */,
|
||||
FDC4381827B34EAD00C60D73 /* Models */,
|
||||
FDC4380727B31D3A00C60D73 /* Types */,
|
||||
FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */,
|
||||
|
@ -3204,9 +3211,10 @@
|
|||
children = (
|
||||
C33FDB01255A580700E217F9 /* AppReadiness.h */,
|
||||
C33FDB75255A581000E217F9 /* AppReadiness.m */,
|
||||
FDF0B7542807C4BB004C14C5 /* Environment.swift */,
|
||||
FD23CE232A675C440000B97C /* Crypto+SessionMessagingKit.swift */,
|
||||
FD859EF127BF6BA200510D0C /* Data+Utilities.swift */,
|
||||
C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */,
|
||||
FDF0B7542807C4BB004C14C5 /* Environment.swift */,
|
||||
C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */,
|
||||
C3A71D0A2558989C0043A11F /* MessageWrapper.swift */,
|
||||
C38EF2F5255B6DBC007E1867 /* OWSAudioPlayer.h */,
|
||||
|
@ -3220,7 +3228,6 @@
|
|||
C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */,
|
||||
C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */,
|
||||
FDC4386827B4E6B700C60D73 /* String+Utlities.swift */,
|
||||
FD772899284AF1BD0018502F /* Sodium+Utilities.swift */,
|
||||
FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */,
|
||||
C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */,
|
||||
C3ECBF7A257056B700EA7FCE /* Threading.swift */,
|
||||
|
@ -3239,7 +3246,6 @@
|
|||
FDF8488C29405C04007DCAE5 /* Jobs */,
|
||||
FDF8489229405C1B007DCAE5 /* Networking */,
|
||||
C3C2A5CD255385F300C340D1 /* Utilities */,
|
||||
FDF8488A29405BF2007DCAE5 /* SSKDependencies.swift */,
|
||||
C3C2A5B9255385ED00C340D1 /* Configuration.swift */,
|
||||
);
|
||||
path = SessionSnodeKit;
|
||||
|
@ -3306,7 +3312,6 @@
|
|||
FD8ECF7529340F4800C0D1BB /* SessionUtil */,
|
||||
FD3E0C82283B581F002A425C /* Shared Models */,
|
||||
C3BBE0B32554F0D30050F1E3 /* Utilities */,
|
||||
FDC438C027BB4E6800C60D73 /* SMKDependencies.swift */,
|
||||
FD245C612850664300B966DD /* Configuration.swift */,
|
||||
);
|
||||
path = SessionMessagingKit;
|
||||
|
@ -3552,7 +3557,10 @@
|
|||
FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */,
|
||||
FD3003692A3ADD6000B5A5FB /* CExceptionHelper.h */,
|
||||
FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */,
|
||||
FD23CE1E2A65269C0000B97C /* Crypto.swift */,
|
||||
FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */,
|
||||
FD09796A27F6C67500936362 /* Failable.swift */,
|
||||
FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */,
|
||||
FD09797127FAA2F500936362 /* Optional+Utilities.swift */,
|
||||
FD09797C27FBDB2000936362 /* Notification+Utilities.swift */,
|
||||
FDF222082818D2B0000A4995 /* NSAttributedString+Utilities.swift */,
|
||||
|
@ -3736,6 +3744,14 @@
|
|||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD23CE202A661CE80000B97C /* Crypto */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */,
|
||||
);
|
||||
path = Crypto;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD29598E2A43BE5400888A17 /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -3838,7 +3854,7 @@
|
|||
FD3C906827E417B100CD579F /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD3C906927E417CE00CD579F /* SodiumUtilitiesSpec.swift */,
|
||||
FD3C906927E417CE00CD579F /* CryptoSMKSpec.swift */,
|
||||
);
|
||||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3876,6 +3892,7 @@
|
|||
children = (
|
||||
FD7115F628C8150D00B47552 /* Disposable Views */,
|
||||
FD7115FD28C8202D00B47552 /* ReplaySubject.swift */,
|
||||
FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */,
|
||||
FD7115FB28C8155800B47552 /* Publisher+Utilities.swift */,
|
||||
FD71160128C8255900B47552 /* UIControl+Combine.swift */,
|
||||
FD7115F928C8153400B47552 /* UIBarButtonItem+Combine.swift */,
|
||||
|
@ -4011,6 +4028,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
FD0B77B129B82B7A009169BA /* ArrayUtilitiesSpec.swift */,
|
||||
FD23CE252A676B5B0000B97C /* DependenciesSpec.swift */,
|
||||
FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */,
|
||||
);
|
||||
path = General;
|
||||
|
@ -4020,9 +4038,14 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
FDC290A527D860CE005DAE71 /* Mock.swift */,
|
||||
FD0969F82A69FFE700C5C365 /* Mocked.swift */,
|
||||
FD23CE272A67755C0000B97C /* MockCrypto.swift */,
|
||||
FD23CE2B2A678DF80000B97C /* MockCaches.swift */,
|
||||
FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */,
|
||||
FD23CE312A67C38D0000B97C /* MockNetwork.swift */,
|
||||
FD96F3A629DBD43D00401309 /* MockJobRunner.swift */,
|
||||
FD83B9BD27CF2243005E1583 /* TestConstants.swift */,
|
||||
FD9DD2702A72516D00ECB68E /* TestExtensions.swift */,
|
||||
FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */,
|
||||
FD078E4727E02561000769AF /* CommonMockedExtensions.swift */,
|
||||
FD23EA6028ED0B260058676E /* CombineExtensions.swift */,
|
||||
|
@ -4141,7 +4164,6 @@
|
|||
FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */,
|
||||
FDC2909327D710B4005DAE71 /* SOGSEndpointSpec.swift */,
|
||||
FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */,
|
||||
FDC2909B27D713D2005DAE71 /* SodiumProtocolsSpec.swift */,
|
||||
);
|
||||
path = Types;
|
||||
sourceTree = "<group>";
|
||||
|
@ -4155,8 +4177,6 @@
|
|||
FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */,
|
||||
FDC4381627B32EC700C60D73 /* Personalization.swift */,
|
||||
FD2959912A4417A900888A17 /* PreparedSendData.swift */,
|
||||
FDC4381427B329CE00C60D73 /* NonceGenerator.swift */,
|
||||
FDC438C227BB512200C60D73 /* SodiumProtocols.swift */,
|
||||
);
|
||||
path = Types;
|
||||
sourceTree = "<group>";
|
||||
|
@ -4248,19 +4268,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
FDC438BC27BB2AB400C60D73 /* Mockable.swift */,
|
||||
FD859EF327C2F49200510D0C /* MockSodium.swift */,
|
||||
FD3C906E27E43E8700CD579F /* MockBox.swift */,
|
||||
FD859EF927C2F5C500510D0C /* MockGenericHash.swift */,
|
||||
FD859EF527C2F52C00510D0C /* MockSign.swift */,
|
||||
FD859EF727C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift */,
|
||||
FD859EFB27C2F60700510D0C /* MockEd25519.swift */,
|
||||
FD078E5927E29F09000769AF /* MockNonce16Generator.swift */,
|
||||
FD078E5B27E29F78000769AF /* MockNonce24Generator.swift */,
|
||||
FD83B9D127D59495005E1583 /* MockUserDefaults.swift */,
|
||||
FD078E4C27E17156000769AF /* MockOGMCache.swift */,
|
||||
FDC290B227DFF9F5005DAE71 /* TestOnionRequestAPI.swift */,
|
||||
FD078E4E27E175F1000769AF /* DependencyExtensions.swift */,
|
||||
FD078E5127E1760A000769AF /* OGMDependencyExtensions.swift */,
|
||||
);
|
||||
path = _TestUtilities;
|
||||
sourceTree = "<group>";
|
||||
|
@ -4427,9 +4436,6 @@
|
|||
C33FDDB0255A582000E217F9 /* NSURLSessionDataTask+StatusCode.h in Headers */,
|
||||
C33FDDD0255A582000E217F9 /* FunctionalUtil.h in Headers */,
|
||||
C33FDD5B255A582000E217F9 /* OWSOperation.h in Headers */,
|
||||
C38EF24C255B6D67007E1867 /* NSAttributedString+OWS.h in Headers */,
|
||||
C38EF32B255B6DBF007E1867 /* OWSFormat.h in Headers */,
|
||||
C33FDD7C255A582000E217F9 /* SSKAsserts.h in Headers */,
|
||||
C33FDDCC255A582000E217F9 /* TSConstants.h in Headers */,
|
||||
C33FDDBD255A582000E217F9 /* ByteParser.h in Headers */,
|
||||
C33FDDB3255A582000E217F9 /* OWSError.h in Headers */,
|
||||
|
@ -4631,9 +4637,9 @@
|
|||
D221A085169C9E5E00537ABF /* Sources */,
|
||||
D221A086169C9E5E00537ABF /* Frameworks */,
|
||||
D221A087169C9E5E00537ABF /* Resources */,
|
||||
FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */,
|
||||
453518771FC635DD00210559 /* Embed Foundation Extensions */,
|
||||
4535189F1FC63DBF00210559 /* Embed Frameworks */,
|
||||
FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */,
|
||||
90DF4725BB1271EBA2C66A12 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
|
@ -5557,7 +5563,6 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FDF8488B29405BF2007DCAE5 /* SSKDependencies.swift in Sources */,
|
||||
FDF8488E29405C04007DCAE5 /* GetSnodePoolJob.swift in Sources */,
|
||||
FDF848C329405C5A007DCAE5 /* DeleteMessagesRequest.swift in Sources */,
|
||||
FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */,
|
||||
|
@ -5629,10 +5634,13 @@
|
|||
FDFD645927F26C6800808CA1 /* Array+Utilities.swift in Sources */,
|
||||
7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */,
|
||||
C32C5D83256DD5B6003C73A2 /* SSKKeychainStorage.swift in Sources */,
|
||||
FD559DF52A7368CB00C7C62A /* DispatchQueue+Utilities.swift in Sources */,
|
||||
FDF8488329405A12007DCAE5 /* BatchResponse.swift in Sources */,
|
||||
C3D9E39B256763C20040E4F3 /* AppContext.m in Sources */,
|
||||
FDFF9FDF2A787F57005E0628 /* JSONEncoder+Utilities.swift in Sources */,
|
||||
C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */,
|
||||
FD17D7C127F5200100122BE0 /* TypedTableDefinition.swift in Sources */,
|
||||
FD23CE1B2A651E6D0000B97C /* NetworkType.swift in Sources */,
|
||||
FD17D7EA27F6A1C600122BE0 /* SUKLegacy.swift in Sources */,
|
||||
FDA8EB10280F8238002B68E5 /* Codable+Utilities.swift in Sources */,
|
||||
FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */,
|
||||
|
@ -5690,8 +5698,10 @@
|
|||
C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */,
|
||||
B8F5F58325EC94A6003BF8D4 /* Collection+Utilities.swift in Sources */,
|
||||
7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */,
|
||||
FD23CE1F2A65269C0000B97C /* Crypto.swift in Sources */,
|
||||
B8BC00C0257D90E30032E807 /* General.swift in Sources */,
|
||||
FDF8488629405A61007DCAE5 /* Request.swift in Sources */,
|
||||
FD23CE302A67B8820000B97C /* Caches.swift in Sources */,
|
||||
FD17D7A127F40D2500122BE0 /* Storage.swift in Sources */,
|
||||
FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */,
|
||||
FD5D201E27B0D87C00FEA984 /* SessionId.swift in Sources */,
|
||||
|
@ -5701,6 +5711,7 @@
|
|||
FDF22211281B5E0B000A4995 /* TableRecord+Utilities.swift in Sources */,
|
||||
B88FA7FB26114EA70049422F /* Hex.swift in Sources */,
|
||||
FD7728962849E7E90018502F /* String+Utilities.swift in Sources */,
|
||||
FD83DCDD2A739D350065FFAE /* RetryWithDependencies.swift in Sources */,
|
||||
C35D0DB525AE5F1200B6BF49 /* UIEdgeInsets.swift in Sources */,
|
||||
C3D9E4F4256778AF0040E4F3 /* NSData+Image.m in Sources */,
|
||||
FDF222092818D2B0000A4995 /* NSAttributedString+Utilities.swift in Sources */,
|
||||
|
@ -5753,7 +5764,6 @@
|
|||
FDF0B74928060D13004C14C5 /* QuotedReplyModel.swift in Sources */,
|
||||
FD3003662A25D5B300B5A5FB /* ConfigMessageReceiveJob.swift in Sources */,
|
||||
7B81682C28B72F480069F315 /* PendingChange.swift in Sources */,
|
||||
FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */,
|
||||
FD5C7309285007920029977D /* BlindedIdLookup.swift in Sources */,
|
||||
FD71161C28D194FB00B47552 /* MentionInfo.swift in Sources */,
|
||||
7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */,
|
||||
|
@ -5851,9 +5861,9 @@
|
|||
FD432434299C6985008A0213 /* PendingReadReceipt.swift in Sources */,
|
||||
FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */,
|
||||
FD245C51285065CC00B966DD /* MessageReceiver.swift in Sources */,
|
||||
FD23CE222A661D000000B97C /* OpenGroupAPI+Crypto.swift in Sources */,
|
||||
FD245C652850665400B966DD /* ClosedGroupControlMessage.swift in Sources */,
|
||||
FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */,
|
||||
FDC4381527B329CE00C60D73 /* NonceGenerator.swift in Sources */,
|
||||
7B93D07027CF194000811CB6 /* ConfigurationMessage+Convenience.swift in Sources */,
|
||||
FD5C72FD284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift in Sources */,
|
||||
C32C5A88256DBCF9003C73A2 /* MessageReceiver+ClosedGroups.swift in Sources */,
|
||||
|
@ -5882,7 +5892,7 @@
|
|||
FDA1E83B29A5F2D500C5C3BD /* SessionUtil+Shared.swift in Sources */,
|
||||
C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */,
|
||||
FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */,
|
||||
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */,
|
||||
FD23CE242A675C440000B97C /* Crypto+SessionMessagingKit.swift in Sources */,
|
||||
B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */,
|
||||
C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */,
|
||||
FD716E722850647600C96BF4 /* Data+Utilities.swift in Sources */,
|
||||
|
@ -5914,7 +5924,6 @@
|
|||
FD09798127FCFEE800936362 /* SessionThread.swift in Sources */,
|
||||
FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */,
|
||||
FDF0B75E280AAF35004C14C5 /* Preferences.swift in Sources */,
|
||||
FDC438C127BB4E6800C60D73 /* SMKDependencies.swift in Sources */,
|
||||
FDC4383827B3863200C60D73 /* VersionResponse.swift in Sources */,
|
||||
B806ECA126C4A7E4008BDA44 /* WebRTCSession+UI.swift in Sources */,
|
||||
FD432432299C6933008A0213 /* _011_AddPendingReadReceipts.swift in Sources */,
|
||||
|
@ -6140,14 +6149,18 @@
|
|||
files = (
|
||||
FD71161728D00DA400B47552 /* ThreadSettingsViewModelSpec.swift in Sources */,
|
||||
FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */,
|
||||
FD23CE292A6775650000B97C /* MockCrypto.swift in Sources */,
|
||||
FD23CE332A67C4D90000B97C /* MockNetwork.swift in Sources */,
|
||||
FD71161528D00D6700B47552 /* ThreadDisappearingMessagesViewModelSpec.swift in Sources */,
|
||||
FD23CE2D2A678E1E0000B97C /* MockCaches.swift in Sources */,
|
||||
FD23EA5E28ED00FD0058676E /* NimbleExtensions.swift in Sources */,
|
||||
FD23EA5F28ED00FF0058676E /* CommonMockedExtensions.swift in Sources */,
|
||||
FD96F3A829DBD4AD00401309 /* MockJobRunner.swift in Sources */,
|
||||
FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */,
|
||||
FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */,
|
||||
FD0969FA2A6A00B000C5C365 /* Mocked.swift in Sources */,
|
||||
FD23EA5C28ED00F80058676E /* Mock.swift in Sources */,
|
||||
FD23EA6128ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||
FD9DD2712A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||
FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -6157,18 +6170,23 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FD2AAAF228ED57B500A49611 /* SynchronousStorage.swift in Sources */,
|
||||
FD96F3A929DBD4AD00401309 /* MockJobRunner.swift in Sources */,
|
||||
FD078E4927E02576000769AF /* CommonMockedExtensions.swift in Sources */,
|
||||
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||
FD9DD2732A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
|
||||
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||
FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||
FD23CE352A67C4DA0000B97C /* MockNetwork.swift in Sources */,
|
||||
FD23CE282A67755C0000B97C /* MockCrypto.swift in Sources */,
|
||||
FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */,
|
||||
FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */,
|
||||
FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */,
|
||||
FD23CE2C2A678DF80000B97C /* MockCaches.swift in Sources */,
|
||||
FD0B77B229B82B7A009169BA /* ArrayUtilitiesSpec.swift in Sources */,
|
||||
FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */,
|
||||
FD23CE262A676B5B0000B97C /* DependenciesSpec.swift in Sources */,
|
||||
FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */,
|
||||
FD0969FB2A6A00B100C5C365 /* Mocked.swift in Sources */,
|
||||
FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */,
|
||||
FD2959902A43BE5F00888A17 /* VersionSpec.swift in Sources */,
|
||||
);
|
||||
|
@ -6180,56 +6198,49 @@
|
|||
files = (
|
||||
FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */,
|
||||
FDDC08F229A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift in Sources */,
|
||||
FD859EFA27C2F5C500510D0C /* MockGenericHash.swift in Sources */,
|
||||
FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */,
|
||||
FD96F3A529DBC3DC00401309 /* MessageSendJobSpec.swift in Sources */,
|
||||
FDC290B327DFF9F5005DAE71 /* TestOnionRequestAPI.swift in Sources */,
|
||||
FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */,
|
||||
FD3C906A27E417CE00CD579F /* SodiumUtilitiesSpec.swift in Sources */,
|
||||
FD3C906A27E417CE00CD579F /* CryptoSMKSpec.swift in Sources */,
|
||||
FD96F3A729DBD43D00401309 /* MockJobRunner.swift in Sources */,
|
||||
FDBB25E12983909300F1508E /* ConfigConvoInfoVolatileSpec.swift in Sources */,
|
||||
FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */,
|
||||
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */,
|
||||
FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */,
|
||||
FD078E4F27E175F1000769AF /* DependencyExtensions.swift in Sources */,
|
||||
FDC2909C27D713D2005DAE71 /* SodiumProtocolsSpec.swift in Sources */,
|
||||
FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */,
|
||||
FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */,
|
||||
FDC2909A27D71376005DAE71 /* NonceGeneratorSpec.swift in Sources */,
|
||||
FD2AAAF128ED57B500A49611 /* SynchronousStorage.swift in Sources */,
|
||||
FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */,
|
||||
FD859EF827C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift in Sources */,
|
||||
FD23CE2A2A6775660000B97C /* MockCrypto.swift in Sources */,
|
||||
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */,
|
||||
FD859EF427C2F49200510D0C /* MockSodium.swift in Sources */,
|
||||
FD078E4D27E17156000769AF /* MockOGMCache.swift in Sources */,
|
||||
FD078E5227E1760A000769AF /* OGMDependencyExtensions.swift in Sources */,
|
||||
FD9DD2722A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||
FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */,
|
||||
FD859EFC27C2F60700510D0C /* MockEd25519.swift in Sources */,
|
||||
FDC290A627D860CE005DAE71 /* Mock.swift in Sources */,
|
||||
FD2B4AFB29429D1000AB4848 /* ConfigContactsSpec.swift in Sources */,
|
||||
FDA1E83D29AC71A800C5C3BD /* SessionUtilSpec.swift in Sources */,
|
||||
FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||
FD3C906F27E43E8700CD579F /* MockBox.swift in Sources */,
|
||||
FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */,
|
||||
FD23CE2E2A678E1E0000B97C /* MockCaches.swift in Sources */,
|
||||
FD23EA6228ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||
FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */,
|
||||
FD23CE342A67C4D90000B97C /* MockNetwork.swift in Sources */,
|
||||
FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */,
|
||||
FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */,
|
||||
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||
FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */,
|
||||
FD0969F92A69FFE700C5C365 /* Mocked.swift in Sources */,
|
||||
FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */,
|
||||
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */,
|
||||
FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */,
|
||||
FD859EF627C2F52C00510D0C /* MockSign.swift in Sources */,
|
||||
FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */,
|
||||
FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */,
|
||||
FD078E5A27E29F09000769AF /* MockNonce16Generator.swift in Sources */,
|
||||
FD3C906D27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift in Sources */,
|
||||
FDC2908D27D70905005DAE71 /* UpdateMessageRequestSpec.swift in Sources */,
|
||||
FD078E5427E197CA000769AF /* OpenGroupManagerSpec.swift in Sources */,
|
||||
FD3C906727E416AF00CD579F /* BlindedIdLookupSpec.swift in Sources */,
|
||||
FD83B9D227D59495005E1583 /* MockUserDefaults.swift in Sources */,
|
||||
FD078E5C27E29F78000769AF /* MockNonce24Generator.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -35,15 +35,15 @@ extension ContextMenuVC {
|
|||
|
||||
// MARK: - Actions
|
||||
|
||||
static func info(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func info(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_info"),
|
||||
title: "context_menu_info".localized(),
|
||||
accessibilityLabel: "Message info"
|
||||
) { delegate?.info(cellViewModel) }
|
||||
) { delegate?.info(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func retry(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func retry(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(systemName: "arrow.triangle.2.circlepath"),
|
||||
title: (cellViewModel.state == .failedToSync ?
|
||||
|
@ -51,23 +51,23 @@ extension ContextMenuVC {
|
|||
"context_menu_resend".localized()
|
||||
),
|
||||
accessibilityLabel: (cellViewModel.state == .failedToSync ? "Resync message" : "Resend message")
|
||||
) { delegate?.retry(cellViewModel) }
|
||||
) { delegate?.retry(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func reply(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func reply(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_reply"),
|
||||
title: "context_menu_reply".localized(),
|
||||
accessibilityLabel: "Reply to message"
|
||||
) { delegate?.reply(cellViewModel) }
|
||||
) { delegate?.reply(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func copy(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func copy(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_copy"),
|
||||
title: "copy".localized(),
|
||||
accessibilityLabel: "Copy text"
|
||||
) { delegate?.copy(cellViewModel) }
|
||||
) { delegate?.copy(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func copySessionID(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
|
@ -79,50 +79,50 @@ extension ContextMenuVC {
|
|||
) { delegate?.copySessionID(cellViewModel) }
|
||||
}
|
||||
|
||||
static func delete(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func delete(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_trash"),
|
||||
title: "TXT_DELETE_TITLE".localized(),
|
||||
accessibilityLabel: "Delete message"
|
||||
) { delegate?.delete(cellViewModel) }
|
||||
) { delegate?.delete(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func save(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func save(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_download"),
|
||||
title: "context_menu_save".localized(),
|
||||
accessibilityLabel: "Save attachment"
|
||||
) { delegate?.save(cellViewModel) }
|
||||
) { delegate?.save(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func ban(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func ban(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_block"),
|
||||
title: "context_menu_ban_user".localized(),
|
||||
accessibilityLabel: "Ban user"
|
||||
) { delegate?.ban(cellViewModel) }
|
||||
) { delegate?.ban(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_block"),
|
||||
title: "context_menu_ban_and_delete_all".localized(),
|
||||
accessibilityLabel: "Ban user and delete"
|
||||
) { delegate?.banAndDeleteAllMessages(cellViewModel) }
|
||||
) { delegate?.banAndDeleteAllMessages(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func react(_ cellViewModel: MessageViewModel, _ emoji: EmojiWithSkinTones, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func react(_ cellViewModel: MessageViewModel, _ emoji: EmojiWithSkinTones, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
title: emoji.rawValue,
|
||||
isEmojiAction: true
|
||||
) { delegate?.react(cellViewModel, with: emoji) }
|
||||
) { delegate?.react(cellViewModel, with: emoji, using: dependencies) }
|
||||
}
|
||||
|
||||
static func emojiPlusButton(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func emojiPlusButton(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
isEmojiPlus: true,
|
||||
accessibilityLabel: "Add emoji"
|
||||
) { delegate?.showFullEmojiKeyboard(cellViewModel) }
|
||||
) { delegate?.showFullEmojiKeyboard(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func dismiss(_ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
|
@ -150,7 +150,8 @@ extension ContextMenuVC {
|
|||
currentUserBlinded25PublicKey: String?,
|
||||
currentUserIsOpenGroupModerator: Bool,
|
||||
currentThreadIsMessageRequest: Bool,
|
||||
delegate: ContextMenuActionDelegate?
|
||||
delegate: ContextMenuActionDelegate?,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> [Action]? {
|
||||
switch cellViewModel.variant {
|
||||
case .standardIncomingDeleted, .infoCall,
|
||||
|
@ -159,7 +160,7 @@ extension ContextMenuVC {
|
|||
.infoClosedGroupCurrentUserLeft, .infoClosedGroupCurrentUserLeaving, .infoClosedGroupCurrentUserErrorLeaving,
|
||||
.infoMessageRequestAccepted, .infoDisappearingMessagesUpdate:
|
||||
// Let the user delete info messages and unsent messages
|
||||
return [ Action.delete(cellViewModel, delegate) ]
|
||||
return [ Action.delete(cellViewModel, delegate, using: dependencies) ]
|
||||
|
||||
case .standardOutgoing, .standardIncoming: break
|
||||
}
|
||||
|
@ -227,18 +228,21 @@ extension ContextMenuVC {
|
|||
let shouldShowInfo: Bool = (cellViewModel.attachments?.isEmpty == false)
|
||||
|
||||
let generatedActions: [Action] = [
|
||||
(canRetry ? Action.retry(cellViewModel, delegate) : nil),
|
||||
(viewModelCanReply(cellViewModel) ? Action.reply(cellViewModel, delegate) : nil),
|
||||
(canCopy ? Action.copy(cellViewModel, delegate) : nil),
|
||||
(canSave ? Action.save(cellViewModel, delegate) : nil),
|
||||
(canRetry ? Action.retry(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(viewModelCanReply(cellViewModel) ? Action.reply(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(canCopy ? Action.copy(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(canSave ? Action.save(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(canCopySessionId ? Action.copySessionID(cellViewModel, delegate) : nil),
|
||||
(canDelete ? Action.delete(cellViewModel, delegate) : nil),
|
||||
(canBan ? Action.ban(cellViewModel, delegate) : nil),
|
||||
(canBan ? Action.banAndDeleteAllMessages(cellViewModel, delegate) : nil),
|
||||
(shouldShowInfo ? Action.info(cellViewModel, delegate) : nil),
|
||||
(canDelete ? Action.delete(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(canBan ? Action.ban(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(canBan ? Action.banAndDeleteAllMessages(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(shouldShowInfo ? Action.info(cellViewModel, delegate, using: dependencies) : nil),
|
||||
]
|
||||
.appending(contentsOf: (shouldShowEmojiActions ? recentEmojis : []).map { Action.react(cellViewModel, $0, delegate) })
|
||||
.appending(Action.emojiPlusButton(cellViewModel, delegate))
|
||||
.appending(
|
||||
contentsOf: (shouldShowEmojiActions ? recentEmojis : [])
|
||||
.map { Action.react(cellViewModel, $0, delegate, using: dependencies) }
|
||||
)
|
||||
.appending(Action.emojiPlusButton(cellViewModel, delegate, using: dependencies))
|
||||
.compactMap { $0 }
|
||||
|
||||
guard !generatedActions.isEmpty else { return [] }
|
||||
|
@ -250,16 +254,16 @@ extension ContextMenuVC {
|
|||
// MARK: - Delegate
|
||||
|
||||
protocol ContextMenuActionDelegate {
|
||||
func info(_ cellViewModel: MessageViewModel)
|
||||
func retry(_ cellViewModel: MessageViewModel)
|
||||
func reply(_ cellViewModel: MessageViewModel)
|
||||
func copy(_ cellViewModel: MessageViewModel)
|
||||
func info(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func retry(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func reply(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func copy(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func copySessionID(_ cellViewModel: MessageViewModel)
|
||||
func delete(_ cellViewModel: MessageViewModel)
|
||||
func save(_ cellViewModel: MessageViewModel)
|
||||
func ban(_ cellViewModel: MessageViewModel)
|
||||
func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel)
|
||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones)
|
||||
func showFullEmojiKeyboard(_ cellViewModel: MessageViewModel)
|
||||
func delete(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func save(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func ban(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones, using dependencies: Dependencies)
|
||||
func showFullEmojiKeyboard(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func contextMenuDismissed()
|
||||
}
|
||||
|
|
|
@ -173,8 +173,8 @@ extension ConversationVC:
|
|||
|
||||
// MARK: - AttachmentApprovalViewControllerDelegate
|
||||
|
||||
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?) {
|
||||
sendMessage(text: (messageText ?? ""), attachments: attachments)
|
||||
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?, using dependencies: Dependencies) {
|
||||
sendMessage(text: (messageText ?? ""), attachments: attachments, using: dependencies)
|
||||
resetMentions()
|
||||
|
||||
dismiss(animated: true) { [weak self] in
|
||||
|
@ -409,7 +409,8 @@ extension ConversationVC:
|
|||
attachments: [SignalAttachment] = [],
|
||||
linkPreviewDraft: LinkPreviewDraft? = nil,
|
||||
quoteModel: QuotedReplyModel? = nil,
|
||||
hasPermissionToSendSeed: Bool = false
|
||||
hasPermissionToSendSeed: Bool = false,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
guard !showBlockedModalIfNeeded() else { return }
|
||||
|
||||
|
@ -488,7 +489,7 @@ extension ConversationVC:
|
|||
let quoteThumbnailAttachment: Attachment? = quoteModel?.attachment?.cloneAsQuoteThumbnail()
|
||||
|
||||
// Actually send the message
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { [weak self] db in
|
||||
// Update the thread to be visible (if it isn't already)
|
||||
if self?.viewModel.threadData.threadShouldBeVisible == false {
|
||||
|
@ -536,7 +537,8 @@ extension ConversationVC:
|
|||
db,
|
||||
interaction: insertedInteraction,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
|
@ -787,10 +789,14 @@ extension ConversationVC:
|
|||
self.contextMenuWindow?.makeKeyAndVisible()
|
||||
}
|
||||
|
||||
func handleItemTapped(_ cellViewModel: MessageViewModel, gestureRecognizer: UITapGestureRecognizer) {
|
||||
func handleItemTapped(
|
||||
_ cellViewModel: MessageViewModel,
|
||||
gestureRecognizer: UITapGestureRecognizer,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
guard cellViewModel.variant != .standardOutgoing || (cellViewModel.state != .failed && cellViewModel.state != .failedToSync) else {
|
||||
// Show the failed message sheet
|
||||
showFailedMessageSheet(for: cellViewModel)
|
||||
showFailedMessageSheet(for: cellViewModel, using: dependencies)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -864,8 +870,8 @@ extension ConversationVC:
|
|||
let threadId: String = self.viewModel.threadData.threadId
|
||||
|
||||
// Retry downloading the failed attachment
|
||||
Storage.shared.writeAsync { db in
|
||||
JobRunner.add(
|
||||
dependencies.storage.writeAsync { db in
|
||||
dependencies.jobRunner.add(
|
||||
db,
|
||||
job: Job(
|
||||
variant: .attachmentDownload,
|
||||
|
@ -874,7 +880,9 @@ extension ConversationVC:
|
|||
details: AttachmentDownloadJob.Details(
|
||||
attachmentId: mediaView.attachment.id
|
||||
)
|
||||
)
|
||||
),
|
||||
canStartJob: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
break
|
||||
|
@ -1013,8 +1021,8 @@ extension ConversationVC:
|
|||
self.present(actionSheet, animated: true)
|
||||
}
|
||||
|
||||
func handleReplyButtonTapped(for cellViewModel: MessageViewModel) {
|
||||
reply(cellViewModel)
|
||||
func handleReplyButtonTapped(for cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
reply(cellViewModel, using: dependencies)
|
||||
}
|
||||
|
||||
func startThread(with sessionId: String, openGroupServer: String?, openGroupPublicKey: String?) {
|
||||
|
@ -1123,15 +1131,15 @@ extension ConversationVC:
|
|||
UIView.setAnimationsEnabled(true)
|
||||
}
|
||||
|
||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones) {
|
||||
react(cellViewModel, with: emoji.rawValue, remove: false)
|
||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones, using dependencies: Dependencies) {
|
||||
react(cellViewModel, with: emoji.rawValue, remove: false, using: dependencies)
|
||||
}
|
||||
|
||||
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones) {
|
||||
react(cellViewModel, with: emoji.rawValue, remove: true)
|
||||
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones, using dependencies: Dependencies) {
|
||||
react(cellViewModel, with: emoji.rawValue, remove: true, using: dependencies)
|
||||
}
|
||||
|
||||
func removeAllReactions(_ cellViewModel: MessageViewModel, for emoji: String) {
|
||||
func removeAllReactions(_ cellViewModel: MessageViewModel, for emoji: String, using dependencies: Dependencies) {
|
||||
guard cellViewModel.threadVariant == .community else { return }
|
||||
|
||||
Storage.shared
|
||||
|
@ -1208,7 +1216,7 @@ extension ConversationVC:
|
|||
let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant
|
||||
let openGroupRoom: String? = self.viewModel.threadData.openGroupRoomToken
|
||||
let sentTimestamp: Int64 = SnodeAPI.currentOffsetTimestampMs()
|
||||
let recentReactionTimestamps: [Int64] = dependencies.generalCache.recentReactionTimestamps
|
||||
let recentReactionTimestamps: [Int64] = dependencies.caches[.general].recentReactionTimestamps
|
||||
|
||||
guard
|
||||
recentReactionTimestamps.count < 20 ||
|
||||
|
@ -1226,7 +1234,7 @@ extension ConversationVC:
|
|||
return
|
||||
}
|
||||
|
||||
dependencies.mutableGeneralCache.mutate {
|
||||
dependencies.caches.mutate(cache: .general) {
|
||||
$0.recentReactionTimestamps = Array($0.recentReactionTimestamps
|
||||
.suffix(19))
|
||||
.appending(sentTimestamp)
|
||||
|
@ -1261,9 +1269,9 @@ extension ConversationVC:
|
|||
))
|
||||
}
|
||||
}
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies)
|
||||
.flatMap { pendingChange -> AnyPublisher<(MessageSender.PreparedSendData?, OpenGroupInfo?), Error> in
|
||||
Storage.shared.writePublisher { [weak self] db -> (MessageSender.PreparedSendData?, OpenGroupInfo?) in
|
||||
dependencies.storage.writePublisher { [weak self] db -> (MessageSender.PreparedSendData?, OpenGroupInfo?) in
|
||||
// Update the thread to be visible (if it isn't already)
|
||||
if self?.viewModel.threadData.threadShouldBeVisible == false {
|
||||
_ = try SessionThread
|
||||
|
@ -1372,7 +1380,8 @@ extension ConversationVC:
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: cellViewModel.threadId, threadVariant: cellViewModel.threadVariant)
|
||||
.defaultNamespace,
|
||||
interactionId: cellViewModel.id
|
||||
interactionId: cellViewModel.id,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
return (sendData, nil)
|
||||
|
@ -1382,7 +1391,7 @@ extension ConversationVC:
|
|||
.tryFlatMap { messageSendData, openGroupInfo -> AnyPublisher<Void, Error> in
|
||||
switch (messageSendData, openGroupInfo) {
|
||||
case (.some(let sendData), _):
|
||||
return MessageSender.sendImmediate(preparedSendData: sendData)
|
||||
return MessageSender.sendImmediate(data: sendData, using: dependencies)
|
||||
|
||||
case (_, .some(let info)):
|
||||
return OpenGroupAPI.send(data: info.sendData)
|
||||
|
@ -1433,14 +1442,14 @@ extension ConversationVC:
|
|||
}
|
||||
}
|
||||
|
||||
func showFullEmojiKeyboard(_ cellViewModel: MessageViewModel) {
|
||||
func showFullEmojiKeyboard(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
hideInputAccessoryView()
|
||||
|
||||
let emojiPicker = EmojiPickerSheet(
|
||||
completionHandler: { [weak self] emoji in
|
||||
guard let emoji: EmojiWithSkinTones = emoji else { return }
|
||||
|
||||
self?.react(cellViewModel, with: emoji)
|
||||
self?.react(cellViewModel, with: emoji, using: dependencies)
|
||||
},
|
||||
dismissHandler: { [weak self] in
|
||||
self?.showInputAccessoryView()
|
||||
|
@ -1456,7 +1465,7 @@ extension ConversationVC:
|
|||
|
||||
// MARK: --action handling
|
||||
|
||||
func showFailedMessageSheet(for cellViewModel: MessageViewModel) {
|
||||
private func showFailedMessageSheet(for cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
let sheet = UIAlertController(
|
||||
title: (cellViewModel.state == .failedToSync ?
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE".localized() :
|
||||
|
@ -1483,7 +1492,7 @@ extension ConversationVC:
|
|||
"context_menu_resend".localized()
|
||||
),
|
||||
style: .default,
|
||||
handler: { [weak self] _ in self?.retry(cellViewModel) }
|
||||
handler: { [weak self] _ in self?.retry(cellViewModel, using: dependencies) }
|
||||
))
|
||||
|
||||
// HACK: Extracting this info from the error string is pretty dodgy
|
||||
|
@ -1596,7 +1605,7 @@ extension ConversationVC:
|
|||
|
||||
// MARK: - ContextMenuActionDelegate
|
||||
|
||||
func info(_ cellViewModel: MessageViewModel) {
|
||||
func info(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
let mediaInfoVC = MediaInfoVC(
|
||||
attachments: (cellViewModel.attachments ?? []),
|
||||
isOutgoing: (cellViewModel.variant == .standardOutgoing),
|
||||
|
@ -1607,8 +1616,8 @@ extension ConversationVC:
|
|||
navigationController?.pushViewController(mediaInfoVC, animated: true)
|
||||
}
|
||||
|
||||
func retry(_ cellViewModel: MessageViewModel) {
|
||||
Storage.shared.writeAsync { [weak self] db in
|
||||
func retry(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
dependencies.storage.writeAsync { [weak self] db in
|
||||
guard
|
||||
let threadId: String = self?.viewModel.threadData.threadId,
|
||||
let threadVariant: SessionThread.Variant = self?.viewModel.threadData.threadVariant,
|
||||
|
@ -1649,12 +1658,13 @@ extension ConversationVC:
|
|||
interaction: interaction,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant,
|
||||
isSyncMessage: (cellViewModel.state == .failedToSync)
|
||||
isSyncMessage: (cellViewModel.state == .failedToSync),
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func reply(_ cellViewModel: MessageViewModel) {
|
||||
func reply(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
let maybeQuoteDraft: QuotedReplyModel? = QuotedReplyModel.quotedReplyForSending(
|
||||
threadId: self.viewModel.threadData.threadId,
|
||||
authorId: cellViewModel.authorId,
|
||||
|
@ -1677,7 +1687,7 @@ extension ConversationVC:
|
|||
snInputView.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func copy(_ cellViewModel: MessageViewModel) {
|
||||
func copy(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
switch cellViewModel.cellType {
|
||||
case .typingIndicator, .dateHeader, .unreadMarker: break
|
||||
|
||||
|
@ -1715,7 +1725,7 @@ extension ConversationVC:
|
|||
UIPasteboard.general.string = cellViewModel.authorId
|
||||
}
|
||||
|
||||
func delete(_ cellViewModel: MessageViewModel) {
|
||||
func delete(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
switch cellViewModel.variant {
|
||||
case .standardIncomingDeleted, .infoCall,
|
||||
.infoScreenshotNotification, .infoMediaSavedNotification,
|
||||
|
@ -1911,7 +1921,8 @@ extension ConversationVC:
|
|||
message: unsendRequest,
|
||||
threadId: cellViewModel.threadId,
|
||||
interactionId: nil,
|
||||
to: .contact(publicKey: userPublicKey)
|
||||
to: .contact(publicKey: userPublicKey),
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
return
|
||||
|
@ -1934,7 +1945,8 @@ extension ConversationVC:
|
|||
message: unsendRequest,
|
||||
threadId: cellViewModel.threadId,
|
||||
interactionId: nil,
|
||||
to: .contact(publicKey: userPublicKey)
|
||||
to: .contact(publicKey: userPublicKey),
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
self?.showInputAccessoryView()
|
||||
|
@ -1962,7 +1974,8 @@ extension ConversationVC:
|
|||
message: unsendRequest,
|
||||
interactionId: nil,
|
||||
threadId: cellViewModel.threadId,
|
||||
threadVariant: cellViewModel.threadVariant
|
||||
threadVariant: cellViewModel.threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1996,7 +2009,7 @@ extension ConversationVC:
|
|||
}
|
||||
}
|
||||
|
||||
func save(_ cellViewModel: MessageViewModel) {
|
||||
func save(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
guard cellViewModel.cellType == .mediaMessage else { return }
|
||||
|
||||
let mediaAttachments: [(Attachment, String)] = (cellViewModel.attachments ?? [])
|
||||
|
@ -2038,24 +2051,10 @@ extension ConversationVC:
|
|||
return
|
||||
}
|
||||
|
||||
let threadId: String = self.viewModel.threadData.threadId
|
||||
let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant
|
||||
|
||||
Storage.shared.writeAsync { db in
|
||||
try MessageSender.send(
|
||||
db,
|
||||
message: DataExtractionNotification(
|
||||
kind: .mediaSaved(timestamp: UInt64(cellViewModel.timestampMs)),
|
||||
sentTimestamp: UInt64(SnodeAPI.currentOffsetTimestampMs())
|
||||
),
|
||||
interactionId: nil,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
)
|
||||
}
|
||||
sendDataExtraction(kind: .mediaSaved(timestamp: UInt64(cellViewModel.timestampMs)))
|
||||
}
|
||||
|
||||
func ban(_ cellViewModel: MessageViewModel) {
|
||||
func ban(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
guard cellViewModel.threadVariant == .community else { return }
|
||||
|
||||
let threadId: String = self.viewModel.threadData.threadId
|
||||
|
@ -2111,7 +2110,7 @@ extension ConversationVC:
|
|||
self.present(modal, animated: true)
|
||||
}
|
||||
|
||||
func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel) {
|
||||
func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
guard cellViewModel.threadVariant == .community else { return }
|
||||
|
||||
let threadId: String = self.viewModel.threadData.threadId
|
||||
|
@ -2303,23 +2302,29 @@ extension ConversationVC:
|
|||
|
||||
// MARK: - Data Extraction Notifications
|
||||
|
||||
@objc func sendScreenshotNotification() {
|
||||
@objc func sendScreenshotNotification() { sendDataExtraction(kind: .screenshot) }
|
||||
|
||||
func sendDataExtraction(
|
||||
kind: DataExtractionNotification.Kind,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
// Only send screenshot notifications to one-to-one conversations
|
||||
guard self.viewModel.threadData.threadVariant == .contact else { return }
|
||||
|
||||
let threadId: String = self.viewModel.threadData.threadId
|
||||
let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant
|
||||
|
||||
Storage.shared.writeAsync { db in
|
||||
dependencies.storage.writeAsync { db in
|
||||
try MessageSender.send(
|
||||
db,
|
||||
message: DataExtractionNotification(
|
||||
kind: .screenshot,
|
||||
kind: kind,
|
||||
sentTimestamp: UInt64(SnodeAPI.currentOffsetTimestampMs())
|
||||
),
|
||||
interactionId: nil,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2355,7 +2360,8 @@ extension ConversationVC {
|
|||
for threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
isNewThread: Bool,
|
||||
timestampMs: Int64
|
||||
timestampMs: Int64,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
guard threadVariant == .contact else { return }
|
||||
|
||||
|
@ -2396,7 +2402,8 @@ extension ConversationVC {
|
|||
),
|
||||
interactionId: nil,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -87,12 +87,18 @@ public class MessageCell: UITableViewCell {
|
|||
|
||||
protocol MessageCellDelegate: ReactionDelegate {
|
||||
func handleItemLongPressed(_ cellViewModel: MessageViewModel)
|
||||
func handleItemTapped(_ cellViewModel: MessageViewModel, gestureRecognizer: UITapGestureRecognizer)
|
||||
func handleItemTapped(_ cellViewModel: MessageViewModel, gestureRecognizer: UITapGestureRecognizer, using dependencies: Dependencies)
|
||||
func handleItemDoubleTapped(_ cellViewModel: MessageViewModel)
|
||||
func handleItemSwiped(_ cellViewModel: MessageViewModel, state: SwipeState)
|
||||
func openUrl(_ urlString: String)
|
||||
func handleReplyButtonTapped(for cellViewModel: MessageViewModel)
|
||||
func handleReplyButtonTapped(for cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func startThread(with sessionId: String, openGroupServer: String?, openGroupPublicKey: String?)
|
||||
func showReactionList(_ cellViewModel: MessageViewModel, selectedReaction: EmojiWithSkinTones?)
|
||||
func needsLayout(for cellViewModel: MessageViewModel, expandingReactions: Bool)
|
||||
}
|
||||
|
||||
extension MessageCellDelegate {
|
||||
func handleItemTapped(_ cellViewModel: MessageViewModel, gestureRecognizer: UITapGestureRecognizer) {
|
||||
handleItemTapped(cellViewModel, gestureRecognizer: gestureRecognizer, using: Dependencies())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -861,7 +861,9 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
|||
isHandlingLongPress = true
|
||||
}
|
||||
|
||||
@objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||
@objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { onTap(gestureRecognizer) }
|
||||
|
||||
private func onTap(_ gestureRecognizer: UITapGestureRecognizer, using dependencies: Dependencies = Dependencies()) {
|
||||
guard let cellViewModel: MessageViewModel = self.viewModel else { return }
|
||||
|
||||
let location = gestureRecognizer.location(in: self)
|
||||
|
@ -897,10 +899,10 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
|||
if reactionContainerView.convert(reactionView.frame, from: reactionView.superview).contains(convertedLocation) {
|
||||
|
||||
if reactionView.viewModel.showBorder {
|
||||
delegate?.removeReact(cellViewModel, for: reactionView.viewModel.emoji)
|
||||
delegate?.removeReact(cellViewModel, for: reactionView.viewModel.emoji, using: dependencies)
|
||||
}
|
||||
else {
|
||||
delegate?.react(cellViewModel, with: reactionView.viewModel.emoji)
|
||||
delegate?.react(cellViewModel, with: reactionView.viewModel.emoji, using: dependencies)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -917,7 +919,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
|||
}
|
||||
}
|
||||
else if snContentView.bounds.contains(snContentView.convert(location, from: self)) {
|
||||
delegate?.handleItemTapped(cellViewModel, gestureRecognizer: gestureRecognizer)
|
||||
delegate?.handleItemTapped(cellViewModel, gestureRecognizer: gestureRecognizer, using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -985,11 +987,11 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
private func reply() {
|
||||
private func reply(using dependencies: Dependencies = Dependencies()) {
|
||||
guard let cellViewModel: MessageViewModel = self.viewModel else { return }
|
||||
|
||||
resetReply()
|
||||
delegate?.handleReplyButtonTapped(for: cellViewModel)
|
||||
delegate?.handleReplyButtonTapped(for: cellViewModel, using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
|
|
@ -39,10 +39,10 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
|||
// MARK: - Initialization
|
||||
|
||||
init(
|
||||
dependencies: Dependencies = Dependencies(),
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
config: DisappearingMessagesConfiguration
|
||||
config: DisappearingMessagesConfiguration,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
self.dependencies = dependencies
|
||||
self.threadId = threadId
|
||||
|
@ -68,7 +68,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
|||
currentSelection
|
||||
.removeDuplicates()
|
||||
.map { [weak self] currentSelection in (self?.storedSelection != currentSelection) }
|
||||
.map { isChanged in
|
||||
.map { [weak self, dependencies] isChanged in
|
||||
guard isChanged else { return [] }
|
||||
|
||||
return [
|
||||
|
@ -76,8 +76,8 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
|||
id: .save,
|
||||
systemItem: .save,
|
||||
accessibilityIdentifier: "Save button"
|
||||
) { [weak self] in
|
||||
self?.saveChanges()
|
||||
) {
|
||||
self?.saveChanges(using: dependencies)
|
||||
self?.dismissScreen()
|
||||
}
|
||||
]
|
||||
|
@ -100,7 +100,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
|||
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
|
||||
private lazy var _observableTableData: ObservableData = ValueObservation
|
||||
.trackingConstantRegion { [weak self, config, dependencies, threadId = self.threadId] db -> [SectionModel] in
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let maybeThreadViewModel: SessionThreadViewModel? = try SessionThreadViewModel
|
||||
.conversationSettingsQuery(threadId: threadId, userPublicKey: userPublicKey)
|
||||
.fetchOne(db)
|
||||
|
@ -156,7 +156,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
|||
|
||||
// MARK: - Functions
|
||||
|
||||
private func saveChanges() {
|
||||
private func saveChanges(using dependencies: Dependencies = Dependencies()) {
|
||||
let threadId: String = self.threadId
|
||||
let threadVariant: SessionThread.Variant = self.threadVariant
|
||||
let currentSelection: TimeInterval = self.currentSelection.value
|
||||
|
@ -195,7 +195,8 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
|||
),
|
||||
interactionId: interaction.id,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Legacy closed groups
|
||||
|
|
|
@ -60,10 +60,10 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
// MARK: - Initialization
|
||||
|
||||
init(
|
||||
dependencies: Dependencies = Dependencies(),
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
didTriggerSearch: @escaping () -> ()
|
||||
didTriggerSearch: @escaping () -> (),
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
self.dependencies = dependencies
|
||||
self.threadId = threadId
|
||||
|
@ -196,7 +196,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
|
||||
private lazy var _observableTableData: ObservableData = ValueObservation
|
||||
.trackingConstantRegion { [weak self, dependencies, threadId = self.threadId, threadVariant = self.threadVariant] db -> [SectionModel] in
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let maybeThreadViewModel: SessionThreadViewModel? = try SessionThreadViewModel
|
||||
.conversationSettingsQuery(threadId: threadId, userPublicKey: userPublicKey)
|
||||
.fetchOne(db)
|
||||
|
@ -755,7 +755,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
publicKey: publicKey
|
||||
)
|
||||
|
||||
dependencies.storage.writeAsync { db in
|
||||
dependencies.storage.writeAsync { [dependencies] db in
|
||||
try selectedUsers.forEach { userId in
|
||||
let thread: SessionThread = try SessionThread
|
||||
.fetchOrCreate(db, id: userId, variant: .contact, shouldBeVisible: nil)
|
||||
|
@ -786,7 +786,8 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
db,
|
||||
interaction: interaction,
|
||||
threadId: thread.id,
|
||||
threadVariant: thread.variant
|
||||
threadVariant: thread.variant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -368,10 +368,12 @@ final class ReactionListSheet: BaseVC {
|
|||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc private func clearAllTapped() {
|
||||
@objc private func clearAllTapped() { clearAll() }
|
||||
|
||||
private func clearAll(using dependencies: Dependencies = Dependencies()) {
|
||||
guard let selectedReaction: EmojiWithSkinTones = self.reactionSummaries.first(where: { $0.isSelected })?.emoji else { return }
|
||||
|
||||
delegate?.removeAllReactions(messageViewModel, for: selectedReaction.rawValue)
|
||||
delegate?.removeAllReactions(messageViewModel, for: selectedReaction.rawValue, using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -599,7 +601,13 @@ extension ReactionListSheet {
|
|||
// MARK: - Delegate
|
||||
|
||||
protocol ReactionDelegate: AnyObject {
|
||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones)
|
||||
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones)
|
||||
func removeAllReactions(_ cellViewModel: MessageViewModel, for emoji: String)
|
||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones, using dependencies: Dependencies)
|
||||
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones, using dependencies: Dependencies)
|
||||
func removeAllReactions(_ cellViewModel: MessageViewModel, for emoji: String, using dependencies: Dependencies)
|
||||
}
|
||||
|
||||
extension ReactionDelegate {
|
||||
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones) {
|
||||
removeReact(cellViewModel, for: emoji, using: Dependencies())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -505,8 +505,9 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
dismissSelf(animated: true)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func didPressShare(_ sender: Any) {
|
||||
@objc public func didPressShare(_ sender: Any) { share() }
|
||||
|
||||
public func share(using dependencies: Dependencies = Dependencies()) {
|
||||
guard let currentViewController = self.viewControllers?[0] as? MediaDetailViewController else {
|
||||
owsFailDebug("currentViewController was unexpectedly nil")
|
||||
return
|
||||
|
@ -553,7 +554,8 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
),
|
||||
interactionId: nil, // Show no interaction for the current user
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -418,7 +418,7 @@ extension SendMediaNavigationController: AttachmentApprovalViewControllerDelegat
|
|||
attachmentDraftCollection.remove(attachment: attachment)
|
||||
}
|
||||
|
||||
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?) {
|
||||
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?, using dependencies: Dependencies) {
|
||||
sendMediaNavDelegate?.sendMediaNav(self, didApproveAttachments: attachments, forThreadId: threadId, messageText: messageText)
|
||||
}
|
||||
|
||||
|
|
|
@ -553,7 +553,8 @@ class NotificationActionHandler {
|
|||
func reply(
|
||||
userInfo: [AnyHashable: Any],
|
||||
replyText: String,
|
||||
applicationState: UIApplication.State
|
||||
applicationState: UIApplication.State,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard let threadId = userInfo[AppNotificationUserInfoKey.threadId] as? String else {
|
||||
return Fail<Void, Error>(error: NotificationError.failDebug("threadId was unexpectedly nil"))
|
||||
|
@ -599,10 +600,11 @@ class NotificationActionHandler {
|
|||
db,
|
||||
interaction: interaction,
|
||||
threadId: threadId,
|
||||
threadVariant: thread.variant
|
||||
threadVariant: thread.variant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.handleEvents(
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
|
|
|
@ -20,7 +20,7 @@ public enum SyncPushTokensJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
// Don't run when inactive or not in main app or if the user doesn't exist yet
|
||||
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
||||
|
|
|
@ -26,7 +26,10 @@ enum Onboarding {
|
|||
return existingPublisher
|
||||
}
|
||||
|
||||
private static func createProfileNameRetrievalPublisher(_ requestId: UUID) -> AnyPublisher<String?, Error> {
|
||||
private static func createProfileNameRetrievalPublisher(
|
||||
_ requestId: UUID,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<String?, Error> {
|
||||
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
|
||||
guard SessionUtil.userConfigsEnabled else {
|
||||
return Just(nil)
|
||||
|
@ -99,7 +102,8 @@ enum Onboarding {
|
|||
)
|
||||
}(),
|
||||
sentTimestamp: TimeInterval((message.sentTimestamp ?? 0) / 1000),
|
||||
calledFromConfigHandling: false
|
||||
calledFromConfigHandling: false,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
return ()
|
||||
|
|
|
@ -244,7 +244,7 @@ final class NukeDataModal: Modal {
|
|||
UserDefaults.removeAll()
|
||||
|
||||
// Remove the cached key so it gets re-cached on next access
|
||||
dependencies.mutableGeneralCache.mutate {
|
||||
dependencies.caches.mutate(cache: .general) {
|
||||
$0.encodedPublicKey = nil
|
||||
$0.recentReactionTimestamps = []
|
||||
}
|
||||
|
|
|
@ -13,10 +13,7 @@ public final class BackgroundPoller {
|
|||
|
||||
public static func poll(
|
||||
completionHandler: @escaping (UIBackgroundFetchResult) -> Void,
|
||||
dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies(
|
||||
subscribeQueue: .global(qos: .background),
|
||||
receiveQueue: .main
|
||||
)
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
Publishers
|
||||
.MergeMany(
|
||||
|
@ -55,8 +52,8 @@ public final class BackgroundPoller {
|
|||
}
|
||||
)
|
||||
)
|
||||
.subscribe(on: dependencies.subscribeQueue)
|
||||
.receive(on: dependencies.receiveQueue)
|
||||
.subscribe(on: DispatchQueue.global(qos: .background), using: dependencies)
|
||||
.receive(on: DispatchQueue.main, using: dependencies)
|
||||
.collect()
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
|
@ -74,7 +71,7 @@ public final class BackgroundPoller {
|
|||
}
|
||||
|
||||
private static func pollForMessages(
|
||||
using dependencies: OpenGroupManager.OGMDependencies
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey()
|
||||
|
||||
|
@ -94,7 +91,7 @@ public final class BackgroundPoller {
|
|||
}
|
||||
|
||||
private static func pollForClosedGroupMessages(
|
||||
using dependencies: OpenGroupManager.OGMDependencies
|
||||
using dependencies: Dependencies
|
||||
) -> [AnyPublisher<Void, Error>] {
|
||||
// Fetch all closed groups (excluding any don't contain the current user as a
|
||||
// GroupMemeber as the user is no longer a member of those)
|
||||
|
|
|
@ -124,13 +124,14 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
_ db: Database,
|
||||
message: CallMessage,
|
||||
interactionId: Int64?,
|
||||
in thread: SessionThread
|
||||
in thread: SessionThread,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> AnyPublisher<Void, Error> {
|
||||
SNLog("[Calls] Sending pre-offer message.")
|
||||
|
||||
return MessageSender
|
||||
.sendImmediate(
|
||||
preparedSendData: try MessageSender
|
||||
data: try MessageSender
|
||||
.preparedSendData(
|
||||
db,
|
||||
message: message,
|
||||
|
@ -138,8 +139,10 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||
.defaultNamespace,
|
||||
interactionId: interactionId
|
||||
)
|
||||
interactionId: interactionId,
|
||||
using: dependencies
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
.handleEvents(receiveOutput: { _ in SNLog("[Calls] Pre-offer message has been sent.") })
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -147,7 +150,8 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
|
||||
public func sendOffer(
|
||||
to thread: SessionThread,
|
||||
isRestartingICEConnection: Bool = false
|
||||
isRestartingICEConnection: Bool = false,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
SNLog("[Calls] Sending offer message.")
|
||||
let uuid: String = self.uuid
|
||||
|
@ -172,7 +176,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { db in
|
||||
try MessageSender
|
||||
.preparedSendData(
|
||||
|
@ -188,10 +192,11 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||
.defaultNamespace,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
|
@ -207,12 +212,15 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public func sendAnswer(to sessionId: String) -> AnyPublisher<Void, Error> {
|
||||
public func sendAnswer(
|
||||
to sessionId: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
SNLog("[Calls] Sending answer message.")
|
||||
let uuid: String = self.uuid
|
||||
let mediaConstraints: RTCMediaConstraints = mediaConstraints(false)
|
||||
|
||||
return Storage.shared
|
||||
return dependencies.storage
|
||||
.readPublisher { db -> SessionThread in
|
||||
guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId) else {
|
||||
throw WebRTCSessionError.noThread
|
||||
|
@ -239,7 +247,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { db in
|
||||
try MessageSender
|
||||
.preparedSendData(
|
||||
|
@ -254,10 +262,11 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||
.defaultNamespace,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
|
@ -283,7 +292,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
private func sendICECandidates() {
|
||||
private func sendICECandidates(using dependencies: Dependencies = Dependencies()) {
|
||||
let candidates: [RTCIceCandidate] = self.queuedICECandidates
|
||||
let uuid: String = self.uuid
|
||||
let contactSessionId: String = self.contactSessionId
|
||||
|
@ -291,7 +300,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
// Empty the queue
|
||||
self.queuedICECandidates.removeAll()
|
||||
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { db in
|
||||
guard let thread: SessionThread = try SessionThread.fetchOne(db, id: contactSessionId) else {
|
||||
throw WebRTCSessionError.noThread
|
||||
|
@ -315,15 +324,20 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||
.defaultNamespace,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.sinkUntilComplete()
|
||||
}
|
||||
|
||||
public func endCall(_ db: Database, with sessionId: String) throws {
|
||||
public func endCall(
|
||||
_ db: Database,
|
||||
with sessionId: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
guard let thread: SessionThread = try SessionThread.fetchOne(db, id: sessionId) else { return }
|
||||
|
||||
SNLog("[Calls] Sending end call message.")
|
||||
|
@ -340,11 +354,12 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||
.defaultNamespace,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
MessageSender
|
||||
.sendImmediate(preparedSendData: preparedSendData)
|
||||
.sendImmediate(data: preparedSendData, using: dependencies)
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.sinkUntilComplete()
|
||||
}
|
||||
|
|
|
@ -1039,7 +1039,10 @@ extension Attachment {
|
|||
}
|
||||
}
|
||||
|
||||
internal func upload(to destination: Attachment.Destination) -> AnyPublisher<String?, Error> {
|
||||
internal func upload(
|
||||
to destination: Attachment.Destination,
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<String?, Error> {
|
||||
// This can occur if an AttachmnetUploadJob was explicitly created for a message
|
||||
// dependant on the attachment being uploaded (in this case the attachment has
|
||||
// already been uploaded so just succeed)
|
||||
|
|
|
@ -76,7 +76,7 @@ public extension BlindedIdLookup {
|
|||
openGroupServer: String,
|
||||
openGroupPublicKey: String,
|
||||
isCheckingForOutbox: Bool,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> BlindedIdLookup {
|
||||
var lookup: BlindedIdLookup = (try? BlindedIdLookup
|
||||
.fetchOne(db, id: blindedId))
|
||||
|
@ -94,11 +94,13 @@ public extension BlindedIdLookup {
|
|||
// If we we given a sessionId then validate it is correct and if so save it
|
||||
if
|
||||
let sessionId: String = sessionId,
|
||||
dependencies.sodium.sessionId(
|
||||
sessionId,
|
||||
matchesBlindedId: blindedId,
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
genericHash: dependencies.genericHash
|
||||
dependencies.crypto.verify(
|
||||
.sessionId(
|
||||
sessionId,
|
||||
matchesBlindedId: blindedId,
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
{
|
||||
lookup = try lookup
|
||||
|
@ -115,9 +117,16 @@ public extension BlindedIdLookup {
|
|||
.fetchCursor(db)
|
||||
|
||||
while let contact: Contact = try contactsThatApprovedMeCursor.next() {
|
||||
guard dependencies.sodium.sessionId(contact.id, matchesBlindedId: blindedId, serverPublicKey: openGroupPublicKey, genericHash: dependencies.genericHash) else {
|
||||
continue
|
||||
}
|
||||
guard
|
||||
dependencies.crypto.verify(
|
||||
.sessionId(
|
||||
contact.id,
|
||||
matchesBlindedId: blindedId,
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
else { continue }
|
||||
|
||||
// We found a match so update the lookup and leave the loop
|
||||
lookup = try lookup
|
||||
|
@ -151,11 +160,13 @@ public extension BlindedIdLookup {
|
|||
while let otherLookup: BlindedIdLookup = try blindedIdLookupCursor.next() {
|
||||
guard
|
||||
let sessionId: String = otherLookup.sessionId,
|
||||
dependencies.sodium.sessionId(
|
||||
sessionId,
|
||||
matchesBlindedId: blindedId,
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
genericHash: dependencies.genericHash
|
||||
dependencies.crypto.verify(
|
||||
.sessionId(
|
||||
sessionId,
|
||||
matchesBlindedId: blindedId,
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
else { continue }
|
||||
|
||||
|
|
|
@ -55,12 +55,12 @@ public struct Contact: Codable, Identifiable, Equatable, FetchableRecord, Persis
|
|||
isBlocked: Bool = false,
|
||||
didApproveMe: Bool = false,
|
||||
hasBeenBlocked: Bool = false,
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
self.id = id
|
||||
self.isTrusted = (
|
||||
isTrusted ||
|
||||
id == getUserHexEncodedPublicKey(dependencies: dependencies) // Always trust ourselves
|
||||
id == getUserHexEncodedPublicKey(using: dependencies) // Always trust ourselves
|
||||
)
|
||||
self.isApproved = isApproved
|
||||
self.isBlocked = isBlocked
|
||||
|
|
|
@ -797,22 +797,19 @@ public extension Interaction {
|
|||
_ db: Database,
|
||||
threadId: String,
|
||||
body: String?,
|
||||
quoteAuthorId: String? = nil
|
||||
quoteAuthorId: String? = nil,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Bool {
|
||||
var publicKeysToCheck: [String] = [
|
||||
getUserHexEncodedPublicKey(db)
|
||||
getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
]
|
||||
|
||||
// If the thread is an open group then add the blinded id as a key to check
|
||||
if let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: threadId) {
|
||||
let sodium: Sodium = Sodium()
|
||||
|
||||
if
|
||||
let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
||||
let blindedKeyPair: KeyPair = sodium.blindedKeyPair(
|
||||
serverPublicKey: openGroup.publicKey,
|
||||
edKeyPair: userEd25519KeyPair,
|
||||
genericHash: sodium.genericHash
|
||||
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
|
||||
.blindedKeyPair(serverPublicKey: openGroup.publicKey, edKeyPair: userEd25519KeyPair, using: dependencies)
|
||||
)
|
||||
{
|
||||
publicKeysToCheck.append(SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString)
|
||||
|
|
|
@ -250,7 +250,7 @@ public extension Profile {
|
|||
///
|
||||
/// **Note:** This method intentionally does **not** save the newly created Profile,
|
||||
/// it will need to be explicitly saved after calling
|
||||
static func fetchOrCreateCurrentUser(_ db: Database? = nil, dependencies: Dependencies = Dependencies()) -> Profile {
|
||||
static func fetchOrCreateCurrentUser(_ db: Database? = nil, using dependencies: Dependencies = Dependencies()) -> Profile {
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
|
||||
guard let db: Database = db else {
|
||||
|
|
|
@ -525,12 +525,19 @@ public extension SessionThread {
|
|||
_ db: Database? = nil,
|
||||
threadId: String,
|
||||
threadVariant: Variant,
|
||||
blindingPrefix: SessionId.Prefix
|
||||
blindingPrefix: SessionId.Prefix,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> String? {
|
||||
guard threadVariant == .community else { return nil }
|
||||
guard let db: Database = db else {
|
||||
return Storage.shared.read { db in
|
||||
getUserHexEncodedBlindedKey(db, threadId: threadId, threadVariant: threadVariant, blindingPrefix: blindingPrefix)
|
||||
return dependencies.storage.read { db in
|
||||
getUserHexEncodedBlindedKey(
|
||||
db,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant,
|
||||
blindingPrefix: blindingPrefix,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -559,12 +566,8 @@ public extension SessionThread {
|
|||
|
||||
guard capabilities.isEmpty || capabilities.contains(.blind) else { return nil }
|
||||
|
||||
let sodium: Sodium = Sodium()
|
||||
|
||||
let blindedKeyPair: KeyPair? = sodium.blindedKeyPair(
|
||||
serverPublicKey: openGroupInfo.publicKey,
|
||||
edKeyPair: userEdKeyPair,
|
||||
genericHash: sodium.getGenericHash()
|
||||
let blindedKeyPair: KeyPair? = dependencies.crypto.generate(
|
||||
.blindedKeyPair(serverPublicKey: openGroupInfo.publicKey, edKeyPair: userEdKeyPair, using: dependencies)
|
||||
)
|
||||
|
||||
return blindedKeyPair.map { keyPair -> String in
|
||||
|
|
|
@ -17,7 +17,7 @@ public enum AttachmentDownloadJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
let threadId: String = job.threadId,
|
||||
|
|
|
@ -17,7 +17,7 @@ public enum AttachmentUploadJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
let threadId: String = job.threadId,
|
||||
|
@ -72,7 +72,7 @@ public enum AttachmentUploadJob: JobExecutor {
|
|||
// reentrancy issues when the success/failure closures get called before the upload as the JobRunner
|
||||
// will attempt to update the state of the job immediately
|
||||
attachment
|
||||
.upload(to: (openGroup.map { .openGroup($0) } ?? .fileServer))
|
||||
.upload(to: (openGroup.map { .openGroup($0) } ?? .fileServer), using: dependencies)
|
||||
.subscribe(on: queue)
|
||||
.receive(on: queue)
|
||||
.sinkUntilComplete(
|
||||
|
@ -95,7 +95,8 @@ public enum AttachmentUploadJob: JobExecutor {
|
|||
message: details.message,
|
||||
with: .other(error),
|
||||
interactionId: interactionId,
|
||||
isSyncMessage: details.isSyncMessage
|
||||
isSyncMessage: details.isSyncMessage,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ public enum ConfigMessageReceiveJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
/// When the `configMessageReceive` job fails we want to unblock any `messageReceive` jobs it was blocking
|
||||
/// to ensure the user isn't losing any messages - this generally _shouldn't_ happen but if it does then having a temporary
|
||||
|
|
|
@ -18,7 +18,7 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
SessionUtil.userConfigsEnabled,
|
||||
|
@ -43,7 +43,7 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
// it again immediately which is pointless)
|
||||
let updatedJob: Job? = dependencies.storage.write { db in
|
||||
try job
|
||||
.with(nextRunTimestamp: Date().timeIntervalSince1970 + maxRunFrequency)
|
||||
.with(nextRunTimestamp: dependencies.dateNow.timeIntervalSince1970 + maxRunFrequency)
|
||||
.saved(db)
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
.map { $0.obsoleteHashes }
|
||||
.reduce([], +)
|
||||
.asSet()
|
||||
let jobStartTimestamp: TimeInterval = Date().timeIntervalSince1970
|
||||
let jobStartTimestamp: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||
SNLog("[ConfigurationSyncJob] For \(publicKey) started with \(pendingConfigChanges.count) change\(pendingConfigChanges.count == 1 ? "" : "s")")
|
||||
|
||||
dependencies.storage
|
||||
|
@ -105,7 +105,8 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
|
||||
return (snodeMessage, namespace)
|
||||
},
|
||||
allObsoleteHashes: Array(allObsoleteHashes)
|
||||
allObsoleteHashes: Array(allObsoleteHashes),
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.subscribe(on: queue)
|
||||
|
@ -223,7 +224,7 @@ public extension ConfigurationSyncJob {
|
|||
)
|
||||
),
|
||||
canStartJob: true,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
return
|
||||
}
|
||||
|
@ -231,16 +232,16 @@ public extension ConfigurationSyncJob {
|
|||
// Upsert a config sync job if needed
|
||||
dependencies.jobRunner.upsert(
|
||||
db,
|
||||
job: ConfigurationSyncJob.createIfNeeded(db, publicKey: publicKey, dependencies: dependencies),
|
||||
job: ConfigurationSyncJob.createIfNeeded(db, publicKey: publicKey, using: dependencies),
|
||||
canStartJob: true,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
@discardableResult static func createIfNeeded(
|
||||
_ db: Database,
|
||||
publicKey: String,
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Job? {
|
||||
/// The ConfigurationSyncJob will automatically reschedule itself to run again after 3 seconds so if there is an existing
|
||||
/// job then there is no need to create another instance
|
||||
|
@ -266,7 +267,7 @@ public extension ConfigurationSyncJob {
|
|||
)
|
||||
}
|
||||
|
||||
static func run() -> AnyPublisher<Void, Error> {
|
||||
static func run(using dependencies: Dependencies = Dependencies()) -> AnyPublisher<Void, Error> {
|
||||
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
|
||||
guard SessionUtil.userConfigsEnabled else {
|
||||
return Storage.shared
|
||||
|
@ -276,17 +277,18 @@ public extension ConfigurationSyncJob {
|
|||
// fresh install due to the migrations getting run)
|
||||
guard Identity.userCompletedRequiredOnboarding(db) else { throw StorageError.generic }
|
||||
|
||||
let publicKey: String = getUserHexEncodedPublicKey(db)
|
||||
let publicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
|
||||
return try MessageSender.preparedSendData(
|
||||
db,
|
||||
message: try ConfigurationMessage.getCurrent(db),
|
||||
to: Message.Destination.contact(publicKey: publicKey),
|
||||
namespace: .default,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
@ -298,7 +300,8 @@ public extension ConfigurationSyncJob {
|
|||
queue: .global(qos: .userInitiated),
|
||||
success: { _, _, _ in resolver(Result.success(())) },
|
||||
failure: { _, error, _, _ in resolver(Result.failure(error ?? HTTPError.generic)) },
|
||||
deferred: { _, _ in }
|
||||
deferred: { _, _ in },
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ public enum DisappearingMessagesJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// The 'backgroundTask' gets captured and cleared within the 'completion' block
|
||||
let timestampNowMs: TimeInterval = TimeInterval(SnodeAPI.currentOffsetTimestampMs())
|
||||
|
|
|
@ -16,7 +16,7 @@ public enum FailedAttachmentDownloadsJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
var changeCount: Int = -1
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ public enum FailedMessageSendsJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
var changeCount: Int = -1
|
||||
var attachmentChangeCount: Int = -1
|
||||
|
|
|
@ -23,7 +23,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
/// Determine what types of data we want to collect (if we didn't provide any then assume we want to collect everything)
|
||||
///
|
||||
|
@ -33,18 +33,18 @@ public enum GarbageCollectionJob: JobExecutor {
|
|||
.map { try? JSONDecoder().decode(Details.self, from: $0) }?
|
||||
.typesToCollect)
|
||||
.defaulting(to: Types.allCases)
|
||||
let timestampNow: TimeInterval = Date().timeIntervalSince1970
|
||||
let timestampNow: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||
|
||||
/// Only do a full collection if the job isn't the recurring one or it's been 23 hours since it last ran (23 hours so a user who opens the
|
||||
/// app at about the same time every day will trigger the garbage collection) - since this runs when the app becomes active we
|
||||
/// want to prevent it running to frequently (the app becomes active if a system alert, the notification center or the control panel
|
||||
/// are shown)
|
||||
let lastGarbageCollection: Date = UserDefaults.standard[.lastGarbageCollection]
|
||||
let lastGarbageCollection: Date = dependencies.standardUserDefaults[.lastGarbageCollection]
|
||||
.defaulting(to: Date.distantPast)
|
||||
let finalTypesToCollect: Set<Types> = {
|
||||
guard
|
||||
job.behaviour != .recurringOnActive ||
|
||||
Date().timeIntervalSince(lastGarbageCollection) > (23 * 60 * 60)
|
||||
dependencies.dateNow.timeIntervalSince(lastGarbageCollection) > (23 * 60 * 60)
|
||||
else {
|
||||
// Note: This should only contain the `Types` which are unlikely to ever cause
|
||||
// a startup delay (ie. avoid mass deletions and file management)
|
||||
|
@ -450,8 +450,8 @@ public enum GarbageCollectionJob: JobExecutor {
|
|||
|
||||
// If we did a full collection then update the 'lastGarbageCollection' date to
|
||||
// prevent a full collection from running again in the next 23 hours
|
||||
if job.behaviour == .recurringOnActive && Date().timeIntervalSince(lastGarbageCollection) > (23 * 60 * 60) {
|
||||
UserDefaults.standard[.lastGarbageCollection] = Date()
|
||||
if job.behaviour == .recurringOnActive && dependencies.dateNow.timeIntervalSince(lastGarbageCollection) > (23 * 60 * 60) {
|
||||
dependencies.standardUserDefaults[.lastGarbageCollection] = dependencies.dateNow
|
||||
}
|
||||
|
||||
success(job, false, dependencies)
|
||||
|
|
|
@ -18,7 +18,7 @@ public enum GroupLeavingJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
guard
|
||||
let detailsData: Data = job.details,
|
||||
|
@ -51,10 +51,11 @@ public enum GroupLeavingJob: JobExecutor {
|
|||
to: destination,
|
||||
namespace: destination.defaultNamespace,
|
||||
interactionId: job.interactionId,
|
||||
isSyncMessage: false
|
||||
isSyncMessage: false,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.subscribe(on: queue)
|
||||
.receive(on: queue)
|
||||
.sinkUntilComplete(
|
||||
|
|
|
@ -15,7 +15,7 @@ public enum MessageReceiveJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
guard
|
||||
let threadId: String = job.threadId,
|
||||
|
|
|
@ -18,7 +18,7 @@ public enum MessageSendJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
let detailsData: Data = job.details,
|
||||
|
@ -90,6 +90,10 @@ public enum MessageSendJob: JobExecutor {
|
|||
switch attachment.state {
|
||||
case .uploading, .pendingDownload, .downloading, .failedUpload, .downloaded:
|
||||
return true
|
||||
|
||||
// If we've somehow got an attachment that is in an 'uploaded' state but doesn't
|
||||
// have a 'downloadUrl' then it's invalid and needs to be re-uploaded
|
||||
case .uploaded: return (attachment.downloadUrl == nil)
|
||||
|
||||
default: return false
|
||||
}
|
||||
|
@ -122,7 +126,7 @@ public enum MessageSendJob: JobExecutor {
|
|||
)
|
||||
}
|
||||
.compactMap { attachmentId -> (jobId: Int64, job: Job)? in
|
||||
JobRunner
|
||||
dependencies.jobRunner
|
||||
.insert(
|
||||
db,
|
||||
job: Job(
|
||||
|
@ -171,13 +175,14 @@ public enum MessageSendJob: JobExecutor {
|
|||
to: details.destination,
|
||||
namespace: details.destination.defaultNamespace,
|
||||
interactionId: job.interactionId,
|
||||
isSyncMessage: details.isSyncMessage
|
||||
isSyncMessage: details.isSyncMessage,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.map { sendData in sendData.with(fileIds: messageFileIds) }
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.subscribe(on: queue)
|
||||
.receive(on: queue)
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.subscribe(on: queue, using: dependencies)
|
||||
.receive(on: queue, using: dependencies)
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
|
|
|
@ -16,7 +16,7 @@ public enum NotifyPushServerJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
let detailsData: Data = job.details,
|
||||
|
|
|
@ -15,7 +15,7 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// Don't run when inactive or not in main app
|
||||
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
||||
|
|
|
@ -17,7 +17,7 @@ public enum SendReadReceiptsJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
let threadId: String = job.threadId,
|
||||
|
@ -47,7 +47,7 @@ public enum SendReadReceiptsJob: JobExecutor {
|
|||
isSyncMessage: false
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.subscribe(on: queue)
|
||||
.receive(on: queue)
|
||||
.sinkUntilComplete(
|
||||
|
@ -59,7 +59,7 @@ public enum SendReadReceiptsJob: JobExecutor {
|
|||
// another one for the same thread but with a 'nextRunTimestamp' set to the
|
||||
// 'maxRunFrequency' value to throttle the read receipt requests
|
||||
var shouldFinishCurrentJob: Bool = false
|
||||
let nextRunTimestamp: TimeInterval = (Date().timeIntervalSince1970 + maxRunFrequency)
|
||||
let nextRunTimestamp: TimeInterval = (dependencies.dateNow.timeIntervalSince1970 + maxRunFrequency)
|
||||
|
||||
let updatedJob: Job? = Storage.shared.write { db in
|
||||
// If another 'sendReadReceipts' job was scheduled then update that one
|
||||
|
|
|
@ -15,7 +15,7 @@ public enum UpdateProfilePictureJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// Don't run when inactive or not in main app
|
||||
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
||||
|
@ -24,8 +24,8 @@ public enum UpdateProfilePictureJob: JobExecutor {
|
|||
|
||||
// Only re-upload the profile picture if enough time has passed since the last upload
|
||||
guard
|
||||
let lastProfilePictureUpload: Date = UserDefaults.standard[.lastProfilePictureUpload],
|
||||
Date().timeIntervalSince(lastProfilePictureUpload) > (14 * 24 * 60 * 60)
|
||||
let lastProfilePictureUpload: Date = dependencies.standardUserDefaults[.lastProfilePictureUpload],
|
||||
dependencies.dateNow.timeIntervalSince(lastProfilePictureUpload) > (14 * 24 * 60 * 60)
|
||||
else {
|
||||
// Reset the `nextRunTimestamp` value just in case the last run failed so we don't get stuck
|
||||
// in a loop endlessly deferring the job
|
||||
|
@ -42,7 +42,7 @@ public enum UpdateProfilePictureJob: JobExecutor {
|
|||
}
|
||||
|
||||
// Note: The user defaults flag is updated in ProfileManager
|
||||
let profile: Profile = Profile.fetchOrCreateCurrentUser(dependencies: dependencies)
|
||||
let profile: Profile = Profile.fetchOrCreateCurrentUser(using: dependencies)
|
||||
let profilePictureData: Data? = profile.profilePictureFileName
|
||||
.map { ProfileManager.loadProfileData(with: $0) }
|
||||
|
||||
|
|
|
@ -230,7 +230,8 @@ public extension Message {
|
|||
|
||||
static func processRawReceivedMessage(
|
||||
_ db: Database,
|
||||
rawMessage: SnodeReceivedMessage
|
||||
rawMessage: SnodeReceivedMessage,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> ProcessedMessage? {
|
||||
guard let envelope = SNProtoEnvelope.from(rawMessage) else {
|
||||
throw MessageReceiverError.invalidMessage
|
||||
|
@ -242,7 +243,8 @@ public extension Message {
|
|||
envelope: envelope,
|
||||
serverExpirationTimestamp: (TimeInterval(rawMessage.info.expirationDateMs) / 1000),
|
||||
serverHash: rawMessage.info.hash,
|
||||
handleClosedGroupKeyUpdateMessages: true
|
||||
handleClosedGroupKeyUpdateMessages: true,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Ensure we actually want to de-dupe messages for this namespace, otherwise just
|
||||
|
@ -289,7 +291,8 @@ public extension Message {
|
|||
static func processRawReceivedMessage(
|
||||
_ db: Database,
|
||||
serializedData: Data,
|
||||
serverHash: String?
|
||||
serverHash: String?,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> ProcessedMessage? {
|
||||
guard let envelope = try? SNProtoEnvelope.parseData(serializedData) else {
|
||||
throw MessageReceiverError.invalidMessage
|
||||
|
@ -303,7 +306,8 @@ public extension Message {
|
|||
ControlMessageProcessRecord.defaultExpirationSeconds
|
||||
),
|
||||
serverHash: serverHash,
|
||||
handleClosedGroupKeyUpdateMessages: true
|
||||
handleClosedGroupKeyUpdateMessages: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -312,7 +316,8 @@ public extension Message {
|
|||
/// closed group key update messages (the `NotificationServiceExtension` does this itself)
|
||||
static func processRawReceivedMessageAsNotification(
|
||||
_ db: Database,
|
||||
envelope: SNProtoEnvelope
|
||||
envelope: SNProtoEnvelope,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> ProcessedMessage? {
|
||||
let processedMessage: ProcessedMessage? = try processRawReceivedMessage(
|
||||
db,
|
||||
|
@ -322,7 +327,8 @@ public extension Message {
|
|||
ControlMessageProcessRecord.defaultExpirationSeconds
|
||||
),
|
||||
serverHash: nil,
|
||||
handleClosedGroupKeyUpdateMessages: false
|
||||
handleClosedGroupKeyUpdateMessages: false,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
return processedMessage
|
||||
|
@ -334,7 +340,7 @@ public extension Message {
|
|||
openGroupServerPublicKey: String,
|
||||
message: OpenGroupAPI.Message,
|
||||
data: Data,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> ProcessedMessage? {
|
||||
// Need a sender in order to process the message
|
||||
guard let sender: String = message.sender, let timestamp = message.posted else { return nil }
|
||||
|
@ -357,7 +363,7 @@ public extension Message {
|
|||
openGroupMessageServerId: message.id,
|
||||
openGroupServerPublicKey: openGroupServerPublicKey,
|
||||
handleClosedGroupKeyUpdateMessages: false,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -368,7 +374,7 @@ public extension Message {
|
|||
data: Data,
|
||||
isOutgoing: Bool? = nil,
|
||||
otherBlindedPublicKey: String? = nil,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> ProcessedMessage? {
|
||||
// Note: The `posted` value is in seconds but all messages in the database use milliseconds for timestamps
|
||||
let envelopeBuilder = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: UInt64(floor(message.posted * 1000)))
|
||||
|
@ -390,7 +396,7 @@ public extension Message {
|
|||
isOutgoing: isOutgoing,
|
||||
otherBlindedPublicKey: otherBlindedPublicKey,
|
||||
handleClosedGroupKeyUpdateMessages: false,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -399,24 +405,26 @@ public extension Message {
|
|||
openGroupId: String,
|
||||
message: OpenGroupAPI.Message,
|
||||
associatedPendingChanges: [OpenGroupAPI.PendingChange],
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> [Reaction] {
|
||||
var results: [Reaction] = []
|
||||
guard let reactions = message.reactions else { return results }
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let blinded15UserPublicKey: String? = SessionThread
|
||||
.getUserHexEncodedBlindedKey(
|
||||
db,
|
||||
threadId: openGroupId,
|
||||
threadVariant: .community,
|
||||
blindingPrefix: .blinded15
|
||||
blindingPrefix: .blinded15,
|
||||
using: dependencies
|
||||
)
|
||||
let blinded25UserPublicKey: String? = SessionThread
|
||||
.getUserHexEncodedBlindedKey(
|
||||
db,
|
||||
threadId: openGroupId,
|
||||
threadVariant: .community,
|
||||
blindingPrefix: .blinded25
|
||||
blindingPrefix: .blinded25,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
for (encodedEmoji, rawReaction) in reactions {
|
||||
|
@ -536,7 +544,7 @@ public extension Message {
|
|||
isOutgoing: Bool? = nil,
|
||||
otherBlindedPublicKey: String? = nil,
|
||||
handleClosedGroupKeyUpdateMessages: Bool,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> ProcessedMessage? {
|
||||
let (message, proto, threadId, threadVariant) = try MessageReceiver.parse(
|
||||
db,
|
||||
|
@ -547,7 +555,7 @@ public extension Message {
|
|||
openGroupServerPublicKey: openGroupServerPublicKey,
|
||||
isOutgoing: isOutgoing,
|
||||
otherBlindedPublicKey: otherBlindedPublicKey,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
message.serverHash = serverHash
|
||||
|
||||
|
@ -568,7 +576,8 @@ public extension Message {
|
|||
db,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant,
|
||||
message: closedGroupControlMessage
|
||||
message: closedGroupControlMessage,
|
||||
using: dependencies
|
||||
)
|
||||
return nil
|
||||
|
||||
|
|
|
@ -0,0 +1,403 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import Sodium
|
||||
import Clibsodium
|
||||
import Curve25519Kit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
// MARK: - Nonce
|
||||
|
||||
internal extension OpenGroupAPI {
|
||||
class NonceGenerator16Byte: NonceGenerator {
|
||||
public var NonceBytes: Int { 16 }
|
||||
}
|
||||
|
||||
class NonceGenerator24Byte: NonceGenerator {
|
||||
public var NonceBytes: Int { 24 }
|
||||
}
|
||||
}
|
||||
|
||||
public extension Crypto.Size {
|
||||
static let nonce16: Crypto.Size = Crypto.Size(id: "nonce16") { OpenGroupAPI.NonceGenerator16Byte().NonceBytes }
|
||||
static let nonce24: Crypto.Size = Crypto.Size(id: "nonce24") { OpenGroupAPI.NonceGenerator24Byte().NonceBytes }
|
||||
}
|
||||
|
||||
public extension Crypto.Action {
|
||||
static func generateNonce16() -> Crypto.Action {
|
||||
return Crypto.Action(id: "generateNonce16") { OpenGroupAPI.NonceGenerator16Byte().nonce() }
|
||||
}
|
||||
|
||||
static func generateNonce24() -> Crypto.Action {
|
||||
return Crypto.Action(id: "generateNonce24") { OpenGroupAPI.NonceGenerator24Byte().nonce() }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AeadXChaCha20Poly1305Ietf
|
||||
|
||||
public extension Crypto.Size {
|
||||
static let aeadXChaCha20KeyBytes: Crypto.Size = Crypto.Size(id: "aeadXChaCha20KeyBytes") {
|
||||
Sodium().aead.xchacha20poly1305ietf.KeyBytes
|
||||
}
|
||||
static let aeadXChaCha20ABytes: Crypto.Size = Crypto.Size(id: "aeadXChaCha20ABytes") {
|
||||
Sodium().aead.xchacha20poly1305ietf.ABytes
|
||||
}
|
||||
}
|
||||
|
||||
public extension Crypto.Action {
|
||||
/// This method is the same as the standard AeadXChaCha20Poly1305Ietf `encrypt` method except it allows the
|
||||
/// specification of a nonce which allows for deterministic behaviour with unit testing
|
||||
static func encryptAeadXChaCha20(
|
||||
message: Bytes,
|
||||
secretKey: Bytes,
|
||||
nonce: Bytes,
|
||||
additionalData: Bytes? = nil,
|
||||
using dependencies: Dependencies
|
||||
) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "encryptAeadXChaCha20",
|
||||
args: [message, secretKey, nonce, additionalData]
|
||||
) {
|
||||
guard secretKey.count == dependencies.crypto.size(.aeadXChaCha20KeyBytes) else { return nil }
|
||||
|
||||
var authenticatedCipherText = Bytes(
|
||||
repeating: 0,
|
||||
count: message.count + dependencies.crypto.size(.aeadXChaCha20ABytes)
|
||||
)
|
||||
var authenticatedCipherTextLen: UInt64 = 0
|
||||
|
||||
let result = crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||
&authenticatedCipherText, &authenticatedCipherTextLen,
|
||||
message, UInt64(message.count),
|
||||
additionalData, UInt64(additionalData?.count ?? 0),
|
||||
nil, nonce, secretKey
|
||||
)
|
||||
|
||||
guard result == 0 else { return nil }
|
||||
|
||||
return authenticatedCipherText
|
||||
}
|
||||
}
|
||||
|
||||
static func decryptAeadXChaCha20(
|
||||
authenticatedCipherText: Bytes,
|
||||
secretKey: Bytes,
|
||||
nonce: Bytes,
|
||||
additionalData: Bytes? = nil
|
||||
) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "decryptAeadXChaCha20",
|
||||
args: [authenticatedCipherText, secretKey, nonce, additionalData]
|
||||
) {
|
||||
return Sodium().aead.xchacha20poly1305ietf.decrypt(
|
||||
authenticatedCipherText: authenticatedCipherText,
|
||||
secretKey: secretKey,
|
||||
nonce: nonce,
|
||||
additionalData: additionalData
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Blinding
|
||||
|
||||
/// These extenion methods are used to generate a sign "blinded" messages
|
||||
///
|
||||
/// According to the Swift engineers the only situation when `UnsafeRawBufferPointer.baseAddress` is nil is when it's an
|
||||
/// empty collection; as such our guard cases wihch return `-1` when unwrapping this value should never be hit and we can ignore
|
||||
/// them as possible results.
|
||||
///
|
||||
/// For more information see:
|
||||
/// https://forums.swift.org/t/when-is-unsafemutablebufferpointer-baseaddress-nil/32136/5
|
||||
/// https://github.com/apple/swift-evolution/blob/master/proposals/0055-optional-unsafe-pointers.md#unsafebufferpointer
|
||||
public extension Crypto.Action {
|
||||
private static let scalarLength: Int = Int(crypto_core_ed25519_scalarbytes()) // 32
|
||||
private static let noClampLength: Int = Int(Sodium.lib_crypto_scalarmult_ed25519_bytes()) // 32
|
||||
private static let scalarMultLength: Int = Int(crypto_scalarmult_bytes()) // 32
|
||||
fileprivate static let publicKeyLength: Int = Int(crypto_scalarmult_bytes()) // 32
|
||||
fileprivate static let secretKeyLength: Int = Int(crypto_sign_secretkeybytes()) // 64
|
||||
|
||||
/// 64-byte blake2b hash then reduce to get the blinding factor
|
||||
static func generateBlindingFactor(
|
||||
serverPublicKey: String,
|
||||
using dependencies: Dependencies
|
||||
) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "generateBlindingFactor",
|
||||
args: [serverPublicKey]
|
||||
) {
|
||||
/// k = salt.crypto_core_ed25519_scalar_reduce(blake2b(server_pk, digest_size=64).digest())
|
||||
let serverPubKeyData: Data = Data(hex: serverPublicKey)
|
||||
|
||||
guard
|
||||
!serverPubKeyData.isEmpty,
|
||||
let serverPublicKeyHashBytes: Bytes = try? dependencies.crypto.perform(
|
||||
.hash(message: [UInt8](serverPubKeyData), outputLength: 64)
|
||||
)
|
||||
else { return nil }
|
||||
|
||||
/// Reduce the server public key into an ed25519 scalar (`k`)
|
||||
let kPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.scalarLength)
|
||||
|
||||
_ = serverPublicKeyHashBytes.withUnsafeBytes { (serverPublicKeyHashPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let serverPublicKeyHashBaseAddress: UnsafePointer<UInt8> = serverPublicKeyHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_reduce(kPtr, serverPublicKeyHashBaseAddress)
|
||||
return 0
|
||||
}
|
||||
|
||||
return Data(bytes: kPtr, count: Crypto.Action.scalarLength).bytes
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate k*a. To get 'a' (the Ed25519 private key scalar) we call the sodium function to
|
||||
/// convert to an *x* secret key, which seems wrong--but isn't because converted keys use the
|
||||
/// same secret scalar secret (and so this is just the most convenient way to get 'a' out of
|
||||
/// a sodium Ed25519 secret key)
|
||||
fileprivate static func generatePrivateKeyScalar(secretKey: Bytes) -> Bytes {
|
||||
/// a = s.to_curve25519_private_key().encode()
|
||||
let aPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.scalarMultLength)
|
||||
|
||||
/// Looks like the `crypto_sign_ed25519_sk_to_curve25519` function can't actually fail so no need to verify the result
|
||||
/// See: https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_sign/ed25519/ref10/keypair.c#L70
|
||||
_ = secretKey.withUnsafeBytes { (secretKeyPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let secretKeyBaseAddress: UnsafePointer<UInt8> = secretKeyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
return crypto_sign_ed25519_sk_to_curve25519(aPtr, secretKeyBaseAddress)
|
||||
}
|
||||
|
||||
return Data(bytes: aPtr, count: Crypto.Action.scalarMultLength).bytes
|
||||
}
|
||||
|
||||
/// Constructs an Ed25519 signature from a root Ed25519 key and a blinded scalar/pubkey pair, with one tweak to the
|
||||
/// construction: we add kA into the hashed value that yields r so that we have domain separation for different blinded
|
||||
/// pubkeys (this doesn't affect verification at all)
|
||||
static func sogsSignature(
|
||||
message: Bytes,
|
||||
secretKey: Bytes,
|
||||
blindedSecretKey ka: Bytes,
|
||||
blindedPublicKey kA: Bytes
|
||||
) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "sogsSignature",
|
||||
args: [message, secretKey, ka, kA]
|
||||
) {
|
||||
/// H_rh = sha512(s.encode()).digest()[32:]
|
||||
let H_rh: Bytes = Bytes(SHA512.hash(data: secretKey).suffix(32))
|
||||
|
||||
/// r = salt.crypto_core_ed25519_scalar_reduce(sha512_multipart(H_rh, kA, message_parts))
|
||||
let combinedHashBytes: Bytes = SHA512.hash(data: H_rh + kA + message).bytes
|
||||
let rPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.scalarLength)
|
||||
|
||||
_ = combinedHashBytes.withUnsafeBytes { (combinedHashPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let combinedHashBaseAddress: UnsafePointer<UInt8> = combinedHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_reduce(rPtr, combinedHashBaseAddress)
|
||||
return 0
|
||||
}
|
||||
|
||||
/// sig_R = salt.crypto_scalarmult_ed25519_base_noclamp(r)
|
||||
let sig_RPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.noClampLength)
|
||||
guard crypto_scalarmult_ed25519_base_noclamp(sig_RPtr, rPtr) == 0 else { return nil }
|
||||
|
||||
/// HRAM = salt.crypto_core_ed25519_scalar_reduce(sha512_multipart(sig_R, kA, message_parts))
|
||||
let sig_RBytes: Bytes = Data(bytes: sig_RPtr, count: Crypto.Action.noClampLength).bytes
|
||||
let HRAMHashBytes: Bytes = SHA512.hash(data: sig_RBytes + kA + message).bytes
|
||||
let HRAMPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.scalarLength)
|
||||
|
||||
_ = HRAMHashBytes.withUnsafeBytes { (HRAMHashPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let HRAMHashBaseAddress: UnsafePointer<UInt8> = HRAMHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_reduce(HRAMPtr, HRAMHashBaseAddress)
|
||||
return 0
|
||||
}
|
||||
|
||||
/// sig_s = salt.crypto_core_ed25519_scalar_add(r, salt.crypto_core_ed25519_scalar_mul(HRAM, ka))
|
||||
let sig_sMulPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.scalarLength)
|
||||
let sig_sPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.scalarLength)
|
||||
|
||||
_ = ka.withUnsafeBytes { (kaPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let kaBaseAddress: UnsafePointer<UInt8> = kaPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_mul(sig_sMulPtr, HRAMPtr, kaBaseAddress)
|
||||
Sodium.lib_crypto_core_ed25519_scalar_add(sig_sPtr, rPtr, sig_sMulPtr)
|
||||
return 0
|
||||
}
|
||||
|
||||
/// full_sig = sig_R + sig_s
|
||||
return (Data(bytes: sig_RPtr, count: Crypto.Action.noClampLength).bytes + Data(bytes: sig_sPtr, count: Crypto.Action.scalarLength).bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Combines two keys (`kA`)
|
||||
static func combineKeys(
|
||||
lhsKeyBytes: Bytes,
|
||||
rhsKeyBytes: Bytes
|
||||
) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "combineKeys",
|
||||
args: [lhsKeyBytes, rhsKeyBytes]
|
||||
) {
|
||||
let combinedPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.noClampLength)
|
||||
|
||||
let result = rhsKeyBytes.withUnsafeBytes { (rhsKeyBytesPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
return lhsKeyBytes.withUnsafeBytes { (lhsKeyBytesPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let lhsKeyBytesBaseAddress: UnsafePointer<UInt8> = lhsKeyBytesPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
guard let rhsKeyBytesBaseAddress: UnsafePointer<UInt8> = rhsKeyBytesPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
return Sodium.lib_crypto_scalarmult_ed25519_noclamp(combinedPtr, lhsKeyBytesBaseAddress, rhsKeyBytesBaseAddress)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure the above worked
|
||||
guard result == 0 else { return nil }
|
||||
|
||||
return Data(bytes: combinedPtr, count: Crypto.Action.noClampLength).bytes
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate a shared secret for a message from A to B:
|
||||
///
|
||||
/// BLAKE2b(a kB || kA || kB)
|
||||
///
|
||||
/// The receiver can calulate the same value via:
|
||||
///
|
||||
/// BLAKE2b(b kA || kA || kB)
|
||||
static func sharedBlindedEncryptionKey(
|
||||
secretKey: Bytes,
|
||||
otherBlindedPublicKey: Bytes,
|
||||
fromBlindedPublicKey kA: Bytes,
|
||||
toBlindedPublicKey kB: Bytes,
|
||||
using dependencies: Dependencies
|
||||
) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "sharedBlindedEncryptionKey",
|
||||
args: [secretKey, otherBlindedPublicKey, kA, kB]
|
||||
) {
|
||||
let aBytes: Bytes = generatePrivateKeyScalar(secretKey: secretKey)
|
||||
let combinedKeyBytes: Bytes = try dependencies.crypto.perform(
|
||||
.combineKeys(lhsKeyBytes: aBytes, rhsKeyBytes: otherBlindedPublicKey)
|
||||
)
|
||||
|
||||
return try dependencies.crypto.perform(
|
||||
.hash(message: (combinedKeyBytes + kA + kB), outputLength: 32)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Crypto.KeyPairType {
|
||||
/// Constructs a "blinded" key pair (`ka, kA`) based on an open group server `publicKey` and an ed25519 `keyPair`
|
||||
static func blindedKeyPair(
|
||||
serverPublicKey: String,
|
||||
edKeyPair: KeyPair,
|
||||
using dependencies: Dependencies
|
||||
) -> Crypto.KeyPairType {
|
||||
return Crypto.KeyPairType(
|
||||
id: "blindedKeyPair",
|
||||
args: [serverPublicKey, edKeyPair]
|
||||
) {
|
||||
guard
|
||||
edKeyPair.publicKey.count == Crypto.Action.publicKeyLength,
|
||||
edKeyPair.secretKey.count == Crypto.Action.secretKeyLength,
|
||||
let kBytes: Bytes = try? dependencies.crypto.perform(
|
||||
.generateBlindingFactor(serverPublicKey: serverPublicKey, using: dependencies)
|
||||
)
|
||||
else { return nil }
|
||||
|
||||
let aBytes: Bytes = Crypto.Action.generatePrivateKeyScalar(secretKey: edKeyPair.secretKey)
|
||||
|
||||
/// Generate the blinded key pair `ka`, `kA`
|
||||
let kaPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.secretKeyLength)
|
||||
let kAPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.publicKeyLength)
|
||||
|
||||
_ = aBytes.withUnsafeBytes { (aPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
return kBytes.withUnsafeBytes { (kPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let kBaseAddress: UnsafePointer<UInt8> = kPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
guard let aBaseAddress: UnsafePointer<UInt8> = aPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_mul(kaPtr, kBaseAddress, aBaseAddress)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
guard crypto_scalarmult_ed25519_base_noclamp(kAPtr, kaPtr) == 0 else { return nil }
|
||||
|
||||
return KeyPair(
|
||||
publicKey: Data(bytes: kAPtr, count: Crypto.Action.publicKeyLength).bytes,
|
||||
secretKey: Data(bytes: kaPtr, count: Crypto.Action.secretKeyLength).bytes
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Crypto.Verification {
|
||||
/// This method should be used to check if a users standard sessionId matches a blinded one
|
||||
static func sessionId(
|
||||
_ standardSessionId: String,
|
||||
matchesBlindedId blindedSessionId: String,
|
||||
serverPublicKey: String,
|
||||
using dependencies: Dependencies
|
||||
) -> Crypto.Verification {
|
||||
return Crypto.Verification(
|
||||
id: "sessionId",
|
||||
args: [standardSessionId, blindedSessionId, serverPublicKey]
|
||||
) {
|
||||
// Only support generating blinded keys for standard session ids
|
||||
guard
|
||||
let sessionId: SessionId = SessionId(from: standardSessionId),
|
||||
sessionId.prefix == .standard,
|
||||
let blindedId: SessionId = SessionId(from: blindedSessionId),
|
||||
(
|
||||
blindedId.prefix == .blinded15 ||
|
||||
blindedId.prefix == .blinded25
|
||||
),
|
||||
let kBytes: Bytes = try? dependencies.crypto.perform(
|
||||
.generateBlindingFactor(serverPublicKey: serverPublicKey, using: dependencies)
|
||||
)
|
||||
else { return false }
|
||||
|
||||
/// From the session id (ignoring 05 prefix) we have two possible ed25519 pubkeys; the first is the positive (which is what
|
||||
/// Signal's XEd25519 conversion always uses)
|
||||
///
|
||||
/// Note: The below method is code we have exposed from the `curve25519_verify` method within the Curve25519 library
|
||||
/// rather than custom code we have written
|
||||
guard let xEd25519Key: Data = try? Ed25519.publicKey(from: Data(hex: sessionId.publicKey)) else { return false }
|
||||
|
||||
/// Blind the positive public key
|
||||
guard
|
||||
let pk1: Bytes = try? dependencies.crypto.perform(
|
||||
.combineKeys(lhsKeyBytes: kBytes, rhsKeyBytes: xEd25519Key.bytes)
|
||||
)
|
||||
else { return false }
|
||||
|
||||
/// For the negative, what we're going to get out of the above is simply the negative of pk1, so flip the sign bit to get pk2
|
||||
/// pk2 = pk1[0:31] + bytes([pk1[31] ^ 0b1000_0000])
|
||||
let pk2: Bytes = (pk1[0..<31] + [(pk1[31] ^ 0b1000_0000)])
|
||||
|
||||
return (
|
||||
SessionId(.blinded15, publicKey: pk1).publicKey == blindedId.publicKey ||
|
||||
SessionId(.blinded15, publicKey: pk2).publicKey == blindedId.publicKey
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -69,7 +69,6 @@ public extension OpenGroupAPI {
|
|||
info = HTTP.ResponseInfo(code: 0, headers: [:])
|
||||
data = [:]
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ extension OpenGroupAPI.Message {
|
|||
guard let sender: String = maybeSender, let data = Data(base64Encoded: base64EncodedData), let signature = Data(base64Encoded: base64EncodedSignature) else {
|
||||
throw HTTPError.parsingFailed
|
||||
}
|
||||
guard let dependencies: SMKDependencies = decoder.userInfo[Dependencies.userInfoKey] as? SMKDependencies else {
|
||||
guard let dependencies: Dependencies = decoder.userInfo[Dependencies.userInfoKey] as? Dependencies else {
|
||||
throw HTTPError.parsingFailed
|
||||
}
|
||||
|
||||
|
@ -78,13 +78,21 @@ extension OpenGroupAPI.Message {
|
|||
|
||||
switch SessionId.Prefix(from: sender) {
|
||||
case .blinded15, .blinded25:
|
||||
guard dependencies.sign.verify(message: data.bytes, publicKey: publicKey.bytes, signature: signature.bytes) else {
|
||||
guard
|
||||
dependencies.crypto.verify(
|
||||
.signature(message: data.bytes, publicKey: publicKey.bytes, signature: signature.bytes)
|
||||
)
|
||||
else {
|
||||
SNLog("Ignoring message with invalid signature.")
|
||||
throw HTTPError.parsingFailed
|
||||
}
|
||||
|
||||
case .standard, .unblinded:
|
||||
guard (try? dependencies.ed25519.verifySignature(signature, publicKey: publicKey, data: data)) == true else {
|
||||
guard
|
||||
dependencies.crypto.verify(
|
||||
.signatureEd25519(signature, publicKey: publicKey, data: data)
|
||||
)
|
||||
else {
|
||||
SNLog("Ignoring message with invalid signature.")
|
||||
throw HTTPError.parsingFailed
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ public enum OpenGroupAPI {
|
|||
server: String,
|
||||
hasPerformedInitialPoll: Bool,
|
||||
timeSinceLastPoll: TimeInterval,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<BatchResponse> {
|
||||
let lastInboxMessageId: Int64 = (try? OpenGroup
|
||||
.select(.inboxLatestMessageId)
|
||||
|
@ -143,7 +143,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
server: String,
|
||||
requests: [ErasedPreparedSendData],
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<BatchResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -173,7 +173,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
server: String,
|
||||
requests: [ErasedPreparedSendData],
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<BatchResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -202,7 +202,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
server: String,
|
||||
forceBlinded: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<Capabilities> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -225,7 +225,7 @@ public enum OpenGroupAPI {
|
|||
public static func preparedRooms(
|
||||
_ db: Database,
|
||||
server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[Room]> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -244,7 +244,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<Room> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -267,7 +267,7 @@ public enum OpenGroupAPI {
|
|||
lastUpdated: Int64,
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<RoomPollInfo> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -292,7 +292,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<CapabilitiesAndRoomResponse> {
|
||||
return try OpenGroupAPI
|
||||
.preparedSequence(
|
||||
|
@ -332,13 +332,18 @@ public enum OpenGroupAPI {
|
|||
}
|
||||
}
|
||||
|
||||
public typealias CapabilitiesAndRoomsResponse = (
|
||||
capabilities: (info: ResponseInfoType, data: Capabilities),
|
||||
rooms: (info: ResponseInfoType, data: [Room])
|
||||
)
|
||||
|
||||
/// This is a convenience method which constructs a `/sequence` of the `capabilities` and `rooms` requests, refer to those
|
||||
/// methods for the documented behaviour of each method
|
||||
public static func preparedCapabilitiesAndRooms(
|
||||
_ db: Database,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
) throws -> PreparedSendData<(capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room]))> {
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<CapabilitiesAndRoomsResponse> {
|
||||
return try OpenGroupAPI
|
||||
.preparedSequence(
|
||||
db,
|
||||
|
@ -351,7 +356,7 @@ public enum OpenGroupAPI {
|
|||
],
|
||||
using: dependencies
|
||||
)
|
||||
.map { (info: ResponseInfoType, response: BatchResponse) -> (capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])) in
|
||||
.map { (info: ResponseInfoType, response: BatchResponse) -> CapabilitiesAndRoomsResponse in
|
||||
let maybeCapabilities: HTTP.BatchSubResponse<Capabilities>? = (response[.capabilities] as? HTTP.BatchSubResponse<Capabilities>)
|
||||
let maybeRooms: HTTP.BatchSubResponse<[Room]>? = response.data
|
||||
.first(where: { key, _ in
|
||||
|
@ -387,7 +392,7 @@ public enum OpenGroupAPI {
|
|||
whisperTo: String?,
|
||||
whisperMods: Bool,
|
||||
fileIds: [String]?,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<Message> {
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else {
|
||||
throw OpenGroupAPIError.signingFailed
|
||||
|
@ -419,7 +424,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<Message> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -443,7 +448,7 @@ public enum OpenGroupAPI {
|
|||
fileIds: [Int64]?,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else {
|
||||
throw OpenGroupAPIError.signingFailed
|
||||
|
@ -473,7 +478,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -497,7 +502,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[Failable<Message>]> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -526,7 +531,7 @@ public enum OpenGroupAPI {
|
|||
messageId: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[Failable<Message>]> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -555,7 +560,7 @@ public enum OpenGroupAPI {
|
|||
seqNo: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[Failable<Message>]> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -591,7 +596,7 @@ public enum OpenGroupAPI {
|
|||
sessionId: String,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -615,7 +620,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
/// URL(String:) won't convert raw emojis, so need to do a little encoding here.
|
||||
/// The raw emoji will come back when calling url.path
|
||||
|
@ -646,7 +651,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<ReactionAddResponse> {
|
||||
/// URL(String:) won't convert raw emojis, so need to do a little encoding here.
|
||||
/// The raw emoji will come back when calling url.path
|
||||
|
@ -675,7 +680,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<ReactionRemoveResponse> {
|
||||
/// URL(String:) won't convert raw emojis, so need to do a little encoding here.
|
||||
/// The raw emoji will come back when calling url.path
|
||||
|
@ -705,7 +710,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<ReactionRemoveAllResponse> {
|
||||
/// URL(String:) won't convert raw emojis, so need to do a little encoding here.
|
||||
/// The raw emoji will come back when calling url.path
|
||||
|
@ -743,7 +748,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -766,7 +771,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -788,7 +793,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -817,7 +822,7 @@ public enum OpenGroupAPI {
|
|||
fileName: String? = nil,
|
||||
to roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<FileUploadResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -849,7 +854,7 @@ public enum OpenGroupAPI {
|
|||
fileId: String,
|
||||
from roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<Data> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -872,7 +877,7 @@ public enum OpenGroupAPI {
|
|||
public static func preparedInbox(
|
||||
_ db: Database,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[DirectMessage]?> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -893,7 +898,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
id: Int64,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[DirectMessage]?> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -915,7 +920,7 @@ public enum OpenGroupAPI {
|
|||
ciphertext: Data,
|
||||
toInboxFor blindedSessionId: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> PreparedSendData<SendDirectMessageResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -939,7 +944,7 @@ public enum OpenGroupAPI {
|
|||
public static func preparedOutbox(
|
||||
_ db: Database,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[DirectMessage]?> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -960,7 +965,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
id: Int64,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[DirectMessage]?> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -1013,7 +1018,7 @@ public enum OpenGroupAPI {
|
|||
for timeout: TimeInterval? = nil,
|
||||
from roomTokens: [String]? = nil,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -1062,7 +1067,7 @@ public enum OpenGroupAPI {
|
|||
sessionId: String,
|
||||
from roomTokens: [String]?,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -1140,7 +1145,7 @@ public enum OpenGroupAPI {
|
|||
visible: Bool,
|
||||
for roomTokens: [String]?,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
guard (moderator != nil && admin == nil) || (moderator == nil && admin != nil) else {
|
||||
throw HTTPError.generic
|
||||
|
@ -1173,7 +1178,7 @@ public enum OpenGroupAPI {
|
|||
sessionId: String,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<BatchResponse> {
|
||||
return try OpenGroupAPI
|
||||
.preparedSequence(
|
||||
|
@ -1208,7 +1213,7 @@ public enum OpenGroupAPI {
|
|||
for serverName: String,
|
||||
fallbackSigningType signingType: SessionId.Prefix,
|
||||
forceBlinded: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> (publicKey: String, signature: Bytes)? {
|
||||
guard
|
||||
let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
||||
|
@ -1228,13 +1233,14 @@ public enum OpenGroupAPI {
|
|||
|
||||
// If we have no capabilities or if the server supports blinded keys then sign using the blinded key
|
||||
if forceBlinded || capabilities.isEmpty || capabilities.contains(.blind) {
|
||||
guard let blindedKeyPair: KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let signatureResult: Bytes = dependencies.sodium.sogsSignature(message: messageBytes, secretKey: userEdKeyPair.secretKey, blindedSecretKey: blindedKeyPair.secretKey, blindedPublicKey: blindedKeyPair.publicKey) else {
|
||||
return nil
|
||||
}
|
||||
guard
|
||||
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
|
||||
.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, using: dependencies)
|
||||
),
|
||||
let signatureResult: Bytes = try? dependencies.crypto.perform(
|
||||
.sogsSignature(message: messageBytes, secretKey: userEdKeyPair.secretKey, blindedSecretKey: blindedKeyPair.secretKey, blindedPublicKey: blindedKeyPair.publicKey)
|
||||
)
|
||||
else { return nil }
|
||||
|
||||
return (
|
||||
publicKey: SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString,
|
||||
|
@ -1245,9 +1251,11 @@ public enum OpenGroupAPI {
|
|||
// Otherwise sign using the fallback type
|
||||
switch signingType {
|
||||
case .unblinded:
|
||||
guard let signatureResult: Bytes = dependencies.sign.signature(message: messageBytes, secretKey: userEdKeyPair.secretKey) else {
|
||||
return nil
|
||||
}
|
||||
guard
|
||||
let signatureResult: Bytes = try? dependencies.crypto.perform(
|
||||
.signature(message: messageBytes, secretKey: userEdKeyPair.secretKey)
|
||||
)
|
||||
else { return nil }
|
||||
|
||||
return (
|
||||
publicKey: SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString,
|
||||
|
@ -1256,10 +1264,12 @@ public enum OpenGroupAPI {
|
|||
|
||||
// Default to using the 'standard' key
|
||||
default:
|
||||
guard let userKeyPair: KeyPair = Identity.fetchUserKeyPair(db) else { return nil }
|
||||
guard let signatureResult: Bytes = try? dependencies.ed25519.sign(data: messageBytes, keyPair: userKeyPair) else {
|
||||
return nil
|
||||
}
|
||||
guard
|
||||
let userKeyPair: KeyPair = Identity.fetchUserKeyPair(db),
|
||||
let signatureResult: Bytes = try? dependencies.crypto.perform(
|
||||
.signEd25519(data: messageBytes, keyPair: userKeyPair)
|
||||
)
|
||||
else { return nil }
|
||||
|
||||
return (
|
||||
publicKey: SessionId(.standard, publicKey: userKeyPair.publicKey).hexString,
|
||||
|
@ -1275,7 +1285,7 @@ public enum OpenGroupAPI {
|
|||
for serverName: String,
|
||||
with serverPublicKey: String,
|
||||
forceBlinded: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> URLRequest? {
|
||||
guard let url: URL = request.url else { return nil }
|
||||
|
||||
|
@ -1283,12 +1293,12 @@ public enum OpenGroupAPI {
|
|||
let path: String = url.path
|
||||
.appending(url.query.map { value in "?\(value)" })
|
||||
let method: String = (request.httpMethod ?? "GET")
|
||||
let timestamp: Int = Int(floor(dependencies.date.timeIntervalSince1970))
|
||||
let nonce: Data = Data(dependencies.nonceGenerator16.nonce())
|
||||
let timestamp: Int = Int(floor(dependencies.dateNow.timeIntervalSince1970))
|
||||
let serverPublicKeyData: Data = Data(hex: serverPublicKey)
|
||||
|
||||
guard
|
||||
!serverPublicKeyData.isEmpty,
|
||||
let nonce: Data = (try? dependencies.crypto.perform(.generateNonce16())).map({ Data($0) }),
|
||||
let timestampBytes: Bytes = "\(timestamp)".data(using: .ascii)?.bytes
|
||||
else { return nil }
|
||||
|
||||
|
@ -1296,7 +1306,7 @@ public enum OpenGroupAPI {
|
|||
let bodyHash: Bytes? = {
|
||||
guard let body: Data = request.httpBody else { return nil }
|
||||
|
||||
return dependencies.genericHash.hash(message: body.bytes, outputLength: 64)
|
||||
return try? dependencies.crypto.perform(.hash(message: body.bytes, outputLength: 64))
|
||||
}()
|
||||
|
||||
/// Generate the signature message
|
||||
|
@ -1341,7 +1351,7 @@ public enum OpenGroupAPI {
|
|||
responseType: R.Type,
|
||||
forceBlinded: Bool = false,
|
||||
timeout: TimeInterval = HTTP.defaultTimeout,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<R> {
|
||||
let urlRequest: URLRequest = try request.generateUrlRequest()
|
||||
let maybePublicKey: String? = try? OpenGroup
|
||||
|
@ -1369,19 +1379,21 @@ public enum OpenGroupAPI {
|
|||
/// This method takes in the `PreparedSendData<R>` and actually sends it to the API
|
||||
public static func send<R>(
|
||||
data: PreparedSendData<R>?,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<(ResponseInfoType, R), Error> {
|
||||
guard let validData: PreparedSendData<R> = data else {
|
||||
return Fail(error: OpenGroupAPIError.invalidPreparedData)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return dependencies.onionApi
|
||||
.sendOnionRequest(
|
||||
validData.request,
|
||||
to: validData.server,
|
||||
with: validData.publicKey,
|
||||
timeout: validData.timeout
|
||||
return dependencies.network
|
||||
.send(
|
||||
.onionRequest(
|
||||
validData.request,
|
||||
to: validData.server,
|
||||
with: validData.publicKey,
|
||||
timeout: validData.timeout
|
||||
)
|
||||
)
|
||||
.decoded(with: validData, using: dependencies)
|
||||
.eraseToAnyPublisher()
|
||||
|
|
|
@ -12,48 +12,17 @@ import SessionSnodeKit
|
|||
public final class OpenGroupManager {
|
||||
public typealias DefaultRoomInfo = (room: OpenGroupAPI.Room, existingImageData: Data?)
|
||||
|
||||
// MARK: - Cache
|
||||
|
||||
public class Cache: OGMMutableCacheType {
|
||||
public var defaultRoomsPublisher: AnyPublisher<[DefaultRoomInfo], Error>?
|
||||
public var groupImagePublishers: [String: AnyPublisher<Data, Error>] = [:]
|
||||
|
||||
public var pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server
|
||||
public var isPolling: Bool = false
|
||||
|
||||
/// Server URL to value
|
||||
public var hasPerformedInitialPoll: [String: Bool] = [:]
|
||||
public var timeSinceLastPoll: [String: TimeInterval] = [:]
|
||||
|
||||
fileprivate var _timeSinceLastOpen: TimeInterval?
|
||||
public func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval {
|
||||
if let storedTimeSinceLastOpen: TimeInterval = _timeSinceLastOpen {
|
||||
return storedTimeSinceLastOpen
|
||||
}
|
||||
|
||||
guard let lastOpen: Date = dependencies.standardUserDefaults[.lastOpen] else {
|
||||
_timeSinceLastOpen = .greatestFiniteMagnitude
|
||||
return .greatestFiniteMagnitude
|
||||
}
|
||||
|
||||
_timeSinceLastOpen = dependencies.date.timeIntervalSince(lastOpen)
|
||||
return dependencies.date.timeIntervalSince(lastOpen)
|
||||
}
|
||||
|
||||
public var pendingChanges: [OpenGroupAPI.PendingChange] = []
|
||||
}
|
||||
|
||||
// MARK: - Variables
|
||||
|
||||
public static let shared: OpenGroupManager = OpenGroupManager()
|
||||
|
||||
// MARK: - Polling
|
||||
|
||||
public func startPolling(using dependencies: OGMDependencies = OGMDependencies()) {
|
||||
public func startPolling(using dependencies: Dependencies = Dependencies()) {
|
||||
// Run on the 'workQueue' to ensure any 'Atomic' access doesn't block the main thread
|
||||
// on startup
|
||||
OpenGroupAPI.workQueue.async {
|
||||
guard !dependencies.cache.isPolling else { return }
|
||||
OpenGroupAPI.workQueue.async(using: dependencies) {
|
||||
guard !dependencies.caches[.openGroupManager].isPolling else { return }
|
||||
|
||||
let servers: Set<String> = dependencies.storage
|
||||
.read { db in
|
||||
|
@ -70,7 +39,7 @@ public final class OpenGroupManager {
|
|||
.defaulting(to: [])
|
||||
|
||||
// Update the cache state and re-create all of the pollers
|
||||
dependencies.mutableCache.mutate { cache in
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||
cache.isPolling = true
|
||||
cache.pollers = servers
|
||||
.reduce(into: [:]) { result, server in
|
||||
|
@ -80,13 +49,14 @@ public final class OpenGroupManager {
|
|||
}
|
||||
|
||||
// Now that the pollers have been created actually start them
|
||||
dependencies.cache.pollers.forEach { _, poller in poller.startIfNeeded(using: dependencies) }
|
||||
dependencies.caches[.openGroupManager].pollers
|
||||
.forEach { _, poller in poller.startIfNeeded(using: dependencies) }
|
||||
}
|
||||
}
|
||||
|
||||
public func stopPolling(using dependencies: OGMDependencies = OGMDependencies()) {
|
||||
dependencies.mutableCache.mutate {
|
||||
$0.pollers.forEach { (_, openGroupPoller) in openGroupPoller.stop() }
|
||||
public func stopPolling(using dependencies: Dependencies = Dependencies()) {
|
||||
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||
$0.pollers.forEach { _, openGroupPoller in openGroupPoller.stop() }
|
||||
$0.pollers.removeAll()
|
||||
$0.isPolling = false
|
||||
}
|
||||
|
@ -132,7 +102,13 @@ public final class OpenGroupManager {
|
|||
return options.contains(serverHost)
|
||||
}
|
||||
|
||||
public func hasExistingOpenGroup(_ db: Database, roomToken: String, server: String, publicKey: String, dependencies: OGMDependencies = OGMDependencies()) -> Bool {
|
||||
public func hasExistingOpenGroup(
|
||||
_ db: Database,
|
||||
roomToken: String,
|
||||
server: String,
|
||||
publicKey: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Bool {
|
||||
guard let serverUrl: URL = URL(string: server.lowercased()) else { return false }
|
||||
|
||||
let serverPort: String = OpenGroupManager.port(for: server, serverUrl: serverUrl)
|
||||
|
@ -164,7 +140,7 @@ public final class OpenGroupManager {
|
|||
}
|
||||
|
||||
// First check if there is no poller for the specified server
|
||||
if Set(dependencies.cache.pollers.keys).intersection(serverOptions).isEmpty {
|
||||
if Set(dependencies.caches[.openGroupManager].pollers.keys).intersection(serverOptions).isEmpty {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -187,10 +163,10 @@ public final class OpenGroupManager {
|
|||
server: String,
|
||||
publicKey: String,
|
||||
calledFromConfigHandling: Bool,
|
||||
dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Bool {
|
||||
// If we are currently polling for this server and already have a TSGroupThread for this room the do nothing
|
||||
if hasExistingOpenGroup(db, roomToken: roomToken, server: server, publicKey: publicKey, dependencies: dependencies) {
|
||||
if hasExistingOpenGroup(db, roomToken: roomToken, server: server, publicKey: publicKey, using: dependencies) {
|
||||
SNLog("Ignoring join open group attempt (already joined), user initiated: \(!calledFromConfigHandling)")
|
||||
return false
|
||||
}
|
||||
|
@ -256,7 +232,7 @@ public final class OpenGroupManager {
|
|||
server: String,
|
||||
publicKey: String,
|
||||
calledFromConfigHandling: Bool,
|
||||
dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard successfullyAddedGroup else {
|
||||
return Just(())
|
||||
|
@ -311,7 +287,7 @@ public final class OpenGroupManager {
|
|||
publicKey: publicKey,
|
||||
for: roomToken,
|
||||
on: targetServer,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
) {
|
||||
resolver(Result.success(()))
|
||||
}
|
||||
|
@ -322,7 +298,7 @@ public final class OpenGroupManager {
|
|||
receiveCompletion: { result in
|
||||
switch result {
|
||||
case .finished: break
|
||||
case .failure: SNLog("Failed to join open group.")
|
||||
case .failure(let error): SNLog("Failed to join open group with error: \(error).")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -333,7 +309,7 @@ public final class OpenGroupManager {
|
|||
_ db: Database,
|
||||
openGroupId: String,
|
||||
calledFromConfigHandling: Bool,
|
||||
using dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
let server: String? = try? OpenGroup
|
||||
.select(.server)
|
||||
|
@ -358,9 +334,9 @@ public final class OpenGroupManager {
|
|||
.defaulting(to: 1)
|
||||
|
||||
if numActiveRooms == 1, let server: String = server?.lowercased() {
|
||||
let poller = dependencies.cache.pollers[server]
|
||||
let poller = dependencies.caches[.openGroupManager].pollers[server]
|
||||
poller?.stop()
|
||||
dependencies.mutableCache.mutate { $0.pollers[server] = nil }
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { $0.pollers[server] = nil }
|
||||
}
|
||||
|
||||
// Remove all the data (everything should cascade delete)
|
||||
|
@ -435,7 +411,7 @@ public final class OpenGroupManager {
|
|||
for roomToken: String,
|
||||
on server: String,
|
||||
waitForImageToComplete: Bool = false,
|
||||
dependencies: OGMDependencies = OGMDependencies(),
|
||||
using dependencies: Dependencies,
|
||||
completion: (() -> ())? = nil
|
||||
) throws {
|
||||
// Create the open group model and get or create the thread
|
||||
|
@ -521,18 +497,19 @@ public final class OpenGroupManager {
|
|||
}
|
||||
}
|
||||
|
||||
db.afterNextTransactionNested { _ in
|
||||
db.afterNextTransactionNested { reentrantDb in
|
||||
// Dispatch async to the workQueue to prevent holding up the DBWrite thread from the
|
||||
// above transaction
|
||||
OpenGroupAPI.workQueue.async {
|
||||
OpenGroupAPI.workQueue.async(using: dependencies) {
|
||||
// Start the poller if needed
|
||||
if dependencies.cache.pollers[server.lowercased()] == nil {
|
||||
dependencies.mutableCache.mutate {
|
||||
if dependencies.caches[.openGroupManager].pollers[server.lowercased()] == nil {
|
||||
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||
$0.pollers[server.lowercased()]?.stop()
|
||||
$0.pollers[server.lowercased()] = OpenGroupAPI.Poller(for: server.lowercased())
|
||||
}
|
||||
|
||||
dependencies.cache.pollers[server.lowercased()]?.startIfNeeded(using: dependencies)
|
||||
dependencies.caches[.openGroupManager].pollers[server.lowercased()]?
|
||||
.startIfNeeded(using: dependencies)
|
||||
}
|
||||
|
||||
/// Start downloading the room image (if we don't have one or it's been updated)
|
||||
|
@ -553,8 +530,8 @@ public final class OpenGroupManager {
|
|||
)
|
||||
// Note: We need to subscribe and receive on different threads to ensure the
|
||||
// logic in 'receiveValue' doesn't result in a reentrancy database issue
|
||||
.subscribe(on: OpenGroupAPI.workQueue)
|
||||
.receive(on: DispatchQueue.global(qos: .default))
|
||||
.subscribe(on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
.receive(on: DispatchQueue.global(qos: .default), using: dependencies)
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { _ in
|
||||
if waitForImageToComplete {
|
||||
|
@ -562,7 +539,7 @@ public final class OpenGroupManager {
|
|||
}
|
||||
},
|
||||
receiveValue: { data in
|
||||
dependencies.storage.write { db in
|
||||
dependencies.storage.write(using: dependencies) { db in
|
||||
_ = try OpenGroup
|
||||
.filter(id: threadId)
|
||||
.updateAll(db, OpenGroup.Columns.imageData.set(to: data))
|
||||
|
@ -588,7 +565,7 @@ public final class OpenGroupManager {
|
|||
messages: [OpenGroupAPI.Message],
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: OpenGroup.idFor(roomToken: roomToken, server: server)) else {
|
||||
SNLog("Couldn't handle open group messages.")
|
||||
|
@ -623,7 +600,7 @@ public final class OpenGroupManager {
|
|||
openGroupServerPublicKey: openGroup.publicKey,
|
||||
message: message,
|
||||
data: data,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
if let messageInfo: MessageReceiveJob.Details.MessageInfo = processedMessage?.messageInfo {
|
||||
|
@ -634,7 +611,7 @@ public final class OpenGroupManager {
|
|||
message: messageInfo.message,
|
||||
serverExpirationTimestamp: messageInfo.serverExpirationTimestamp,
|
||||
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
largestValidSeqNo = max(largestValidSeqNo, message.seqNo)
|
||||
}
|
||||
|
@ -661,7 +638,7 @@ public final class OpenGroupManager {
|
|||
db,
|
||||
openGroupId: openGroup.id,
|
||||
message: message,
|
||||
associatedPendingChanges: dependencies.cache.pendingChanges
|
||||
associatedPendingChanges: dependencies.caches[.openGroupManager].pendingChanges
|
||||
.filter {
|
||||
guard $0.server == server && $0.room == roomToken && $0.changeType == .reaction else {
|
||||
return false
|
||||
|
@ -672,7 +649,7 @@ public final class OpenGroupManager {
|
|||
}
|
||||
return false
|
||||
},
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
try MessageReceiver.handleOpenGroupReactions(
|
||||
|
@ -708,7 +685,7 @@ public final class OpenGroupManager {
|
|||
.updateAll(db, OpenGroup.Columns.sequenceNumber.set(to: largestValidSeqNo))
|
||||
|
||||
// Update pendingChange cache based on the `largestValidSeqNo` value
|
||||
dependencies.mutableCache.mutate {
|
||||
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||
$0.pendingChanges = $0.pendingChanges
|
||||
.filter { $0.seqNo == nil || $0.seqNo! > largestValidSeqNo }
|
||||
}
|
||||
|
@ -719,7 +696,7 @@ public final class OpenGroupManager {
|
|||
messages: [OpenGroupAPI.DirectMessage],
|
||||
fromOutbox: Bool,
|
||||
on server: String,
|
||||
dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// Don't need to do anything if we have no messages (it's a valid case)
|
||||
guard !messages.isEmpty else { return }
|
||||
|
@ -762,7 +739,7 @@ public final class OpenGroupManager {
|
|||
data: messageData,
|
||||
isOutgoing: fromOutbox,
|
||||
otherBlindedPublicKey: (fromOutbox ? message.recipient : message.sender),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// We want to update the BlindedIdLookup cache with the message info so we can avoid using the
|
||||
|
@ -788,7 +765,7 @@ public final class OpenGroupManager {
|
|||
openGroupServer: server.lowercased(),
|
||||
openGroupPublicKey: openGroup.publicKey,
|
||||
isCheckingForOutbox: fromOutbox,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}()
|
||||
lookupCache[message.recipient] = lookup
|
||||
|
@ -817,7 +794,7 @@ public final class OpenGroupManager {
|
|||
message: messageInfo.message,
|
||||
serverExpirationTimestamp: messageInfo.serverExpirationTimestamp,
|
||||
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -846,7 +823,7 @@ public final class OpenGroupManager {
|
|||
in roomToken: String,
|
||||
on server: String,
|
||||
type: OpenGroupAPI.PendingChange.ReactAction,
|
||||
using dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> OpenGroupAPI.PendingChange {
|
||||
let pendingChange = OpenGroupAPI.PendingChange(
|
||||
server: server,
|
||||
|
@ -859,7 +836,7 @@ public final class OpenGroupManager {
|
|||
)
|
||||
)
|
||||
|
||||
dependencies.mutableCache.mutate {
|
||||
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||
$0.pendingChanges.append(pendingChange)
|
||||
}
|
||||
|
||||
|
@ -869,9 +846,9 @@ public final class OpenGroupManager {
|
|||
public static func updatePendingChange(
|
||||
_ pendingChange: OpenGroupAPI.PendingChange,
|
||||
seqNo: Int64?,
|
||||
using dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
dependencies.mutableCache.mutate {
|
||||
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||
if let index = $0.pendingChanges.firstIndex(of: pendingChange) {
|
||||
$0.pendingChanges[index].seqNo = seqNo
|
||||
}
|
||||
|
@ -880,9 +857,9 @@ public final class OpenGroupManager {
|
|||
|
||||
public static func removePendingChange(
|
||||
_ pendingChange: OpenGroupAPI.PendingChange,
|
||||
using dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
dependencies.mutableCache.mutate {
|
||||
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||
if let index = $0.pendingChanges.firstIndex(of: pendingChange) {
|
||||
$0.pendingChanges.remove(at: index)
|
||||
}
|
||||
|
@ -894,7 +871,7 @@ public final class OpenGroupManager {
|
|||
_ db: Database? = nil,
|
||||
capability: Capability.Variant,
|
||||
on server: String?,
|
||||
using dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Bool {
|
||||
guard let server: String = server else { return false }
|
||||
guard let db: Database = db else {
|
||||
|
@ -919,7 +896,7 @@ public final class OpenGroupManager {
|
|||
_ publicKey: String,
|
||||
for roomToken: String?,
|
||||
on server: String?,
|
||||
using dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Bool {
|
||||
guard let roomToken: String = roomToken, let server: String = server else { return false }
|
||||
|
||||
|
@ -943,7 +920,7 @@ public final class OpenGroupManager {
|
|||
|
||||
// Conveniently the logic for these different cases works in order so we can fallthrough each
|
||||
// case with only minor efficiency losses
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
|
||||
switch sessionId.prefix {
|
||||
case .standard:
|
||||
|
@ -967,10 +944,12 @@ public final class OpenGroupManager {
|
|||
.filter(id: groupId)
|
||||
.asRequest(of: String.self)
|
||||
.fetchOne(db),
|
||||
let blindedKeyPair: KeyPair = dependencies.sodium.blindedKeyPair(
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
edKeyPair: userEdKeyPair,
|
||||
genericHash: dependencies.genericHash
|
||||
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
edKeyPair: userEdKeyPair,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
else { return false }
|
||||
guard
|
||||
|
@ -1003,19 +982,16 @@ public final class OpenGroupManager {
|
|||
}
|
||||
|
||||
@discardableResult public static func getDefaultRoomsIfNeeded(
|
||||
using dependencies: OGMDependencies = OGMDependencies(
|
||||
subscribeQueue: OpenGroupAPI.workQueue,
|
||||
receiveQueue: OpenGroupAPI.workQueue
|
||||
)
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<[DefaultRoomInfo], Error> {
|
||||
// Note: If we already have a 'defaultRoomsPromise' then there is no need to get it again
|
||||
if let existingPublisher: AnyPublisher<[DefaultRoomInfo], Error> = dependencies.cache.defaultRoomsPublisher {
|
||||
if let existingPublisher: AnyPublisher<[DefaultRoomInfo], Error> = dependencies.caches[.openGroupManager].defaultRoomsPublisher {
|
||||
return existingPublisher
|
||||
}
|
||||
|
||||
// Try to retrieve the default rooms 8 times
|
||||
let publisher: AnyPublisher<[DefaultRoomInfo], Error> = dependencies.storage
|
||||
.readPublisher { db in
|
||||
.readPublisher { db -> OpenGroupAPI.PreparedSendData<OpenGroupAPI.CapabilitiesAndRoomsResponse> in
|
||||
try OpenGroupAPI.preparedCapabilitiesAndRooms(
|
||||
db,
|
||||
on: OpenGroupAPI.defaultServer,
|
||||
|
@ -1023,9 +999,9 @@ public final class OpenGroupManager {
|
|||
)
|
||||
}
|
||||
.flatMap { OpenGroupAPI.send(data: $0, using: dependencies) }
|
||||
.subscribe(on: dependencies.subscribeQueue)
|
||||
.receive(on: dependencies.receiveQueue)
|
||||
.retry(8)
|
||||
.subscribe(on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
.receive(on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
.retry(8, using: dependencies)
|
||||
.map { info, response -> [DefaultRoomInfo]? in
|
||||
dependencies.storage.write { db -> [DefaultRoomInfo] in
|
||||
// Store the capabilities first
|
||||
|
@ -1090,7 +1066,7 @@ public final class OpenGroupManager {
|
|||
switch result {
|
||||
case .finished: break
|
||||
case .failure:
|
||||
dependencies.mutableCache.mutate { cache in
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||
cache.defaultRoomsPublisher = nil
|
||||
}
|
||||
}
|
||||
|
@ -1099,7 +1075,7 @@ public final class OpenGroupManager {
|
|||
.shareReplay(1)
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
dependencies.mutableCache.mutate { cache in
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||
cache.defaultRoomsPublisher = publisher
|
||||
}
|
||||
|
||||
|
@ -1114,9 +1090,7 @@ public final class OpenGroupManager {
|
|||
for roomToken: String,
|
||||
on server: String,
|
||||
existingData: Data?,
|
||||
using dependencies: OGMDependencies = OGMDependencies(
|
||||
subscribeQueue: .global(qos: .background)
|
||||
)
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Data, Error> {
|
||||
// Normally the image for a given group is stored with the group thread, so it's only
|
||||
// fetched once. However, on the join open group screen we show images for groups the
|
||||
|
@ -1129,7 +1103,7 @@ public final class OpenGroupManager {
|
|||
// there is one.
|
||||
let threadId: String = OpenGroup.idFor(roomToken: roomToken, server: server)
|
||||
let lastOpenGroupImageUpdate: Date? = dependencies.standardUserDefaults[.lastOpenGroupImageUpdate]
|
||||
let now: Date = dependencies.date
|
||||
let now: Date = dependencies.dateNow
|
||||
let timeSinceLastUpdate: TimeInterval = (lastOpenGroupImageUpdate.map { now.timeIntervalSince($0) } ?? .greatestFiniteMagnitude)
|
||||
let updateInterval: TimeInterval = (7 * 24 * 60 * 60)
|
||||
let canUseExistingImage: Bool = (
|
||||
|
@ -1143,14 +1117,14 @@ public final class OpenGroupManager {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
if let publisher: AnyPublisher<Data, Error> = dependencies.cache.groupImagePublishers[threadId] {
|
||||
if let publisher: AnyPublisher<Data, Error> = dependencies.caches[.openGroupManager].groupImagePublishers[threadId] {
|
||||
return publisher
|
||||
}
|
||||
|
||||
// Defer the actual download and run it on a separate thread to avoid blocking the calling thread
|
||||
let publisher: AnyPublisher<Data, Error> = Deferred {
|
||||
Future { resolver in
|
||||
dependencies.subscribeQueue.async {
|
||||
DispatchQueue.global(qos: .background).async(using: dependencies) {
|
||||
// Hold on to the publisher until it has completed at least once
|
||||
dependencies.storage
|
||||
.readPublisher { db -> (Data?, OpenGroupAPI.PreparedSendData<Data>?) in
|
||||
|
@ -1224,10 +1198,10 @@ public final class OpenGroupManager {
|
|||
// Automatically subscribe for the roomImage download (want to download regardless of
|
||||
// whether the upstream subscribes)
|
||||
publisher
|
||||
.subscribe(on: dependencies.subscribeQueue)
|
||||
.subscribe(on: DispatchQueue.global(qos: .background), using: dependencies)
|
||||
.sinkUntilComplete()
|
||||
|
||||
dependencies.mutableCache.mutate { cache in
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||
cache.groupImagePublishers[threadId] = publisher
|
||||
}
|
||||
|
||||
|
@ -1235,9 +1209,65 @@ public final class OpenGroupManager {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - OpenGroupManager Cache
|
||||
|
||||
public extension OpenGroupManager {
|
||||
class Cache: OGMCacheType {
|
||||
public var defaultRoomsPublisher: AnyPublisher<[DefaultRoomInfo], Error>?
|
||||
public var groupImagePublishers: [String: AnyPublisher<Data, Error>] = [:]
|
||||
|
||||
public var pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server
|
||||
public var isPolling: Bool = false
|
||||
|
||||
/// Server URL to value
|
||||
public var hasPerformedInitialPoll: [String: Bool] = [:]
|
||||
public var timeSinceLastPoll: [String: TimeInterval] = [:]
|
||||
|
||||
fileprivate var _timeSinceLastOpen: TimeInterval?
|
||||
public func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval {
|
||||
if let storedTimeSinceLastOpen: TimeInterval = _timeSinceLastOpen {
|
||||
return storedTimeSinceLastOpen
|
||||
}
|
||||
|
||||
guard let lastOpen: Date = dependencies.standardUserDefaults[.lastOpen] else {
|
||||
_timeSinceLastOpen = .greatestFiniteMagnitude
|
||||
return .greatestFiniteMagnitude
|
||||
}
|
||||
|
||||
_timeSinceLastOpen = dependencies.dateNow.timeIntervalSince(lastOpen)
|
||||
return dependencies.dateNow.timeIntervalSince(lastOpen)
|
||||
}
|
||||
|
||||
public var pendingChanges: [OpenGroupAPI.PendingChange] = []
|
||||
}
|
||||
}
|
||||
|
||||
public extension Cache {
|
||||
static let openGroupManager: CacheInfo.Config<OGMCacheType, OGMImmutableCacheType> = CacheInfo.create(
|
||||
createInstance: { OpenGroupManager.Cache() },
|
||||
mutableInstance: { $0 },
|
||||
immutableInstance: { $0 }
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - OGMCacheType
|
||||
|
||||
public protocol OGMMutableCacheType: OGMCacheType {
|
||||
/// This is a read-only version of the `OpenGroupManager.Cache` designed to avoid unintentionally mutating the instance in a
|
||||
/// non-thread-safe way
|
||||
public protocol OGMImmutableCacheType: ImmutableCacheType {
|
||||
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { get }
|
||||
var groupImagePublishers: [String: AnyPublisher<Data, Error>] { get }
|
||||
|
||||
var pollers: [String: OpenGroupAPI.Poller] { get }
|
||||
var isPolling: Bool { get }
|
||||
|
||||
var hasPerformedInitialPoll: [String: Bool] { get }
|
||||
var timeSinceLastPoll: [String: TimeInterval] { get }
|
||||
|
||||
var pendingChanges: [OpenGroupAPI.PendingChange] { get }
|
||||
}
|
||||
|
||||
public protocol OGMCacheType: OGMImmutableCacheType, MutableCacheType {
|
||||
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { get set }
|
||||
var groupImagePublishers: [String: AnyPublisher<Data, Error>] { get set }
|
||||
|
||||
|
@ -1251,90 +1281,3 @@ public protocol OGMMutableCacheType: OGMCacheType {
|
|||
|
||||
func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval
|
||||
}
|
||||
|
||||
/// This is a read-only version of the `OGMMutableCacheType` designed to avoid unintentionally mutating the instance in a
|
||||
/// non-thread-safe way
|
||||
public protocol OGMCacheType {
|
||||
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { get }
|
||||
var groupImagePublishers: [String: AnyPublisher<Data, Error>] { get }
|
||||
|
||||
var pollers: [String: OpenGroupAPI.Poller] { get }
|
||||
var isPolling: Bool { get }
|
||||
|
||||
var hasPerformedInitialPoll: [String: Bool] { get }
|
||||
var timeSinceLastPoll: [String: TimeInterval] { get }
|
||||
|
||||
var pendingChanges: [OpenGroupAPI.PendingChange] { get }
|
||||
}
|
||||
|
||||
// MARK: - OGMDependencies
|
||||
|
||||
extension OpenGroupManager {
|
||||
public class OGMDependencies: SMKDependencies {
|
||||
/// These should not be accessed directly but rather via an instance of this type
|
||||
private static let _cacheInstance: OGMMutableCacheType = OpenGroupManager.Cache()
|
||||
private static let _cacheInstanceAccessQueue = DispatchQueue(label: "OGMCacheInstanceAccess")
|
||||
|
||||
internal var _mutableCache: Atomic<OGMMutableCacheType?>
|
||||
public var mutableCache: Atomic<OGMMutableCacheType> {
|
||||
get {
|
||||
Dependencies.getMutableValueSettingIfNull(&_mutableCache) {
|
||||
OGMDependencies._cacheInstanceAccessQueue.sync { OGMDependencies._cacheInstance }
|
||||
}
|
||||
}
|
||||
}
|
||||
public var cache: OGMCacheType {
|
||||
get {
|
||||
Dependencies.getValueSettingIfNull(&_mutableCache) {
|
||||
OGMDependencies._cacheInstanceAccessQueue.sync { OGMDependencies._cacheInstance }
|
||||
}
|
||||
}
|
||||
set {
|
||||
guard let mutableValue: OGMMutableCacheType = newValue as? OGMMutableCacheType else { return }
|
||||
|
||||
_mutableCache.mutate { $0 = mutableValue }
|
||||
}
|
||||
}
|
||||
|
||||
public init(
|
||||
subscribeQueue: DispatchQueue? = nil,
|
||||
receiveQueue: DispatchQueue? = nil,
|
||||
cache: OGMMutableCacheType? = nil,
|
||||
onionApi: OnionRequestAPIType.Type? = nil,
|
||||
generalCache: MutableGeneralCacheType? = nil,
|
||||
storage: Storage? = nil,
|
||||
scheduler: ValueObservationScheduler? = nil,
|
||||
sodium: SodiumType? = nil,
|
||||
box: BoxType? = nil,
|
||||
genericHash: GenericHashType? = nil,
|
||||
sign: SignType? = nil,
|
||||
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
|
||||
ed25519: Ed25519Type? = nil,
|
||||
nonceGenerator16: NonceGenerator16ByteType? = nil,
|
||||
nonceGenerator24: NonceGenerator24ByteType? = nil,
|
||||
standardUserDefaults: UserDefaultsType? = nil,
|
||||
date: Date? = nil
|
||||
) {
|
||||
_mutableCache = Atomic(cache)
|
||||
|
||||
super.init(
|
||||
subscribeQueue: subscribeQueue,
|
||||
receiveQueue: receiveQueue,
|
||||
onionApi: onionApi,
|
||||
generalCache: generalCache,
|
||||
storage: storage,
|
||||
scheduler: scheduler,
|
||||
sodium: sodium,
|
||||
box: box,
|
||||
genericHash: genericHash,
|
||||
sign: sign,
|
||||
aeadXChaCha20Poly1305Ietf: aeadXChaCha20Poly1305Ietf,
|
||||
ed25519: ed25519,
|
||||
nonceGenerator16: nonceGenerator16,
|
||||
nonceGenerator24: nonceGenerator24,
|
||||
standardUserDefaults: standardUserDefaults,
|
||||
date: date
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Sodium
|
||||
|
||||
public protocol NonceGenerator16ByteType {
|
||||
var NonceBytes: Int { get }
|
||||
|
||||
func nonce() -> Array<UInt8>
|
||||
}
|
||||
|
||||
public protocol NonceGenerator24ByteType {
|
||||
var NonceBytes: Int { get }
|
||||
|
||||
func nonce() -> Array<UInt8>
|
||||
}
|
||||
|
||||
extension OpenGroupAPI {
|
||||
public class NonceGenerator16Byte: NonceGenerator, NonceGenerator16ByteType {
|
||||
public var NonceBytes: Int { 16 }
|
||||
}
|
||||
|
||||
public class NonceGenerator24Byte: NonceGenerator, NonceGenerator24ByteType {
|
||||
public var NonceBytes: Int { 24 }
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ public extension OpenGroupAPI {
|
|||
private let method: HTTPMethod
|
||||
private let path: String
|
||||
public let endpoint: Endpoint
|
||||
fileprivate let batchEndpoints: [Endpoint]
|
||||
internal let batchEndpoints: [Endpoint]
|
||||
public let batchResponseTypes: [Decodable.Type]
|
||||
|
||||
/// The `jsonBodyEncoder` is used to simplify the encoding for `BatchRequest`
|
||||
|
@ -185,7 +185,7 @@ public extension OpenGroupAPI.PreparedSendData {
|
|||
public extension Publisher where Output == (ResponseInfoType, Data?), Failure == Error {
|
||||
func decoded<R>(
|
||||
with preparedData: OpenGroupAPI.PreparedSendData<R>,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<(ResponseInfoType, R), Error> {
|
||||
self
|
||||
.tryMap { responseInfo, maybeData -> (ResponseInfoType, R) in
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import Curve25519Kit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
public protocol SodiumType {
|
||||
func getBox() -> BoxType
|
||||
func getGenericHash() -> GenericHashType
|
||||
func getSign() -> SignType
|
||||
func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType
|
||||
|
||||
func generateBlindingFactor(serverPublicKey: String, genericHash: GenericHashType) -> Bytes?
|
||||
func blindedKeyPair(serverPublicKey: String, edKeyPair: KeyPair, genericHash: GenericHashType) -> KeyPair?
|
||||
func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes?
|
||||
|
||||
func combineKeys(lhsKeyBytes: Bytes, rhsKeyBytes: Bytes) -> Bytes?
|
||||
func sharedBlindedEncryptionKey(secretKey a: Bytes, otherBlindedPublicKey: Bytes, fromBlindedPublicKey kA: Bytes, toBlindedPublicKey kB: Bytes, genericHash: GenericHashType) -> Bytes?
|
||||
|
||||
func sessionId(_ sessionId: String, matchesBlindedId blindedSessionId: String, serverPublicKey: String, genericHash: GenericHashType) -> Bool
|
||||
}
|
||||
|
||||
public protocol AeadXChaCha20Poly1305IetfType {
|
||||
var KeyBytes: Int { get }
|
||||
var ABytes: Int { get }
|
||||
|
||||
func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes?
|
||||
func decrypt(authenticatedCipherText: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes?
|
||||
}
|
||||
|
||||
public protocol Ed25519Type {
|
||||
func sign(data: Bytes, keyPair: KeyPair) throws -> Bytes?
|
||||
func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool
|
||||
}
|
||||
|
||||
public protocol BoxType {
|
||||
func seal(message: Bytes, recipientPublicKey: Bytes) -> Bytes?
|
||||
func open(anonymousCipherText: Bytes, recipientPublicKey: Bytes, recipientSecretKey: Bytes) -> Bytes?
|
||||
}
|
||||
|
||||
public protocol GenericHashType {
|
||||
func hash(message: Bytes, key: Bytes?) -> Bytes?
|
||||
func hash(message: Bytes, outputLength: Int) -> Bytes?
|
||||
func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes?
|
||||
}
|
||||
|
||||
public protocol SignType {
|
||||
var Bytes: Int { get }
|
||||
var PublicKeyBytes: Int { get }
|
||||
|
||||
func toX25519(ed25519PublicKey: Bytes) -> Bytes?
|
||||
func signature(message: Bytes, secretKey: Bytes) -> Bytes?
|
||||
func verify(message: Bytes, publicKey: Bytes, signature: Bytes) -> Bool
|
||||
}
|
||||
|
||||
// MARK: - Default Values
|
||||
|
||||
extension GenericHashType {
|
||||
func hash(message: Bytes) -> Bytes? { return hash(message: message, key: nil) }
|
||||
|
||||
func hashSaltPersonal(message: Bytes, outputLength: Int, salt: Bytes, personal: Bytes) -> Bytes? {
|
||||
return hashSaltPersonal(message: message, outputLength: outputLength, key: nil, salt: salt, personal: personal)
|
||||
}
|
||||
}
|
||||
|
||||
extension AeadXChaCha20Poly1305IetfType {
|
||||
func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes) -> Bytes? {
|
||||
return encrypt(message: message, secretKey: secretKey, nonce: nonce, additionalData: nil)
|
||||
}
|
||||
|
||||
func decrypt(authenticatedCipherText: Bytes, secretKey: Bytes, nonce: Bytes) -> Bytes? {
|
||||
return decrypt(authenticatedCipherText: authenticatedCipherText, secretKey: secretKey, nonce: nonce, additionalData: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Conformance
|
||||
|
||||
extension Sodium: SodiumType {
|
||||
public func getBox() -> BoxType { return box }
|
||||
public func getGenericHash() -> GenericHashType { return genericHash }
|
||||
public func getSign() -> SignType { return sign }
|
||||
public func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return aead.xchacha20poly1305ietf }
|
||||
|
||||
public func blindedKeyPair(serverPublicKey: String, edKeyPair: KeyPair) -> KeyPair? {
|
||||
return blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: edKeyPair, genericHash: getGenericHash())
|
||||
}
|
||||
}
|
||||
|
||||
extension Box: BoxType {}
|
||||
extension GenericHash: GenericHashType {}
|
||||
extension Sign: SignType {}
|
||||
extension Aead.XChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {}
|
||||
|
||||
struct Ed25519Wrapper: Ed25519Type {
|
||||
func sign(data: Bytes, keyPair: KeyPair) throws -> Bytes? {
|
||||
let ecKeyPair: ECKeyPair = try ECKeyPair(
|
||||
publicKeyData: Data(keyPair.publicKey),
|
||||
privateKeyData: Data(keyPair.secretKey)
|
||||
)
|
||||
|
||||
return try Ed25519.sign(Data(data), with: ecKeyPair).bytes
|
||||
}
|
||||
|
||||
func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool {
|
||||
return try Ed25519.verifySignature(signature, publicKey: publicKey, data: data)
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Sodium
|
||||
import SessionSnodeKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
public class SMKDependencies: SSKDependencies {
|
||||
internal var _sodium: Atomic<SodiumType?>
|
||||
public var sodium: SodiumType {
|
||||
get { Dependencies.getValueSettingIfNull(&_sodium) { Sodium() } }
|
||||
set { _sodium.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _box: Atomic<BoxType?>
|
||||
public var box: BoxType {
|
||||
get { Dependencies.getValueSettingIfNull(&_box) { sodium.getBox() } }
|
||||
set { _box.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _genericHash: Atomic<GenericHashType?>
|
||||
public var genericHash: GenericHashType {
|
||||
get { Dependencies.getValueSettingIfNull(&_genericHash) { sodium.getGenericHash() } }
|
||||
set { _genericHash.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _sign: Atomic<SignType?>
|
||||
public var sign: SignType {
|
||||
get { Dependencies.getValueSettingIfNull(&_sign) { sodium.getSign() } }
|
||||
set { _sign.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _aeadXChaCha20Poly1305Ietf: Atomic<AeadXChaCha20Poly1305IetfType?>
|
||||
public var aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {
|
||||
get { Dependencies.getValueSettingIfNull(&_aeadXChaCha20Poly1305Ietf) { sodium.getAeadXChaCha20Poly1305Ietf() } }
|
||||
set { _aeadXChaCha20Poly1305Ietf.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _ed25519: Atomic<Ed25519Type?>
|
||||
public var ed25519: Ed25519Type {
|
||||
get { Dependencies.getValueSettingIfNull(&_ed25519) { Ed25519Wrapper() } }
|
||||
set { _ed25519.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _nonceGenerator16: Atomic<NonceGenerator16ByteType?>
|
||||
public var nonceGenerator16: NonceGenerator16ByteType {
|
||||
get { Dependencies.getValueSettingIfNull(&_nonceGenerator16) { OpenGroupAPI.NonceGenerator16Byte() } }
|
||||
set { _nonceGenerator16.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _nonceGenerator24: Atomic<NonceGenerator24ByteType?>
|
||||
public var nonceGenerator24: NonceGenerator24ByteType {
|
||||
get { Dependencies.getValueSettingIfNull(&_nonceGenerator24) { OpenGroupAPI.NonceGenerator24Byte() } }
|
||||
set { _nonceGenerator24.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
public init(
|
||||
subscribeQueue: DispatchQueue? = nil,
|
||||
receiveQueue: DispatchQueue? = nil,
|
||||
onionApi: OnionRequestAPIType.Type? = nil,
|
||||
generalCache: MutableGeneralCacheType? = nil,
|
||||
storage: Storage? = nil,
|
||||
scheduler: ValueObservationScheduler? = nil,
|
||||
sodium: SodiumType? = nil,
|
||||
box: BoxType? = nil,
|
||||
genericHash: GenericHashType? = nil,
|
||||
sign: SignType? = nil,
|
||||
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
|
||||
ed25519: Ed25519Type? = nil,
|
||||
nonceGenerator16: NonceGenerator16ByteType? = nil,
|
||||
nonceGenerator24: NonceGenerator24ByteType? = nil,
|
||||
standardUserDefaults: UserDefaultsType? = nil,
|
||||
date: Date? = nil
|
||||
) {
|
||||
_sodium = Atomic(sodium)
|
||||
_box = Atomic(box)
|
||||
_genericHash = Atomic(genericHash)
|
||||
_sign = Atomic(sign)
|
||||
_aeadXChaCha20Poly1305Ietf = Atomic(aeadXChaCha20Poly1305Ietf)
|
||||
_ed25519 = Atomic(ed25519)
|
||||
_nonceGenerator16 = Atomic(nonceGenerator16)
|
||||
_nonceGenerator24 = Atomic(nonceGenerator24)
|
||||
|
||||
super.init(
|
||||
subscribeQueue: subscribeQueue,
|
||||
receiveQueue: receiveQueue,
|
||||
onionApi: onionApi,
|
||||
generalCache: generalCache,
|
||||
storage: storage,
|
||||
scheduler: scheduler,
|
||||
standardUserDefaults: standardUserDefaults,
|
||||
date: date
|
||||
)
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ public enum MessageSenderError: LocalizedError, Equatable {
|
|||
case encryptionFailed
|
||||
case noUsername
|
||||
case attachmentsNotUploaded
|
||||
case blindingFailed
|
||||
|
||||
// Closed groups
|
||||
case noThread
|
||||
|
@ -21,7 +22,10 @@ public enum MessageSenderError: LocalizedError, Equatable {
|
|||
|
||||
internal var isRetryable: Bool {
|
||||
switch self {
|
||||
case .invalidMessage, .protoConversionFailed, .invalidClosedGroupUpdate, .signingFailed, .encryptionFailed: return false
|
||||
case .invalidMessage, .protoConversionFailed, .invalidClosedGroupUpdate,
|
||||
.signingFailed, .encryptionFailed, .blindingFailed:
|
||||
return false
|
||||
|
||||
default: return true
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +40,7 @@ public enum MessageSenderError: LocalizedError, Equatable {
|
|||
case .encryptionFailed: return "Couldn't encrypt message."
|
||||
case .noUsername: return "Missing username."
|
||||
case .attachmentsNotUploaded: return "Attachments for this message have not been uploaded."
|
||||
case .blindingFailed: return "Couldn't blind the sender"
|
||||
|
||||
// Closed groups
|
||||
case .noThread: return "Couldn't find a thread associated with the given group public key."
|
||||
|
@ -58,6 +63,7 @@ public enum MessageSenderError: LocalizedError, Equatable {
|
|||
case (.noThread, .noThread): return true
|
||||
case (.noKeyPair, .noKeyPair): return true
|
||||
case (.invalidClosedGroupUpdate, .invalidClosedGroupUpdate): return true
|
||||
case (.blindingFailed, .blindingFailed): return true
|
||||
|
||||
case (.other(let lhsError), .other(let rhsError)):
|
||||
// Not ideal but the best we can do
|
||||
|
|
|
@ -194,7 +194,11 @@ extension MessageReceiver {
|
|||
|
||||
// MARK: - Convenience
|
||||
|
||||
public static func handleIncomingCallOfferInBusyState(_ db: Database, message: CallMessage) throws {
|
||||
public static func handleIncomingCallOfferInBusyState(
|
||||
_ db: Database,
|
||||
message: CallMessage,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
let messageInfo: CallMessage.MessageInfo = CallMessage.MessageInfo(state: .missed)
|
||||
|
||||
guard
|
||||
|
@ -229,7 +233,7 @@ extension MessageReceiver {
|
|||
.inserted(db)
|
||||
|
||||
MessageSender.sendImmediate(
|
||||
preparedSendData: try MessageSender
|
||||
data: try MessageSender
|
||||
.preparedSendData(
|
||||
db,
|
||||
message: CallMessage(
|
||||
|
@ -242,8 +246,10 @@ extension MessageReceiver {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||
.defaultNamespace,
|
||||
interactionId: nil // Explicitly nil as it's a separate message from above
|
||||
)
|
||||
interactionId: nil, // Explicitly nil as it's a separate message from above
|
||||
using: dependencies
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.sinkUntilComplete()
|
||||
|
|
|
@ -12,10 +12,11 @@ extension MessageReceiver {
|
|||
_ db: Database,
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
message: ClosedGroupControlMessage
|
||||
message: ClosedGroupControlMessage,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
switch message.kind {
|
||||
case .new: try handleNewClosedGroup(db, message: message)
|
||||
case .new: try handleNewClosedGroup(db, message: message, using: dependencies)
|
||||
|
||||
case .encryptionKeyPair:
|
||||
try handleClosedGroupEncryptionKeyPair(
|
||||
|
@ -65,7 +66,11 @@ extension MessageReceiver {
|
|||
|
||||
// MARK: - Specific Handling
|
||||
|
||||
private static func handleNewClosedGroup(_ db: Database, message: ClosedGroupControlMessage) throws {
|
||||
private static func handleNewClosedGroup(
|
||||
_ db: Database,
|
||||
message: ClosedGroupControlMessage,
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
guard case let .new(publicKeyAsData, name, encryptionKeyPair, membersAsData, adminsAsData, expirationTimer) = message.kind else {
|
||||
return
|
||||
}
|
||||
|
@ -112,7 +117,8 @@ extension MessageReceiver {
|
|||
admins: adminsAsData.map { $0.toHexString() },
|
||||
expirationTimer: expirationTimer,
|
||||
formationTimestampMs: sentTimestamp,
|
||||
calledFromConfigHandling: false
|
||||
calledFromConfigHandling: false,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -125,7 +131,8 @@ extension MessageReceiver {
|
|||
admins: [String],
|
||||
expirationTimer: UInt32,
|
||||
formationTimestampMs: UInt64,
|
||||
calledFromConfigHandling: Bool
|
||||
calledFromConfigHandling: Bool,
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
// With new closed groups we only want to create them if the admin creating the closed group is an
|
||||
// approved contact (to prevent spam via closed groups getting around message requests if users are
|
||||
|
@ -222,7 +229,7 @@ extension MessageReceiver {
|
|||
}
|
||||
|
||||
// Start polling
|
||||
ClosedGroupPoller.shared.startIfNeeded(for: groupPublicKey)
|
||||
ClosedGroupPoller.shared.startIfNeeded(for: groupPublicKey, using: dependencies)
|
||||
|
||||
// Notify the PN server
|
||||
let _ = PushNotificationAPI.performOperation(.subscribe, for: groupPublicKey, publicKey: getUserHexEncodedPublicKey(db))
|
||||
|
|
|
@ -7,7 +7,11 @@ import SessionUIKit
|
|||
import SessionUtilitiesKit
|
||||
|
||||
extension MessageReceiver {
|
||||
internal static func handleLegacyConfigurationMessage(_ db: Database, message: ConfigurationMessage) throws {
|
||||
internal static func handleLegacyConfigurationMessage(
|
||||
_ db: Database,
|
||||
message: ConfigurationMessage,
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
|
||||
guard !SessionUtil.userConfigsEnabled(db) else {
|
||||
TopBannerController.show(warning: .outdatedUserConfig)
|
||||
|
@ -46,7 +50,8 @@ extension MessageReceiver {
|
|||
)
|
||||
}(),
|
||||
sentTimestamp: messageSentTimestamp,
|
||||
calledFromConfigHandling: true
|
||||
calledFromConfigHandling: true,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Create a contact for the current user if needed (also force-approve the current user
|
||||
|
@ -192,7 +197,8 @@ extension MessageReceiver {
|
|||
admins: [String](closedGroup.admins),
|
||||
expirationTimer: closedGroup.expirationTimer,
|
||||
formationTimestampMs: message.sentTimestamp!,
|
||||
calledFromConfigHandling: false // Legacy config isn't an issue
|
||||
calledFromConfigHandling: false, // Legacy config isn't an issue
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ extension MessageReceiver {
|
|||
internal static func handleMessageRequestResponse(
|
||||
_ db: Database,
|
||||
message: MessageRequestResponse,
|
||||
dependencies: SMKDependencies
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
let userPublicKey = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let userPublicKey = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
var blindedContactIds: [String] = []
|
||||
|
||||
// Ignore messages which were sent from the current user
|
||||
|
@ -42,7 +42,8 @@ extension MessageReceiver {
|
|||
fileName: nil
|
||||
)
|
||||
}(),
|
||||
sentTimestamp: messageSentTimestamp
|
||||
sentTimestamp: messageSentTimestamp,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -73,11 +74,13 @@ extension MessageReceiver {
|
|||
// If the sessionId matches the blindedId then this thread needs to be converted to an
|
||||
// un-blinded thread
|
||||
guard
|
||||
dependencies.sodium.sessionId(
|
||||
senderId,
|
||||
matchesBlindedId: blindedIdLookup.blindedId,
|
||||
serverPublicKey: blindedIdLookup.openGroupPublicKey,
|
||||
genericHash: dependencies.genericHash
|
||||
dependencies.crypto.verify(
|
||||
.sessionId(
|
||||
senderId,
|
||||
matchesBlindedId: blindedIdLookup.blindedId,
|
||||
serverPublicKey: blindedIdLookup.openGroupPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
else { return }
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ extension MessageReceiver {
|
|||
threadVariant: SessionThread.Variant,
|
||||
message: VisibleMessage,
|
||||
associatedWithProto proto: SNProtoContent,
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> Int64 {
|
||||
guard let sender: String = message.sender, let dataMessage = proto.dataMessage else {
|
||||
throw MessageReceiverError.invalidMessage
|
||||
|
@ -43,7 +43,8 @@ extension MessageReceiver {
|
|||
fileName: nil
|
||||
)
|
||||
}(),
|
||||
sentTimestamp: messageSentTimestamp
|
||||
sentTimestamp: messageSentTimestamp,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -64,7 +65,7 @@ extension MessageReceiver {
|
|||
}
|
||||
|
||||
// Store the message variant so we can run variant-specific behaviours
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let thread: SessionThread = try SessionThread
|
||||
.fetchOrCreate(db, id: threadId, variant: threadVariant, shouldBeVisible: nil)
|
||||
let maybeOpenGroup: OpenGroup? = {
|
||||
|
@ -90,10 +91,12 @@ extension MessageReceiver {
|
|||
|
||||
guard
|
||||
let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
||||
let blindedKeyPair: KeyPair = sodium.blindedKeyPair(
|
||||
serverPublicKey: openGroup.publicKey,
|
||||
edKeyPair: userEdKeyPair,
|
||||
genericHash: sodium.genericHash
|
||||
let blindedKeyPair: KeyPair = try? dependencies.crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: openGroup.publicKey,
|
||||
edKeyPair: userEdKeyPair,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
else { return .standardIncoming }
|
||||
|
||||
|
@ -163,7 +166,8 @@ extension MessageReceiver {
|
|||
db,
|
||||
threadId: thread.id,
|
||||
body: message.text,
|
||||
quoteAuthorId: dataMessage.quote?.author
|
||||
quoteAuthorId: dataMessage.quote?.author,
|
||||
using: dependencies
|
||||
),
|
||||
// Note: Ensure we don't ever expire open group messages
|
||||
expiresInSeconds: (disappearingMessagesConfiguration.isEnabled && message.openGroupServerMessageId == nil ?
|
||||
|
@ -294,7 +298,7 @@ extension MessageReceiver {
|
|||
.appending(quote?.attachmentId)
|
||||
.appending(linkPreview?.attachmentId)
|
||||
.forEach { attachmentId in
|
||||
JobRunner.add(
|
||||
dependencies.jobRunner.add(
|
||||
db,
|
||||
job: Job(
|
||||
variant: .attachmentDownload,
|
||||
|
@ -304,7 +308,8 @@ extension MessageReceiver {
|
|||
attachmentId: attachmentId
|
||||
)
|
||||
),
|
||||
canStartJob: isMainAppActive
|
||||
canStartJob: isMainAppActive,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import Foundation
|
|||
import Combine
|
||||
import GRDB
|
||||
import Sodium
|
||||
import Curve25519Kit
|
||||
import SessionUtilitiesKit
|
||||
import SessionSnodeKit
|
||||
|
||||
|
@ -13,21 +12,21 @@ extension MessageSender {
|
|||
|
||||
public static func createClosedGroup(
|
||||
name: String,
|
||||
members: Set<String>
|
||||
members: Set<String>,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<SessionThread, Error> {
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { db -> (String, SessionThread, [MessageSender.PreparedSendData]) in
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
var members: Set<String> = members
|
||||
// Generate the group's two keys
|
||||
guard
|
||||
let groupKeyPair: KeyPair = dependencies.crypto.generate(.x25519KeyPair()),
|
||||
let encryptionKeyPair: KeyPair = dependencies.crypto.generate(.x25519KeyPair())
|
||||
else { throw MessageSenderError.noKeyPair }
|
||||
|
||||
// Generate the group's public key
|
||||
let groupKeyPair: ECKeyPair = Curve25519.generateKeyPair()
|
||||
let groupPublicKey: String = KeyPair(
|
||||
publicKey: groupKeyPair.publicKey.bytes,
|
||||
secretKey: groupKeyPair.privateKey.bytes
|
||||
).hexEncodedPublicKey // Includes the 'SessionId.Prefix.standard' prefix
|
||||
// Generate the key pair that'll be used for encryption and decryption
|
||||
let encryptionKeyPair: ECKeyPair = Curve25519.generateKeyPair()
|
||||
// Includes the 'SessionId.Prefix.standard' prefix
|
||||
let groupPublicKey: String = groupKeyPair.hexEncodedPublicKey
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
var members: Set<String> = members
|
||||
|
||||
// Create the group
|
||||
members.insert(userPublicKey) // Ensure the current user is included in the member list
|
||||
|
@ -49,8 +48,8 @@ extension MessageSender {
|
|||
let latestKeyPairReceivedTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000)
|
||||
try ClosedGroupKeyPair(
|
||||
threadId: groupPublicKey,
|
||||
publicKey: encryptionKeyPair.publicKey,
|
||||
secretKey: encryptionKeyPair.privateKey,
|
||||
publicKey: Data(encryptionKeyPair.publicKey),
|
||||
secretKey: Data(encryptionKeyPair.secretKey),
|
||||
receivedTimestamp: latestKeyPairReceivedTimestamp
|
||||
).insert(db)
|
||||
|
||||
|
@ -78,8 +77,8 @@ extension MessageSender {
|
|||
db,
|
||||
groupPublicKey: groupPublicKey,
|
||||
name: name,
|
||||
latestKeyPairPublicKey: encryptionKeyPair.publicKey,
|
||||
latestKeyPairSecretKey: encryptionKeyPair.privateKey,
|
||||
latestKeyPairPublicKey: Data(encryptionKeyPair.publicKey),
|
||||
latestKeyPairSecretKey: Data(encryptionKeyPair.secretKey),
|
||||
latestKeyPairReceivedTimestamp: latestKeyPairReceivedTimestamp,
|
||||
disappearingConfig: DisappearingMessagesConfiguration.defaultWith(groupPublicKey),
|
||||
members: members,
|
||||
|
@ -94,10 +93,7 @@ extension MessageSender {
|
|||
kind: .new(
|
||||
publicKey: Data(hex: groupPublicKey),
|
||||
name: name,
|
||||
encryptionKeyPair: KeyPair(
|
||||
publicKey: encryptionKeyPair.publicKey.bytes,
|
||||
secretKey: encryptionKeyPair.privateKey.bytes
|
||||
),
|
||||
encryptionKeyPair: encryptionKeyPair,
|
||||
members: membersAsData,
|
||||
admins: adminsAsData,
|
||||
expirationTimer: 0
|
||||
|
@ -108,7 +104,8 @@ extension MessageSender {
|
|||
),
|
||||
to: .contact(publicKey: memberId),
|
||||
namespace: Message.Destination.contact(publicKey: memberId).defaultNamespace,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -119,7 +116,7 @@ extension MessageSender {
|
|||
.MergeMany(
|
||||
// Send a closed group update message to all members individually
|
||||
memberSendData
|
||||
.map { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.map { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.appending(
|
||||
// Notify the PN server
|
||||
PushNotificationAPI.performOperation(
|
||||
|
@ -135,7 +132,7 @@ extension MessageSender {
|
|||
.handleEvents(
|
||||
receiveOutput: { thread in
|
||||
// Start polling
|
||||
ClosedGroupPoller.shared.startIfNeeded(for: thread.id)
|
||||
ClosedGroupPoller.shared.startIfNeeded(for: thread.id, using: dependencies)
|
||||
}
|
||||
)
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -151,21 +148,25 @@ extension MessageSender {
|
|||
targetMembers: Set<String>,
|
||||
userPublicKey: String,
|
||||
allGroupMembers: [GroupMember],
|
||||
closedGroup: ClosedGroup
|
||||
closedGroup: ClosedGroup,
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard allGroupMembers.contains(where: { $0.role == .admin && $0.profileId == userPublicKey }) else {
|
||||
return Fail(error: MessageSenderError.invalidClosedGroupUpdate)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return Storage.shared
|
||||
return dependencies.storage
|
||||
.readPublisher { db -> (ClosedGroupKeyPair, MessageSender.PreparedSendData) in
|
||||
// Generate the new encryption key pair
|
||||
let legacyNewKeyPair: ECKeyPair = Curve25519.generateKeyPair()
|
||||
guard let legacyNewKeyPair: KeyPair = dependencies.crypto.generate(.x25519KeyPair()) else {
|
||||
throw MessageSenderError.noKeyPair
|
||||
}
|
||||
|
||||
let newKeyPair: ClosedGroupKeyPair = ClosedGroupKeyPair(
|
||||
threadId: closedGroup.threadId,
|
||||
publicKey: legacyNewKeyPair.publicKey,
|
||||
secretKey: legacyNewKeyPair.privateKey,
|
||||
publicKey: Data(legacyNewKeyPair.publicKey),
|
||||
secretKey: Data(legacyNewKeyPair.secretKey),
|
||||
receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000)
|
||||
)
|
||||
|
||||
|
@ -193,7 +194,8 @@ extension MessageSender {
|
|||
encryptedKeyPair: try MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
plaintext: plaintext,
|
||||
for: memberPublicKey
|
||||
for: memberPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -204,20 +206,21 @@ extension MessageSender {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup)
|
||||
.defaultNamespace,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
return (newKeyPair, sendData)
|
||||
}
|
||||
.flatMap { newKeyPair, sendData -> AnyPublisher<ClosedGroupKeyPair, Error> in
|
||||
MessageSender.sendImmediate(preparedSendData: sendData)
|
||||
MessageSender.sendImmediate(data: sendData, using: dependencies)
|
||||
.map { _ in newKeyPair }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.handleEvents(
|
||||
receiveOutput: { newKeyPair in
|
||||
/// Store it **after** having sent out the message to the group
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
try newKeyPair.insert(db)
|
||||
|
||||
// Update libSession
|
||||
|
@ -251,11 +254,12 @@ extension MessageSender {
|
|||
public static func update(
|
||||
groupPublicKey: String,
|
||||
with members: Set<String>,
|
||||
name: String
|
||||
name: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
return Storage.shared
|
||||
.writePublisher { db -> (String, ClosedGroup, [GroupMember], Set<String>) in
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
|
||||
// Get the group, check preconditions & prepare
|
||||
guard (try? SessionThread.exists(db, id: groupPublicKey)) == true else {
|
||||
|
@ -292,7 +296,8 @@ extension MessageSender {
|
|||
message: ClosedGroupControlMessage(kind: .nameChange(name: name)),
|
||||
interactionId: interactionId,
|
||||
threadId: groupPublicKey,
|
||||
threadVariant: .legacyGroup
|
||||
threadVariant: .legacyGroup,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Update libSession
|
||||
|
@ -321,7 +326,8 @@ extension MessageSender {
|
|||
addedMembers: addedMembers,
|
||||
userPublicKey: userPublicKey,
|
||||
allGroupMembers: allGroupMembers,
|
||||
closedGroup: closedGroup
|
||||
closedGroup: closedGroup,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
@ -348,7 +354,8 @@ extension MessageSender {
|
|||
removedMembers: removedMembers,
|
||||
userPublicKey: userPublicKey,
|
||||
allGroupMembers: allGroupMembers,
|
||||
closedGroup: closedGroup
|
||||
closedGroup: closedGroup,
|
||||
using: dependencies
|
||||
)
|
||||
.catch { _ in Fail(error: MessageSenderError.invalidClosedGroupUpdate).eraseToAnyPublisher() }
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -364,7 +371,8 @@ extension MessageSender {
|
|||
addedMembers: Set<String>,
|
||||
userPublicKey: String,
|
||||
allGroupMembers: [GroupMember],
|
||||
closedGroup: ClosedGroup
|
||||
closedGroup: ClosedGroup,
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
guard let disappearingMessagesConfig: DisappearingMessagesConfiguration = try DisappearingMessagesConfiguration.fetchOne(db, id: closedGroup.threadId) else {
|
||||
throw StorageError.objectNotFound
|
||||
|
@ -419,7 +427,8 @@ extension MessageSender {
|
|||
),
|
||||
interactionId: interactionId,
|
||||
threadId: closedGroup.threadId,
|
||||
threadVariant: .legacyGroup
|
||||
threadVariant: .legacyGroup,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
try addedMembers.forEach { member in
|
||||
|
@ -446,7 +455,8 @@ extension MessageSender {
|
|||
),
|
||||
interactionId: nil,
|
||||
threadId: member,
|
||||
threadVariant: .contact
|
||||
threadVariant: .contact,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Add the users to the group
|
||||
|
@ -469,7 +479,8 @@ extension MessageSender {
|
|||
removedMembers: Set<String>,
|
||||
userPublicKey: String,
|
||||
allGroupMembers: [GroupMember],
|
||||
closedGroup: ClosedGroup
|
||||
closedGroup: ClosedGroup,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard !removedMembers.contains(userPublicKey) else {
|
||||
SNLog("Invalid closed group update.")
|
||||
|
@ -490,7 +501,7 @@ extension MessageSender {
|
|||
.map { $0.profileId }
|
||||
let members: Set<String> = Set(groupMemberIds).subtracting(removedMembers)
|
||||
|
||||
return Storage.shared
|
||||
return dependencies.storage
|
||||
.writePublisher { db in
|
||||
// Update zombie & member list
|
||||
try GroupMember
|
||||
|
@ -535,16 +546,18 @@ extension MessageSender {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup)
|
||||
.defaultNamespace,
|
||||
interactionId: interactionId
|
||||
interactionId: interactionId,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.flatMap { _ -> AnyPublisher<Void, Error> in
|
||||
MessageSender.generateAndSendNewEncryptionKeyPair(
|
||||
targetMembers: members,
|
||||
userPublicKey: userPublicKey,
|
||||
allGroupMembers: allGroupMembers,
|
||||
closedGroup: closedGroup
|
||||
closedGroup: closedGroup,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -561,9 +574,10 @@ extension MessageSender {
|
|||
public static func leave(
|
||||
_ db: Database,
|
||||
groupPublicKey: String,
|
||||
deleteThread: Bool
|
||||
deleteThread: Bool,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
|
||||
// Notify the user
|
||||
let interaction: Interaction = try Interaction(
|
||||
|
@ -574,7 +588,7 @@ extension MessageSender {
|
|||
timestampMs: SnodeAPI.currentOffsetTimestampMs()
|
||||
).inserted(db)
|
||||
|
||||
JobRunner.upsert(
|
||||
dependencies.jobRunner.upsert(
|
||||
db,
|
||||
job: Job(
|
||||
variant: .groupLeaving,
|
||||
|
@ -583,14 +597,17 @@ extension MessageSender {
|
|||
details: GroupLeavingJob.Details(
|
||||
deleteThread: deleteThread
|
||||
)
|
||||
)
|
||||
),
|
||||
canStartJob: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
public static func sendLatestEncryptionKeyPair(
|
||||
_ db: Database,
|
||||
to publicKey: String,
|
||||
for groupPublicKey: String
|
||||
for groupPublicKey: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: groupPublicKey) else {
|
||||
return SNLog("Couldn't send key pair for nonexistent closed group.")
|
||||
|
@ -626,7 +643,8 @@ extension MessageSender {
|
|||
let ciphertext = try MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
plaintext: plaintext,
|
||||
for: publicKey
|
||||
for: publicKey,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
SNLog("Sending latest encryption key pair to: \(publicKey).")
|
||||
|
@ -645,7 +663,8 @@ extension MessageSender {
|
|||
),
|
||||
interactionId: nil,
|
||||
threadId: thread.id,
|
||||
threadVariant: thread.variant
|
||||
threadVariant: thread.variant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
catch {}
|
||||
|
|
|
@ -6,18 +6,24 @@ import Sodium
|
|||
import SessionUtilitiesKit
|
||||
|
||||
extension MessageReceiver {
|
||||
internal static func decryptWithSessionProtocol(ciphertext: Data, using x25519KeyPair: KeyPair, dependencies: SMKDependencies = SMKDependencies()) throws -> (plaintext: Data, senderX25519PublicKey: String) {
|
||||
let recipientX25519PrivateKey = x25519KeyPair.secretKey
|
||||
let recipientX25519PublicKey = x25519KeyPair.publicKey
|
||||
let signatureSize = dependencies.sign.Bytes
|
||||
let ed25519PublicKeySize = dependencies.sign.PublicKeyBytes
|
||||
internal static func decryptWithSessionProtocol(
|
||||
ciphertext: Data,
|
||||
using x25519KeyPair: KeyPair,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> (plaintext: Data, senderX25519PublicKey: String) {
|
||||
let recipientX25519PrivateKey: Bytes = x25519KeyPair.secretKey
|
||||
let recipientX25519PublicKey: Bytes = x25519KeyPair.publicKey
|
||||
let signatureSize: Int = dependencies.crypto.size(.signature)
|
||||
let ed25519PublicKeySize: Int = dependencies.crypto.size(.publicKey)
|
||||
|
||||
// 1. ) Decrypt the message
|
||||
guard
|
||||
let plaintextWithMetadata = dependencies.box.open(
|
||||
anonymousCipherText: Bytes(ciphertext),
|
||||
recipientPublicKey: Box.PublicKey(Bytes(recipientX25519PublicKey)),
|
||||
recipientSecretKey: Bytes(recipientX25519PrivateKey)
|
||||
let plaintextWithMetadata = try? dependencies.crypto.perform(
|
||||
.open(
|
||||
anonymousCipherText: Bytes(ciphertext),
|
||||
recipientPublicKey: Box.PublicKey(Bytes(recipientX25519PublicKey)),
|
||||
recipientSecretKey: Bytes(recipientX25519PrivateKey)
|
||||
)
|
||||
),
|
||||
plaintextWithMetadata.count > (signatureSize + ed25519PublicKeySize)
|
||||
else {
|
||||
|
@ -32,79 +38,100 @@ extension MessageReceiver {
|
|||
// 3. ) Verify the signature
|
||||
let verificationData = plaintext + senderED25519PublicKey + recipientX25519PublicKey
|
||||
|
||||
guard dependencies.sign.verify(message: verificationData, publicKey: senderED25519PublicKey, signature: signature) else {
|
||||
throw MessageReceiverError.invalidSignature
|
||||
}
|
||||
guard
|
||||
dependencies.crypto.verify(
|
||||
.signature(message: verificationData, publicKey: senderED25519PublicKey, signature: signature)
|
||||
)
|
||||
else { throw MessageReceiverError.invalidSignature }
|
||||
|
||||
// 4. ) Get the sender's X25519 public key
|
||||
guard let senderX25519PublicKey = dependencies.sign.toX25519(ed25519PublicKey: senderED25519PublicKey) else {
|
||||
throw MessageReceiverError.decryptionFailed
|
||||
}
|
||||
guard
|
||||
let senderX25519PublicKey = try? dependencies.crypto.perform(
|
||||
.toX25519(ed25519PublicKey: senderED25519PublicKey)
|
||||
)
|
||||
else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
return (Data(plaintext), SessionId(.standard, publicKey: senderX25519PublicKey).hexString)
|
||||
}
|
||||
|
||||
internal static func decryptWithSessionBlindingProtocol(data: Data, isOutgoing: Bool, otherBlindedPublicKey: String, with openGroupPublicKey: String, userEd25519KeyPair: KeyPair, using dependencies: SMKDependencies = SMKDependencies()) throws -> (plaintext: Data, senderX25519PublicKey: String) {
|
||||
internal static func decryptWithSessionBlindingProtocol(
|
||||
data: Data,
|
||||
isOutgoing: Bool,
|
||||
otherBlindedPublicKey: String,
|
||||
with openGroupPublicKey: String,
|
||||
userEd25519KeyPair: KeyPair,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> (plaintext: Data, senderX25519PublicKey: String) {
|
||||
/// Ensure the data is at least long enough to have the required components
|
||||
guard
|
||||
data.count > (dependencies.nonceGenerator24.NonceBytes + 2),
|
||||
let blindedKeyPair = dependencies.sodium.blindedKeyPair(
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
edKeyPair: userEd25519KeyPair,
|
||||
genericHash: dependencies.genericHash
|
||||
data.count > (dependencies.crypto.size(.nonce24) + 2),
|
||||
let blindedKeyPair = dependencies.crypto.generate(
|
||||
.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, using: dependencies)
|
||||
)
|
||||
else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
/// Step one: calculate the shared encryption key, receiving from A to B
|
||||
let otherKeyBytes: Bytes = Data(hex: otherBlindedPublicKey.removingIdPrefixIfNeeded()).bytes
|
||||
let kA: Bytes = (isOutgoing ? blindedKeyPair.publicKey : otherKeyBytes)
|
||||
guard let dec_key: Bytes = dependencies.sodium.sharedBlindedEncryptionKey(
|
||||
secretKey: userEd25519KeyPair.secretKey,
|
||||
otherBlindedPublicKey: otherKeyBytes,
|
||||
fromBlindedPublicKey: kA,
|
||||
toBlindedPublicKey: (isOutgoing ? otherKeyBytes : blindedKeyPair.publicKey),
|
||||
genericHash: dependencies.genericHash
|
||||
) else {
|
||||
throw MessageReceiverError.decryptionFailed
|
||||
}
|
||||
guard
|
||||
let dec_key: Bytes = try? dependencies.crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: userEd25519KeyPair.secretKey,
|
||||
otherBlindedPublicKey: otherKeyBytes,
|
||||
fromBlindedPublicKey: kA,
|
||||
toBlindedPublicKey: (isOutgoing ? otherKeyBytes : blindedKeyPair.publicKey),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
/// v, ct, nc = data[0], data[1:-24], data[-24:]
|
||||
let version: UInt8 = data.bytes[0]
|
||||
let ciphertext: Bytes = Bytes(data.bytes[1..<(data.count - dependencies.nonceGenerator24.NonceBytes)])
|
||||
let nonce: Bytes = Bytes(data.bytes[(data.count - dependencies.nonceGenerator24.NonceBytes)..<data.count])
|
||||
let ciphertext: Bytes = Bytes(data.bytes[1..<(data.count - dependencies.crypto.size(.nonce24))])
|
||||
let nonce: Bytes = Bytes(data.bytes[(data.count - dependencies.crypto.size(.nonce24))..<data.count])
|
||||
|
||||
/// Make sure our encryption version is okay
|
||||
guard version == 0 else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
/// Decrypt
|
||||
guard let innerBytes: Bytes = dependencies.aeadXChaCha20Poly1305Ietf.decrypt(authenticatedCipherText: ciphertext, secretKey: dec_key, nonce: nonce) else {
|
||||
throw MessageReceiverError.decryptionFailed
|
||||
}
|
||||
guard
|
||||
let innerBytes: Bytes = try? dependencies.crypto.perform(
|
||||
.decryptAeadXChaCha20(
|
||||
authenticatedCipherText: ciphertext,
|
||||
secretKey: dec_key,
|
||||
nonce: nonce
|
||||
)
|
||||
)
|
||||
else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
/// Ensure the length is correct
|
||||
guard innerBytes.count > dependencies.sign.PublicKeyBytes else { throw MessageReceiverError.decryptionFailed }
|
||||
guard innerBytes.count > dependencies.crypto.size(.publicKey) else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
/// Split up: the last 32 bytes are the sender's *unblinded* ed25519 key
|
||||
let plaintext: Bytes = Bytes(innerBytes[
|
||||
0...(innerBytes.count - 1 - dependencies.sign.PublicKeyBytes)
|
||||
0...(innerBytes.count - 1 - dependencies.crypto.size(.publicKey))
|
||||
])
|
||||
let sender_edpk: Bytes = Bytes(innerBytes[
|
||||
(innerBytes.count - dependencies.sign.PublicKeyBytes)...(innerBytes.count - 1)
|
||||
(innerBytes.count - dependencies.crypto.size(.publicKey))...(innerBytes.count - 1)
|
||||
])
|
||||
|
||||
/// Verify that the inner sender_edpk (A) yields the same outer kA we got with the message
|
||||
guard let blindingFactor: Bytes = dependencies.sodium.generateBlindingFactor(serverPublicKey: openGroupPublicKey, genericHash: dependencies.genericHash) else {
|
||||
throw MessageReceiverError.invalidSignature
|
||||
}
|
||||
guard let sharedSecret: Bytes = dependencies.sodium.combineKeys(lhsKeyBytes: blindingFactor, rhsKeyBytes: sender_edpk) else {
|
||||
throw MessageReceiverError.invalidSignature
|
||||
}
|
||||
guard kA == sharedSecret else { throw MessageReceiverError.invalidSignature }
|
||||
guard
|
||||
let blindingFactor: Bytes = try? dependencies.crypto.perform(
|
||||
.generateBlindingFactor(serverPublicKey: openGroupPublicKey, using: dependencies)
|
||||
),
|
||||
let sharedSecret: Bytes = try? dependencies.crypto.perform(
|
||||
.combineKeys(lhsKeyBytes: blindingFactor, rhsKeyBytes: sender_edpk)
|
||||
),
|
||||
kA == sharedSecret
|
||||
else { throw MessageReceiverError.invalidSignature }
|
||||
|
||||
/// Get the sender's X25519 public key
|
||||
guard let senderSessionIdBytes: Bytes = dependencies.sign.toX25519(ed25519PublicKey: sender_edpk) else {
|
||||
throw MessageReceiverError.decryptionFailed
|
||||
}
|
||||
guard
|
||||
let senderSessionIdBytes: Bytes = try? dependencies.crypto.perform(
|
||||
.toX25519(ed25519PublicKey: sender_edpk)
|
||||
)
|
||||
else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
return (Data(plaintext), SessionId(.standard, publicKey: senderSessionIdBytes).hexString)
|
||||
}
|
||||
|
|
|
@ -18,9 +18,9 @@ public enum MessageReceiver {
|
|||
openGroupServerPublicKey: String?,
|
||||
isOutgoing: Bool? = nil,
|
||||
otherBlindedPublicKey: String? = nil,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> (Message, SNProtoContent, String, SessionThread.Variant) {
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let isOpenGroupMessage: Bool = (openGroupId != nil)
|
||||
|
||||
// Decrypt the contents
|
||||
|
@ -183,7 +183,7 @@ public enum MessageReceiver {
|
|||
message: Message,
|
||||
serverExpirationTimestamp: TimeInterval?,
|
||||
associatedWithProto proto: SNProtoContent,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
// Check if the message requires an existing conversation (if it does and the conversation isn't in
|
||||
// the config then the message will be dropped)
|
||||
|
@ -198,7 +198,7 @@ public enum MessageReceiver {
|
|||
message: message,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
switch message {
|
||||
|
@ -222,7 +222,8 @@ public enum MessageReceiver {
|
|||
db,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant,
|
||||
message: message
|
||||
message: message,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case let message as DataExtractionNotification:
|
||||
|
@ -242,7 +243,11 @@ public enum MessageReceiver {
|
|||
)
|
||||
|
||||
case let message as ConfigurationMessage:
|
||||
try MessageReceiver.handleLegacyConfigurationMessage(db, message: message)
|
||||
try MessageReceiver.handleLegacyConfigurationMessage(
|
||||
db,
|
||||
message: message,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case let message as UnsendRequest:
|
||||
try MessageReceiver.handleUnsendRequest(
|
||||
|
@ -264,7 +269,7 @@ public enum MessageReceiver {
|
|||
try MessageReceiver.handleMessageRequestResponse(
|
||||
db,
|
||||
message: message,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case let message as VisibleMessage:
|
||||
|
@ -360,7 +365,7 @@ public enum MessageReceiver {
|
|||
message: Message,
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
switch message {
|
||||
case is ReadReceipt: return // No visible artifact created so better to keep for more reliable read states
|
||||
|
@ -369,7 +374,7 @@ public enum MessageReceiver {
|
|||
}
|
||||
|
||||
// Determine the state of the conversation and the validity of the message
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let conversationVisibleInConfig: Bool = SessionUtil.conversationInConfig(
|
||||
db,
|
||||
threadId: threadId,
|
||||
|
|
|
@ -14,7 +14,8 @@ extension MessageSender {
|
|||
interaction: Interaction,
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
isSyncMessage: Bool = false
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
// Only 'VisibleMessage' types can be sent via this method
|
||||
guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage }
|
||||
|
@ -26,7 +27,8 @@ extension MessageSender {
|
|||
threadId: threadId,
|
||||
interactionId: interactionId,
|
||||
to: try Message.Destination.from(db, threadId: threadId, threadVariant: threadVariant),
|
||||
isSyncMessage: isSyncMessage
|
||||
isSyncMessage: isSyncMessage,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -36,7 +38,8 @@ extension MessageSender {
|
|||
interactionId: Int64?,
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
isSyncMessage: Bool = false
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
send(
|
||||
db,
|
||||
|
@ -44,7 +47,8 @@ extension MessageSender {
|
|||
threadId: threadId,
|
||||
interactionId: interactionId,
|
||||
to: try Message.Destination.from(db, threadId: threadId, threadVariant: threadVariant),
|
||||
isSyncMessage: isSyncMessage
|
||||
isSyncMessage: isSyncMessage,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -54,7 +58,8 @@ extension MessageSender {
|
|||
threadId: String?,
|
||||
interactionId: Int64?,
|
||||
to destination: Message.Destination,
|
||||
isSyncMessage: Bool = false
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// If it's a sync message then we need to make some slight tweaks before sending so use the proper
|
||||
// sync message sending process instead of the standard process
|
||||
|
@ -65,12 +70,13 @@ extension MessageSender {
|
|||
destination: destination,
|
||||
threadId: threadId,
|
||||
interactionId: interactionId,
|
||||
isAlreadySyncMessage: false
|
||||
isAlreadySyncMessage: false,
|
||||
using: dependencies
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
JobRunner.add(
|
||||
dependencies.jobRunner.add(
|
||||
db,
|
||||
job: Job(
|
||||
variant: .messageSend,
|
||||
|
@ -81,7 +87,9 @@ extension MessageSender {
|
|||
message: message,
|
||||
isSyncMessage: isSyncMessage
|
||||
)
|
||||
)
|
||||
),
|
||||
canStartJob: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -91,7 +99,8 @@ extension MessageSender {
|
|||
_ db: Database,
|
||||
interaction: Interaction,
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant
|
||||
threadVariant: SessionThread.Variant,
|
||||
using dependencies: Dependencies
|
||||
) throws -> PreparedSendData {
|
||||
// Only 'VisibleMessage' types can be sent via this method
|
||||
guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage }
|
||||
|
@ -104,11 +113,15 @@ extension MessageSender {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: threadId, threadVariant: threadVariant)
|
||||
.defaultNamespace,
|
||||
interactionId: interactionId
|
||||
interactionId: interactionId,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
public static func performUploadsIfNeeded(preparedSendData: PreparedSendData) -> AnyPublisher<PreparedSendData, Error> {
|
||||
public static func performUploadsIfNeeded(
|
||||
preparedSendData: PreparedSendData,
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<PreparedSendData, Error> {
|
||||
// We need an interactionId in order for a message to have uploads
|
||||
guard let interactionId: Int64 = preparedSendData.interactionId else {
|
||||
return Just(preparedSendData)
|
||||
|
@ -127,7 +140,7 @@ extension MessageSender {
|
|||
}
|
||||
}()
|
||||
|
||||
return Storage.shared
|
||||
return dependencies.storage
|
||||
.readPublisher { db -> (attachments: [Attachment], openGroup: OpenGroup?) in
|
||||
let attachmentStateInfo: [Attachment.StateInfo] = (try? Attachment
|
||||
.stateInfo(interactionId: interactionId, state: .uploading)
|
||||
|
@ -162,7 +175,8 @@ extension MessageSender {
|
|||
to: (
|
||||
openGroup.map { Attachment.Destination.openGroup($0) } ??
|
||||
.fileServer
|
||||
)
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
@ -10,7 +10,7 @@ extension MessageSender {
|
|||
_ db: Database,
|
||||
plaintext: Data,
|
||||
for recipientHexEncodedX25519PublicKey: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> Data {
|
||||
guard let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else {
|
||||
throw MessageSenderError.noUserED25519KeyPair
|
||||
|
@ -19,14 +19,21 @@ extension MessageSender {
|
|||
let recipientX25519PublicKey = Data(hex: recipientHexEncodedX25519PublicKey.removingIdPrefixIfNeeded())
|
||||
|
||||
let verificationData = plaintext + Data(userEd25519KeyPair.publicKey) + recipientX25519PublicKey
|
||||
guard let signature = dependencies.sign.signature(message: Bytes(verificationData), secretKey: userEd25519KeyPair.secretKey) else {
|
||||
throw MessageSenderError.signingFailed
|
||||
}
|
||||
guard
|
||||
let signature = try? dependencies.crypto.perform(
|
||||
.signature(message: Bytes(verificationData), secretKey: userEd25519KeyPair.secretKey)
|
||||
)
|
||||
else { throw MessageSenderError.signingFailed }
|
||||
|
||||
let plaintextWithMetadata = plaintext + Data(userEd25519KeyPair.publicKey) + Data(signature)
|
||||
guard let ciphertext = dependencies.box.seal(message: Bytes(plaintextWithMetadata), recipientPublicKey: Bytes(recipientX25519PublicKey)) else {
|
||||
throw MessageSenderError.encryptionFailed
|
||||
}
|
||||
guard
|
||||
let ciphertext = try? dependencies.crypto.perform(
|
||||
.seal(
|
||||
message: Bytes(plaintextWithMetadata),
|
||||
recipientPublicKey: Bytes(recipientX25519PublicKey)
|
||||
)
|
||||
)
|
||||
else { throw MessageSenderError.encryptionFailed }
|
||||
|
||||
return Data(ciphertext)
|
||||
}
|
||||
|
@ -36,7 +43,7 @@ extension MessageSender {
|
|||
plaintext: Data,
|
||||
for recipientBlindedId: String,
|
||||
openGroupPublicKey: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> Data {
|
||||
guard
|
||||
SessionId.Prefix(from: recipientBlindedId) == .blinded15 ||
|
||||
|
@ -45,32 +52,37 @@ extension MessageSender {
|
|||
guard let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else {
|
||||
throw MessageSenderError.noUserED25519KeyPair
|
||||
}
|
||||
guard let blindedKeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, genericHash: dependencies.genericHash) else {
|
||||
throw MessageSenderError.signingFailed
|
||||
}
|
||||
guard
|
||||
let blindedKeyPair = dependencies.crypto.generate(
|
||||
.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, using: dependencies)
|
||||
)
|
||||
else { throw MessageSenderError.signingFailed }
|
||||
|
||||
let recipientBlindedPublicKey = Data(hex: recipientBlindedId.removingIdPrefixIfNeeded())
|
||||
|
||||
/// Step one: calculate the shared encryption key, sending from A to B
|
||||
guard let enc_key: Bytes = dependencies.sodium.sharedBlindedEncryptionKey(
|
||||
secretKey: userEd25519KeyPair.secretKey,
|
||||
otherBlindedPublicKey: recipientBlindedPublicKey.bytes,
|
||||
fromBlindedPublicKey: blindedKeyPair.publicKey,
|
||||
toBlindedPublicKey: recipientBlindedPublicKey.bytes,
|
||||
genericHash: dependencies.genericHash
|
||||
) else {
|
||||
throw MessageSenderError.signingFailed
|
||||
}
|
||||
guard
|
||||
let enc_key: Bytes = try? dependencies.crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: userEd25519KeyPair.secretKey,
|
||||
otherBlindedPublicKey: recipientBlindedPublicKey.bytes,
|
||||
fromBlindedPublicKey: blindedKeyPair.publicKey,
|
||||
toBlindedPublicKey: recipientBlindedPublicKey.bytes,
|
||||
using: dependencies
|
||||
)
|
||||
),
|
||||
let nonce: Bytes = try? dependencies.crypto.perform(.generateNonce24())
|
||||
else { throw MessageSenderError.signingFailed }
|
||||
|
||||
/// Inner data: msg || A (i.e. the sender's ed25519 master pubkey, *not* kA blinded pubkey)
|
||||
let innerBytes: Bytes = (plaintext.bytes + userEd25519KeyPair.publicKey)
|
||||
|
||||
/// Encrypt using xchacha20-poly1305
|
||||
let nonce: Bytes = dependencies.nonceGenerator24.nonce()
|
||||
|
||||
guard let ciphertext = dependencies.aeadXChaCha20Poly1305Ietf.encrypt(message: innerBytes, secretKey: enc_key, nonce: nonce) else {
|
||||
throw MessageSenderError.encryptionFailed
|
||||
}
|
||||
guard
|
||||
let ciphertext = try? dependencies.crypto.perform(
|
||||
.encryptAeadXChaCha20(message: innerBytes, secretKey: enc_key, nonce: nonce, using: dependencies)
|
||||
)
|
||||
else { throw MessageSenderError.encryptionFailed }
|
||||
|
||||
/// data = b'\x00' + ciphertext + nonce
|
||||
return Data(Bytes(arrayLiteral: 0) + ciphertext + nonce)
|
||||
|
|
|
@ -140,10 +140,10 @@ public final class MessageSender {
|
|||
namespace: SnodeAPI.Namespace?,
|
||||
interactionId: Int64?,
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData {
|
||||
// Common logic for all destinations
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let messageSendTimestamp: Int64 = SnodeAPI.currentOffsetTimestampMs()
|
||||
let updatedMessage: Message = message
|
||||
|
||||
|
@ -199,7 +199,7 @@ public final class MessageSender {
|
|||
userPublicKey: String,
|
||||
messageSendTimestamp: Int64,
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> PreparedSendData {
|
||||
message.sender = userPublicKey
|
||||
message.recipient = {
|
||||
|
@ -276,7 +276,7 @@ public final class MessageSender {
|
|||
do {
|
||||
switch destination {
|
||||
case .contact(let publicKey):
|
||||
ciphertext = try encryptWithSessionProtocol(db, plaintext: plaintext, for: publicKey)
|
||||
ciphertext = try encryptWithSessionProtocol(db, plaintext: plaintext, for: publicKey, using: dependencies)
|
||||
|
||||
case .closedGroup(let groupPublicKey):
|
||||
guard let encryptionKeyPair: ClosedGroupKeyPair = try? ClosedGroupKeyPair.fetchLatestKeyPair(db, threadId: groupPublicKey) else {
|
||||
|
@ -286,7 +286,8 @@ public final class MessageSender {
|
|||
ciphertext = try encryptWithSessionProtocol(
|
||||
db,
|
||||
plaintext: plaintext,
|
||||
for: SessionId(.standard, publicKey: encryptionKeyPair.publicKey.bytes).hexString
|
||||
for: SessionId(.standard, publicKey: encryptionKeyPair.publicKey.bytes).hexString,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case .openGroup, .openGroupInbox: preconditionFailure()
|
||||
|
@ -365,7 +366,7 @@ public final class MessageSender {
|
|||
to destination: Message.Destination,
|
||||
interactionId: Int64?,
|
||||
messageSendTimestamp: Int64,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> PreparedSendData {
|
||||
let threadId: String
|
||||
|
||||
|
@ -394,7 +395,7 @@ public final class MessageSender {
|
|||
throw MessageSenderError.invalidMessage
|
||||
}
|
||||
|
||||
message.sender = {
|
||||
message.sender = try {
|
||||
let capabilities: [Capability.Variant] = (try? Capability
|
||||
.select(.variant)
|
||||
.filter(Capability.Columns.openGroupServer == server)
|
||||
|
@ -407,9 +408,11 @@ public final class MessageSender {
|
|||
guard capabilities.isEmpty || capabilities.contains(.blind) else {
|
||||
return SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString
|
||||
}
|
||||
guard let blindedKeyPair: KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroup.publicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else {
|
||||
preconditionFailure()
|
||||
}
|
||||
guard
|
||||
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
|
||||
.blindedKeyPair(serverPublicKey: openGroup.publicKey, edKeyPair: userEdKeyPair, using: dependencies)
|
||||
)
|
||||
else { throw MessageSenderError.signingFailed }
|
||||
|
||||
return SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString
|
||||
}()
|
||||
|
@ -492,7 +495,7 @@ public final class MessageSender {
|
|||
interactionId: Int64?,
|
||||
userPublicKey: String,
|
||||
messageSendTimestamp: Int64,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> PreparedSendData {
|
||||
guard case .openGroupInbox(_, let openGroupPublicKey, let recipientBlindedPublicKey) = destination else {
|
||||
throw MessageSenderError.invalidMessage
|
||||
|
@ -582,10 +585,10 @@ public final class MessageSender {
|
|||
// MARK: - Sending
|
||||
|
||||
public static func sendImmediate(
|
||||
preparedSendData: PreparedSendData,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
data: PreparedSendData,
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard preparedSendData.shouldSend else {
|
||||
guard data.shouldSend else {
|
||||
return Just(())
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -597,7 +600,7 @@ public final class MessageSender {
|
|||
//
|
||||
// If you see this error then you need to call
|
||||
// `MessageSender.performUploadsIfNeeded(queue:preparedSendData:)` before calling this function
|
||||
switch preparedSendData.message {
|
||||
switch data.message {
|
||||
case let visibleMessage as VisibleMessage:
|
||||
let expectedAttachmentUploadCount: Int = (
|
||||
visibleMessage.attachmentIds.count +
|
||||
|
@ -605,17 +608,17 @@ public final class MessageSender {
|
|||
(visibleMessage.quote?.attachmentId != nil ? 1 : 0)
|
||||
)
|
||||
|
||||
guard expectedAttachmentUploadCount == preparedSendData.totalAttachmentsUploaded else {
|
||||
guard expectedAttachmentUploadCount == data.totalAttachmentsUploaded else {
|
||||
// Make sure to actually handle this as a failure (if we don't then the message
|
||||
// won't go into an error state correctly)
|
||||
if let message: Message = preparedSendData.message {
|
||||
if let message: Message = data.message {
|
||||
dependencies.storage.read { db in
|
||||
MessageSender.handleFailedMessageSend(
|
||||
db,
|
||||
message: message,
|
||||
with: .attachmentsNotUploaded,
|
||||
interactionId: preparedSendData.interactionId,
|
||||
isSyncMessage: (preparedSendData.isSyncMessage == true),
|
||||
interactionId: data.interactionId,
|
||||
isSyncMessage: (data.isSyncMessage == true),
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
@ -630,10 +633,10 @@ public final class MessageSender {
|
|||
default: break
|
||||
}
|
||||
|
||||
switch preparedSendData.destination {
|
||||
case .contact, .closedGroup: return sendToSnodeDestination(data: preparedSendData, using: dependencies)
|
||||
case .openGroup: return sendToOpenGroupDestination(data: preparedSendData, using: dependencies)
|
||||
case .openGroupInbox: return sendToOpenGroupInbox(data: preparedSendData, using: dependencies)
|
||||
switch data.destination {
|
||||
case .contact, .closedGroup: return sendToSnodeDestination(data: data, using: dependencies)
|
||||
case .openGroup: return sendToOpenGroupDestination(data: data, using: dependencies)
|
||||
case .openGroupInbox: return sendToOpenGroupInbox(data: data, using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -641,7 +644,7 @@ public final class MessageSender {
|
|||
|
||||
private static func sendToSnodeDestination(
|
||||
data: PreparedSendData,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard
|
||||
let message: Message = data.message,
|
||||
|
@ -653,14 +656,11 @@ public final class MessageSender {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return SnodeAPI
|
||||
.sendMessage(
|
||||
snodeMessage,
|
||||
in: namespace
|
||||
)
|
||||
.flatMap { response -> AnyPublisher<Void, Error> in
|
||||
return dependencies.network
|
||||
.send(.message(snodeMessage, in: namespace, using: dependencies))
|
||||
.flatMap { info, response -> AnyPublisher<Void, Error> in
|
||||
let updatedMessage: Message = message
|
||||
updatedMessage.serverHash = response.1.hash
|
||||
updatedMessage.serverHash = response.hash
|
||||
|
||||
let job: Job? = Job(
|
||||
variant: .notifyPushServer,
|
||||
|
@ -695,7 +695,7 @@ public final class MessageSender {
|
|||
|
||||
guard shouldNotify else { return () }
|
||||
|
||||
JobRunner.add(db, job: job)
|
||||
dependencies.jobRunner.add(db, job: job, canStartJob: true, using: dependencies)
|
||||
return ()
|
||||
}
|
||||
.flatMap { _ -> AnyPublisher<Void, Error> in
|
||||
|
@ -726,7 +726,8 @@ public final class MessageSender {
|
|||
deferred: { _, _ in
|
||||
// Always fulfill because the notify PN server job isn't critical.
|
||||
resolver(Result.success(()))
|
||||
}
|
||||
},
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -761,7 +762,7 @@ public final class MessageSender {
|
|||
|
||||
private static func sendToOpenGroupDestination(
|
||||
data: PreparedSendData,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard
|
||||
let message: Message = data.message,
|
||||
|
@ -829,7 +830,7 @@ public final class MessageSender {
|
|||
|
||||
private static func sendToOpenGroupInbox(
|
||||
data: PreparedSendData,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard
|
||||
let message: Message = data.message,
|
||||
|
@ -926,7 +927,7 @@ public final class MessageSender {
|
|||
interactionId: Int64?,
|
||||
serverTimestampMs: UInt64? = nil,
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
// If the message was a reaction then we want to update the reaction instead of the original
|
||||
// interaction (which the 'interactionId' is pointing to
|
||||
|
@ -964,13 +965,15 @@ public final class MessageSender {
|
|||
.updateAll(db, RecipientState.Columns.state.set(to: RecipientState.State.sent))
|
||||
|
||||
// Start the disappearing messages timer if needed
|
||||
JobRunner.upsert(
|
||||
dependencies.jobRunner.upsert(
|
||||
db,
|
||||
job: DisappearingMessagesJob.updateNextRunIfNeeded(
|
||||
db,
|
||||
interaction: interaction,
|
||||
startedAtMs: TimeInterval(SnodeAPI.currentOffsetTimestampMs())
|
||||
)
|
||||
),
|
||||
canStartJob: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -995,7 +998,8 @@ public final class MessageSender {
|
|||
destination: destination,
|
||||
threadId: threadId,
|
||||
interactionId: interactionId,
|
||||
isAlreadySyncMessage: isSyncMessage
|
||||
isAlreadySyncMessage: isSyncMessage,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1005,7 +1009,7 @@ public final class MessageSender {
|
|||
with error: MessageSenderError,
|
||||
interactionId: Int64?,
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> Error {
|
||||
// If the message was a reaction then we don't want to do anything to the original
|
||||
// interaciton (which the 'interactionId' is pointing to
|
||||
|
@ -1072,11 +1076,12 @@ public final class MessageSender {
|
|||
destination: Message.Destination,
|
||||
threadId: String?,
|
||||
interactionId: Int64?,
|
||||
isAlreadySyncMessage: Bool
|
||||
isAlreadySyncMessage: Bool,
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// Sync the message if it's not a sync message, wasn't already sent to the current user and
|
||||
// it's a message type which should be synced
|
||||
let currentUserPublicKey = getUserHexEncodedPublicKey(db)
|
||||
let currentUserPublicKey = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
|
||||
if
|
||||
case .contact(let publicKey) = destination,
|
||||
|
@ -1087,7 +1092,7 @@ public final class MessageSender {
|
|||
if let message = message as? VisibleMessage { message.syncTarget = publicKey }
|
||||
if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey }
|
||||
|
||||
JobRunner.add(
|
||||
dependencies.jobRunner.add(
|
||||
db,
|
||||
job: Job(
|
||||
variant: .messageSend,
|
||||
|
@ -1098,7 +1103,9 @@ public final class MessageSender {
|
|||
message: message,
|
||||
isSyncMessage: true
|
||||
)
|
||||
)
|
||||
),
|
||||
canStartJob: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,23 +23,23 @@ public final class ClosedGroupPoller: Poller {
|
|||
|
||||
// MARK: - Public API
|
||||
|
||||
public func start() {
|
||||
public func start(using dependencies: Dependencies = Dependencies()) {
|
||||
// Fetch all closed groups (excluding any don't contain the current user as a
|
||||
// GroupMemeber as the user is no longer a member of those)
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.read { db in
|
||||
try ClosedGroup
|
||||
.select(.threadId)
|
||||
.joining(
|
||||
required: ClosedGroup.members
|
||||
.filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db))
|
||||
.filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db, using: dependencies))
|
||||
)
|
||||
.asRequest(of: String.self)
|
||||
.fetchAll(db)
|
||||
}
|
||||
.defaulting(to: [])
|
||||
.forEach { [weak self] publicKey in
|
||||
self?.startIfNeeded(for: publicKey)
|
||||
self?.startIfNeeded(for: publicKey, using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ public final class ClosedGroupPoller: Poller {
|
|||
return "closed group with public key: \(publicKey)"
|
||||
}
|
||||
|
||||
override func nextPollDelay(for publicKey: String) -> TimeInterval {
|
||||
override func nextPollDelay(for publicKey: String, using dependencies: Dependencies) -> TimeInterval {
|
||||
// Get the received date of the last message in the thread. If we don't have
|
||||
// any messages yet, pick some reasonable fake time interval to use instead
|
||||
let lastMessageDate: Date = Storage.shared
|
||||
|
@ -68,7 +68,7 @@ public final class ClosedGroupPoller: Poller {
|
|||
}
|
||||
.defaulting(to: Date().addingTimeInterval(-5 * 60))
|
||||
|
||||
let timeSinceLastMessage: TimeInterval = Date().timeIntervalSince(lastMessageDate)
|
||||
let timeSinceLastMessage: TimeInterval = dependencies.dateNow.timeIntervalSince(lastMessageDate)
|
||||
let minPollInterval: Double = ClosedGroupPoller.minPollInterval
|
||||
let limit: Double = (12 * 60 * 60)
|
||||
let a: TimeInterval = ((ClosedGroupPoller.maxPollInterval - minPollInterval) / limit)
|
||||
|
@ -78,11 +78,7 @@ public final class ClosedGroupPoller: Poller {
|
|||
return nextPollInterval
|
||||
}
|
||||
|
||||
override func handlePollError(
|
||||
_ error: Error,
|
||||
for publicKey: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
) -> Bool {
|
||||
override func handlePollError(_ error: Error, for publicKey: String, using dependencies: Dependencies) -> Bool {
|
||||
SNLog("Polling failed for closed group with public key: \(publicKey) due to error: \(error).")
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -33,13 +33,13 @@ public final class CurrentUserPoller: Poller {
|
|||
|
||||
// MARK: - Convenience Functions
|
||||
|
||||
public func start() {
|
||||
let publicKey: String = getUserHexEncodedPublicKey()
|
||||
public func start(using dependencies: Dependencies = Dependencies()) {
|
||||
let publicKey: String = getUserHexEncodedPublicKey(using: dependencies)
|
||||
|
||||
guard isPolling.wrappedValue[publicKey] != true else { return }
|
||||
|
||||
SNLog("Started polling.")
|
||||
super.startIfNeeded(for: publicKey)
|
||||
super.startIfNeeded(for: publicKey, using: dependencies)
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
|
@ -53,7 +53,7 @@ public final class CurrentUserPoller: Poller {
|
|||
return "Main Poller"
|
||||
}
|
||||
|
||||
override func nextPollDelay(for publicKey: String) -> TimeInterval {
|
||||
override func nextPollDelay(for publicKey: String, using dependencies: Dependencies) -> TimeInterval {
|
||||
let failureCount: TimeInterval = TimeInterval(failureCount.wrappedValue[publicKey] ?? 0)
|
||||
|
||||
// If there have been no failures then just use the 'minPollInterval'
|
||||
|
@ -65,11 +65,7 @@ public final class CurrentUserPoller: Poller {
|
|||
return min(maxRetryInterval, nextDelay)
|
||||
}
|
||||
|
||||
override func handlePollError(
|
||||
_ error: Error,
|
||||
for publicKey: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
) -> Bool {
|
||||
override func handlePollError(_ error: Error, for publicKey: String, using dependencies: Dependencies) -> Bool {
|
||||
if UserDefaults.sharedLokiProject?[.isMainAppActive] != true {
|
||||
// Do nothing when an error gets throws right after returning from the background (happens frequently)
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ extension OpenGroupAPI {
|
|||
self.server = server
|
||||
}
|
||||
|
||||
public func startIfNeeded(using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()) {
|
||||
public func startIfNeeded(using dependencies: Dependencies) {
|
||||
guard !hasStarted else { return }
|
||||
|
||||
hasStarted = true
|
||||
|
@ -49,20 +49,15 @@ extension OpenGroupAPI {
|
|||
|
||||
// MARK: - Polling
|
||||
|
||||
private func pollRecursively(
|
||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies(
|
||||
subscribeQueue: Threading.pollerQueue,
|
||||
receiveQueue: OpenGroupAPI.workQueue
|
||||
)
|
||||
) {
|
||||
private func pollRecursively(using dependencies: Dependencies) {
|
||||
guard hasStarted else { return }
|
||||
|
||||
let server: String = self.server
|
||||
let lastPollStart: TimeInterval = Date().timeIntervalSince1970
|
||||
let lastPollStart: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||
|
||||
poll(using: dependencies)
|
||||
.subscribe(on: dependencies.subscribeQueue)
|
||||
.receive(on: dependencies.receiveQueue)
|
||||
.subscribe(on: Threading.pollerQueue, using: dependencies)
|
||||
.receive(on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { [weak self] _ in
|
||||
let minPollFailureCount: Int64 = dependencies.storage
|
||||
|
@ -76,7 +71,7 @@ extension OpenGroupAPI {
|
|||
.defaulting(to: 0)
|
||||
|
||||
// Calculate the remaining poll delay
|
||||
let currentTime: TimeInterval = Date().timeIntervalSince1970
|
||||
let currentTime: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||
let nextPollInterval: TimeInterval = Poller.getInterval(
|
||||
for: TimeInterval(minPollFailureCount),
|
||||
minInterval: Poller.minPollInterval,
|
||||
|
@ -86,12 +81,12 @@ extension OpenGroupAPI {
|
|||
|
||||
// Schedule the next poll
|
||||
guard remainingInterval > 0 else {
|
||||
return dependencies.subscribeQueue.async {
|
||||
return Threading.pollerQueue.async(using: dependencies) {
|
||||
self?.pollRecursively(using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies.subscribeQueue.asyncAfter(deadline: .now() + .milliseconds(Int(remainingInterval * 1000)), qos: .default) {
|
||||
Threading.pollerQueue.asyncAfter(deadline: .now() + .milliseconds(Int(remainingInterval * 1000)), qos: .default, using: dependencies) {
|
||||
self?.pollRecursively(using: dependencies)
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +94,7 @@ extension OpenGroupAPI {
|
|||
}
|
||||
|
||||
public func poll(
|
||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
return poll(
|
||||
calledFromBackgroundPoller: false,
|
||||
|
@ -112,7 +107,7 @@ extension OpenGroupAPI {
|
|||
calledFromBackgroundPoller: Bool,
|
||||
isBackgroundPollerValid: @escaping (() -> Bool) = { true },
|
||||
isPostCapabilitiesRetry: Bool,
|
||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard !self.isPolling else {
|
||||
return Just(())
|
||||
|
@ -122,10 +117,12 @@ extension OpenGroupAPI {
|
|||
|
||||
self.isPolling = true
|
||||
let server: String = self.server
|
||||
let hasPerformedInitialPoll: Bool = (dependencies.cache.hasPerformedInitialPoll[server] == true)
|
||||
let hasPerformedInitialPoll: Bool = (dependencies.caches[.openGroupManager].hasPerformedInitialPoll[server] == true)
|
||||
let timeSinceLastPoll: TimeInterval = (
|
||||
dependencies.cache.timeSinceLastPoll[server] ??
|
||||
dependencies.mutableCache.mutate { $0.getTimeSinceLastOpen(using: dependencies) }
|
||||
dependencies.caches[.openGroupManager].timeSinceLastPoll[server] ??
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||
cache.getTimeSinceLastOpen(using: dependencies)
|
||||
}
|
||||
)
|
||||
|
||||
return dependencies.storage
|
||||
|
@ -170,10 +167,11 @@ extension OpenGroupAPI {
|
|||
using: dependencies
|
||||
)
|
||||
|
||||
dependencies.mutableCache.mutate { cache in
|
||||
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||
cache.hasPerformedInitialPoll[server] = true
|
||||
cache.timeSinceLastPoll[server] = Date().timeIntervalSince1970
|
||||
UserDefaults.standard[.lastOpen] = Date()
|
||||
cache.timeSinceLastPoll[server] = dependencies.dateNow.timeIntervalSince1970
|
||||
dependencies.standardUserDefaults[.lastOpen] = dependencies.dateNow
|
||||
}
|
||||
|
||||
SNLog("Open group polling finished for \(server).")
|
||||
|
@ -303,7 +301,7 @@ extension OpenGroupAPI {
|
|||
isBackgroundPollerValid: @escaping (() -> Bool) = { true },
|
||||
isPostCapabilitiesRetry: Bool,
|
||||
error: Error,
|
||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Bool, Error> {
|
||||
/// We want to custom handle a '400' error code due to not having blinded auth as it likely means that we join the
|
||||
/// OpenGroup before blinding was enabled and need to update it's capabilities
|
||||
|
@ -376,7 +374,7 @@ extension OpenGroupAPI {
|
|||
info: ResponseInfoType,
|
||||
response: BatchResponse,
|
||||
failureCount: Int64,
|
||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
let server: String = self.server
|
||||
let validResponses: [OpenGroupAPI.Endpoint: Decodable] = response.data
|
||||
|
@ -550,7 +548,7 @@ extension OpenGroupAPI {
|
|||
publicKey: nil,
|
||||
for: roomToken,
|
||||
on: server,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _):
|
||||
|
@ -564,7 +562,7 @@ extension OpenGroupAPI {
|
|||
messages: responseBody.compactMap { $0.value },
|
||||
for: roomToken,
|
||||
on: server,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case .inbox, .inboxSince, .outbox, .outboxSince:
|
||||
|
@ -587,7 +585,7 @@ extension OpenGroupAPI {
|
|||
messages: messages,
|
||||
fromOutbox: fromOutbox,
|
||||
on: server,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
default: break // No custom handling needed
|
||||
|
|
|
@ -53,18 +53,18 @@ public class Poller {
|
|||
}
|
||||
|
||||
/// Calculate the delay which should occur before the next poll
|
||||
internal func nextPollDelay(for publicKey: String) -> TimeInterval {
|
||||
internal func nextPollDelay(for publicKey: String, using dependencies: Dependencies) -> TimeInterval {
|
||||
preconditionFailure("abstract class - override in subclass")
|
||||
}
|
||||
|
||||
/// Perform and logic which should occur when the poll errors, will stop polling if `false` is returned
|
||||
internal func handlePollError(_ error: Error, for publicKey: String, using dependencies: SMKDependencies) -> Bool {
|
||||
internal func handlePollError(_ error: Error, for publicKey: String, using dependencies: Dependencies) -> Bool {
|
||||
preconditionFailure("abstract class - override in subclass")
|
||||
}
|
||||
|
||||
// MARK: - Private API
|
||||
|
||||
internal func startIfNeeded(for publicKey: String) {
|
||||
internal func startIfNeeded(for publicKey: String, using dependencies: Dependencies) {
|
||||
// Run on the 'pollerQueue' to ensure any 'Atomic' access doesn't block the main thread
|
||||
// on startup
|
||||
Threading.pollerQueue.async { [weak self] in
|
||||
|
@ -74,13 +74,13 @@ public class Poller {
|
|||
// and the timer is not created, if we mark the group as is polling
|
||||
// after setUpPolling. So the poller may not work, thus misses messages
|
||||
self?.isPolling.mutate { $0[publicKey] = true }
|
||||
self?.pollRecursively(for: publicKey)
|
||||
self?.pollRecursively(for: publicKey, using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
internal func getSnodeForPolling(
|
||||
for publicKey: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Snode, Error> {
|
||||
// If we don't want to poll a snode multiple times then just grab a random one from the swarm
|
||||
guard maxNodePollCount > 0 else {
|
||||
|
@ -135,14 +135,14 @@ public class Poller {
|
|||
|
||||
private func pollRecursively(
|
||||
for publicKey: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard isPolling.wrappedValue[publicKey] == true else { return }
|
||||
|
||||
let namespaces: [SnodeAPI.Namespace] = self.namespaces
|
||||
let lastPollStart: TimeInterval = Date().timeIntervalSince1970
|
||||
let lastPollInterval: TimeInterval = nextPollDelay(for: publicKey)
|
||||
let getSnodePublisher: AnyPublisher<Snode, Error> = getSnodeForPolling(for: publicKey)
|
||||
let lastPollStart: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||
let lastPollInterval: TimeInterval = nextPollDelay(for: publicKey, using: dependencies)
|
||||
let getSnodePublisher: AnyPublisher<Snode, Error> = getSnodeForPolling(for: publicKey, using: dependencies)
|
||||
|
||||
// Store the publisher intp the cancellables dictionary
|
||||
cancellables.mutate { [weak self] cancellables in
|
||||
|
@ -156,8 +156,8 @@ public class Poller {
|
|||
using: dependencies
|
||||
)
|
||||
}
|
||||
.subscribe(on: dependencies.subscribeQueue)
|
||||
.receive(on: dependencies.receiveQueue)
|
||||
.subscribe(on: Threading.pollerQueue, using: dependencies)
|
||||
.receive(on: Threading.pollerQueue, using: dependencies)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
|
@ -174,21 +174,21 @@ public class Poller {
|
|||
self?.incrementPollCount(publicKey: publicKey)
|
||||
|
||||
// Calculate the remaining poll delay
|
||||
let currentTime: TimeInterval = Date().timeIntervalSince1970
|
||||
let currentTime: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||
let nextPollInterval: TimeInterval = (
|
||||
self?.nextPollDelay(for: publicKey) ??
|
||||
self?.nextPollDelay(for: publicKey, using: dependencies) ??
|
||||
lastPollInterval
|
||||
)
|
||||
let remainingInterval: TimeInterval = max(0, nextPollInterval - (currentTime - lastPollStart))
|
||||
|
||||
// Schedule the next poll
|
||||
guard remainingInterval > 0 else {
|
||||
return dependencies.subscribeQueue.async {
|
||||
return Threading.pollerQueue.async(using: dependencies) {
|
||||
self?.pollRecursively(for: publicKey, using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies.subscribeQueue.asyncAfter(deadline: .now() + .milliseconds(Int(remainingInterval * 1000)), qos: .default) {
|
||||
Threading.pollerQueue.asyncAfter(deadline: .now() + .milliseconds(Int(remainingInterval * 1000)), qos: .default, using: dependencies) {
|
||||
self?.pollRecursively(for: publicKey, using: dependencies)
|
||||
}
|
||||
},
|
||||
|
@ -209,10 +209,7 @@ public class Poller {
|
|||
calledFromBackgroundPoller: Bool = false,
|
||||
isBackgroundPollValid: @escaping (() -> Bool) = { true },
|
||||
poller: Poller? = nil,
|
||||
using dependencies: SMKDependencies = SMKDependencies(
|
||||
subscribeQueue: Threading.pollerQueue,
|
||||
receiveQueue: Threading.pollerQueue
|
||||
)
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<[Message], Error> {
|
||||
// If the polling has been cancelled then don't continue
|
||||
guard
|
||||
|
@ -276,7 +273,7 @@ public class Poller {
|
|||
var standardMessageJobsToRun: [Job] = []
|
||||
var pollerLogOutput: String = "\(pollerName) failed to process any messages"
|
||||
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
let allProcessedMessages: [ProcessedMessage] = allMessages
|
||||
.compactMap { message -> ProcessedMessage? in
|
||||
do {
|
||||
|
@ -338,7 +335,7 @@ public class Poller {
|
|||
db,
|
||||
job: jobToRun,
|
||||
canStartJob: !calledFromBackgroundPoller,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
return updatedJob?.id
|
||||
|
@ -372,7 +369,7 @@ public class Poller {
|
|||
db,
|
||||
job: jobToRun,
|
||||
canStartJob: !calledFromBackgroundPoller,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Create the dependency between the jobs
|
||||
|
@ -429,10 +426,11 @@ public class Poller {
|
|||
// Note: In the background we just want jobs to fail silently
|
||||
ConfigMessageReceiveJob.run(
|
||||
job,
|
||||
queue: dependencies.receiveQueue,
|
||||
queue: Threading.pollerQueue,
|
||||
success: { _, _, _ in resolver(Result.success(())) },
|
||||
failure: { _, _, _, _ in resolver(Result.success(())) },
|
||||
deferred: { _, _ in resolver(Result.success(())) }
|
||||
deferred: { _, _ in resolver(Result.success(())) },
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -449,10 +447,11 @@ public class Poller {
|
|||
// Note: In the background we just want jobs to fail silently
|
||||
MessageReceiveJob.run(
|
||||
job,
|
||||
queue: dependencies.receiveQueue,
|
||||
queue: Threading.pollerQueue,
|
||||
success: { _, _, _ in resolver(Result.success(())) },
|
||||
failure: { _, _, _, _ in resolver(Result.success(())) },
|
||||
deferred: { _, _ in resolver(Result.success(())) }
|
||||
deferred: { _, _ in resolver(Result.success(())) },
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,11 +54,11 @@ public class TypingIndicators {
|
|||
self.timestampMs = (timestampMs ?? SnodeAPI.currentOffsetTimestampMs())
|
||||
}
|
||||
|
||||
fileprivate func start(_ db: Database) {
|
||||
fileprivate func start(_ db: Database, using dependencies: Dependencies = Dependencies()) {
|
||||
// Start the typing indicator
|
||||
switch direction {
|
||||
case .outgoing:
|
||||
scheduleRefreshCallback(db, shouldSend: (refreshTimer == nil))
|
||||
scheduleRefreshCallback(db, shouldSend: (refreshTimer == nil), using: dependencies)
|
||||
|
||||
case .incoming:
|
||||
try? ThreadTypingIndicator(
|
||||
|
@ -72,7 +72,7 @@ public class TypingIndicators {
|
|||
refreshTimeout()
|
||||
}
|
||||
|
||||
fileprivate func stop(_ db: Database) {
|
||||
fileprivate func stop(_ db: Database, using dependencies: Dependencies = Dependencies()) {
|
||||
self.refreshTimer?.invalidate()
|
||||
self.refreshTimer = nil
|
||||
self.stopTimer?.invalidate()
|
||||
|
@ -85,7 +85,8 @@ public class TypingIndicators {
|
|||
message: TypingIndicator(kind: .stopped),
|
||||
interactionId: nil,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case .incoming:
|
||||
|
@ -111,14 +112,19 @@ public class TypingIndicators {
|
|||
}
|
||||
}
|
||||
|
||||
private func scheduleRefreshCallback(_ db: Database, shouldSend: Bool = true) {
|
||||
private func scheduleRefreshCallback(
|
||||
_ db: Database,
|
||||
shouldSend: Bool = true,
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
if shouldSend {
|
||||
try? MessageSender.send(
|
||||
db,
|
||||
message: TypingIndicator(kind: .started),
|
||||
interactionId: nil,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -127,8 +133,8 @@ public class TypingIndicators {
|
|||
withTimeInterval: 10,
|
||||
repeats: false
|
||||
) { [weak self] _ in
|
||||
Storage.shared.writeAsync { db in
|
||||
self?.scheduleRefreshCallback(db)
|
||||
dependencies.storage.writeAsync { db in
|
||||
self?.scheduleRefreshCallback(db, using: dependencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@ internal extension SessionUtil {
|
|||
_ db: Database,
|
||||
in conf: UnsafeMutablePointer<config_object>?,
|
||||
mergeNeedsDump: Bool,
|
||||
latestConfigSentTimestampMs: Int64
|
||||
latestConfigSentTimestampMs: Int64,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
guard mergeNeedsDump else { return }
|
||||
guard conf != nil else { throw SessionUtilError.nilConfigObject }
|
||||
|
@ -236,7 +237,8 @@ internal extension SessionUtil {
|
|||
admins: updatedAdmins.map { $0.profileId },
|
||||
expirationTimer: UInt32(group.disappearingConfig?.durationSeconds ?? 0),
|
||||
formationTimestampMs: UInt64((group.joinedAt.map { $0 * 1000 } ?? latestConfigSentTimestampMs)),
|
||||
calledFromConfigHandling: true
|
||||
calledFromConfigHandling: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -18,7 +18,8 @@ internal extension SessionUtil {
|
|||
_ db: Database,
|
||||
in conf: UnsafeMutablePointer<config_object>?,
|
||||
mergeNeedsDump: Bool,
|
||||
latestConfigSentTimestampMs: Int64
|
||||
latestConfigSentTimestampMs: Int64,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
typealias ProfileData = (profileName: String, profilePictureUrl: String?, profilePictureKey: Data?)
|
||||
|
||||
|
@ -51,7 +52,8 @@ internal extension SessionUtil {
|
|||
)
|
||||
}(),
|
||||
sentTimestamp: (TimeInterval(latestConfigSentTimestampMs) / 1000),
|
||||
calledFromConfigHandling: true
|
||||
calledFromConfigHandling: true,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Update the 'Note to Self' visibility and priority
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import Clibsodium
|
||||
import Curve25519Kit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
// MARK: - Generic Hash
|
||||
|
||||
public extension Crypto.Action {
|
||||
static func hash(message: Bytes, key: Bytes?) -> Crypto.Action {
|
||||
return Crypto.Action(id: "hash", args: [message, key]) {
|
||||
Sodium().genericHash.hash(message: message, key: key)
|
||||
}
|
||||
}
|
||||
|
||||
static func hash(message: Bytes, outputLength: Int) -> Crypto.Action {
|
||||
return Crypto.Action(id: "hashOutputLength", args: [message, outputLength]) {
|
||||
Sodium().genericHash.hash(message: message, outputLength: outputLength)
|
||||
}
|
||||
}
|
||||
|
||||
static func hashSaltPersonal(
|
||||
message: Bytes,
|
||||
outputLength: Int,
|
||||
key: Bytes? = nil,
|
||||
salt: Bytes,
|
||||
personal: Bytes
|
||||
) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "hashSaltPersonal",
|
||||
args: [message, outputLength, key, salt, personal]
|
||||
) {
|
||||
var output: [UInt8] = [UInt8](repeating: 0, count: outputLength)
|
||||
|
||||
let result = crypto_generichash_blake2b_salt_personal(
|
||||
&output,
|
||||
outputLength,
|
||||
message,
|
||||
UInt64(message.count),
|
||||
key,
|
||||
(key?.count ?? 0),
|
||||
salt,
|
||||
personal
|
||||
)
|
||||
|
||||
guard result == 0 else { return nil }
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sign
|
||||
|
||||
public extension Crypto.Action {
|
||||
static func toX25519(ed25519PublicKey: Bytes) -> Crypto.Action {
|
||||
return Crypto.Action(id: "toX25519", args: [ed25519PublicKey]) {
|
||||
Sodium().sign.toX25519(ed25519PublicKey: ed25519PublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
static func toX25519(ed25519SecretKey: Bytes) -> Crypto.Action {
|
||||
return Crypto.Action(id: "toX25519", args: [ed25519SecretKey]) {
|
||||
Sodium().sign.toX25519(ed25519SecretKey: ed25519SecretKey)
|
||||
}
|
||||
}
|
||||
|
||||
static func signature(message: Bytes, secretKey: Bytes) -> Crypto.Action {
|
||||
return Crypto.Action(id: "signature", args: [message, secretKey]) {
|
||||
Sodium().sign.signature(message: message, secretKey: secretKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Crypto.Verification {
|
||||
static func signature(message: Bytes, publicKey: Bytes, signature: Bytes) -> Crypto.Verification {
|
||||
return Crypto.Verification(id: "signature", args: [message, publicKey, signature]) {
|
||||
Sodium().sign.verify(message: message, publicKey: publicKey, signature: signature)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Box
|
||||
|
||||
public extension Crypto.Size {
|
||||
static let signature: Crypto.Size = Crypto.Size(id: "signature") { Sodium().sign.Bytes }
|
||||
static let publicKey: Crypto.Size = Crypto.Size(id: "publicKey") { Sodium().sign.PublicKeyBytes }
|
||||
}
|
||||
|
||||
public extension Crypto.Action {
|
||||
static func seal(message: Bytes, recipientPublicKey: Bytes) -> Crypto.Action {
|
||||
return Crypto.Action(id: "seal", args: [message, recipientPublicKey]) {
|
||||
Sodium().box.seal(message: message, recipientPublicKey: recipientPublicKey)
|
||||
}
|
||||
}
|
||||
|
||||
static func open(anonymousCipherText: Bytes, recipientPublicKey: Bytes, recipientSecretKey: Bytes) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "open",
|
||||
args: [anonymousCipherText, recipientPublicKey, recipientSecretKey]
|
||||
) {
|
||||
Sodium().box.open(
|
||||
anonymousCipherText: anonymousCipherText,
|
||||
recipientPublicKey: recipientPublicKey,
|
||||
recipientSecretKey: recipientSecretKey
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Ed25519
|
||||
|
||||
public extension Crypto.Action {
|
||||
static func signEd25519(data: Bytes, keyPair: KeyPair) -> Crypto.Action {
|
||||
return Crypto.Action(id: "signEd25519", args: [data, keyPair]) {
|
||||
let ecKeyPair: ECKeyPair = try ECKeyPair(
|
||||
publicKeyData: Data(keyPair.publicKey),
|
||||
privateKeyData: Data(keyPair.secretKey)
|
||||
)
|
||||
|
||||
return try Ed25519.sign(Data(data), with: ecKeyPair).bytes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Crypto.Verification {
|
||||
static func signatureEd25519(_ signature: Data, publicKey: Data, data: Data) -> Crypto.Verification {
|
||||
return Crypto.Verification(id: "signatureEd25519", args: [signature, publicKey, data]) {
|
||||
return ((try? Ed25519.verifySignature(signature, publicKey: publicKey, data: data)) == true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Crypto.KeyPairType {
|
||||
static func x25519KeyPair() -> Crypto.KeyPairType {
|
||||
return Crypto.KeyPairType(id: "x25519KeyPair") {
|
||||
let keyPair: ECKeyPair = Curve25519.generateKeyPair()
|
||||
|
||||
return KeyPair(publicKey: Array(keyPair.publicKey), secretKey: Array(keyPair.privateKey))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -286,9 +286,10 @@ public struct ProfileManager {
|
|||
profileName: String,
|
||||
avatarUpdate: AvatarUpdate = .none,
|
||||
success: ((Database) throws -> ())? = nil,
|
||||
failure: ((ProfileManagerError) -> ())? = nil
|
||||
failure: ((ProfileManagerError) -> ())? = nil,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey()
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(using: dependencies)
|
||||
let isRemovingAvatar: Bool = {
|
||||
switch avatarUpdate {
|
||||
case .remove: return true
|
||||
|
@ -298,7 +299,7 @@ public struct ProfileManager {
|
|||
|
||||
switch avatarUpdate {
|
||||
case .none, .remove, .updateTo:
|
||||
Storage.shared.writeAsync { db in
|
||||
dependencies.storage.writeAsync { db in
|
||||
if isRemovingAvatar {
|
||||
let existingProfileUrl: String? = try Profile
|
||||
.filter(id: userPublicKey)
|
||||
|
@ -327,7 +328,8 @@ public struct ProfileManager {
|
|||
publicKey: userPublicKey,
|
||||
name: profileName,
|
||||
avatarUpdate: avatarUpdate,
|
||||
sentTimestamp: Date().timeIntervalSince1970
|
||||
sentTimestamp: dependencies.dateNow.timeIntervalSince1970,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
SNLog("Successfully updated service with profile.")
|
||||
|
@ -345,7 +347,8 @@ public struct ProfileManager {
|
|||
publicKey: userPublicKey,
|
||||
name: profileName,
|
||||
avatarUpdate: .updateTo(url: downloadUrl, key: newProfileKey, fileName: fileName),
|
||||
sentTimestamp: Date().timeIntervalSince1970
|
||||
sentTimestamp: dependencies.dateNow.timeIntervalSince1970,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
SNLog("Successfully updated service with profile.")
|
||||
|
@ -498,9 +501,9 @@ public struct ProfileManager {
|
|||
avatarUpdate: AvatarUpdate,
|
||||
sentTimestamp: TimeInterval,
|
||||
calledFromConfigHandling: Bool = false,
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
let isCurrentUser = (publicKey == getUserHexEncodedPublicKey(db, dependencies: dependencies))
|
||||
let isCurrentUser = (publicKey == getUserHexEncodedPublicKey(db, using: dependencies))
|
||||
let profile: Profile = Profile.fetchOrCreate(db, id: publicKey)
|
||||
var profileChanges: [ConfigColumnAssignment] = []
|
||||
|
||||
|
@ -604,7 +607,7 @@ public struct ProfileManager {
|
|||
let targetProfile: Profile = Profile.fetchOrCreate(db, id: publicKey)
|
||||
|
||||
// FIXME: Refactor avatar downloading to be a proper Job so we can avoid this
|
||||
JobRunner.afterBlockingQueue {
|
||||
dependencies.jobRunner.afterBlockingQueue {
|
||||
ProfileManager.downloadAvatar(for: targetProfile)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,285 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import Clibsodium
|
||||
import Sodium
|
||||
import Curve25519Kit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
/// These extenion methods are used to generate a sign "blinded" messages
|
||||
///
|
||||
/// According to the Swift engineers the only situation when `UnsafeRawBufferPointer.baseAddress` is nil is when it's an
|
||||
/// empty collection; as such our guard cases wihch return `-1` when unwrapping this value should never be hit and we can ignore
|
||||
/// them as possible results.
|
||||
///
|
||||
/// For more information see:
|
||||
/// https://forums.swift.org/t/when-is-unsafemutablebufferpointer-baseaddress-nil/32136/5
|
||||
/// https://github.com/apple/swift-evolution/blob/master/proposals/0055-optional-unsafe-pointers.md#unsafebufferpointer
|
||||
extension Sodium {
|
||||
private static let scalarLength: Int = Int(crypto_core_ed25519_scalarbytes()) // 32
|
||||
private static let noClampLength: Int = Int(Sodium.lib_crypto_scalarmult_ed25519_bytes()) // 32
|
||||
private static let scalarMultLength: Int = Int(crypto_scalarmult_bytes()) // 32
|
||||
private static let publicKeyLength: Int = Int(crypto_scalarmult_bytes()) // 32
|
||||
private static let secretKeyLength: Int = Int(crypto_sign_secretkeybytes()) // 64
|
||||
|
||||
/// 64-byte blake2b hash then reduce to get the blinding factor
|
||||
public func generateBlindingFactor(serverPublicKey: String, genericHash: GenericHashType) -> Bytes? {
|
||||
/// k = salt.crypto_core_ed25519_scalar_reduce(blake2b(server_pk, digest_size=64).digest())
|
||||
let serverPubKeyData: Data = Data(hex: serverPublicKey)
|
||||
|
||||
guard !serverPubKeyData.isEmpty, let serverPublicKeyHashBytes: Bytes = genericHash.hash(message: [UInt8](serverPubKeyData), outputLength: 64) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Reduce the server public key into an ed25519 scalar (`k`)
|
||||
let kPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
|
||||
|
||||
_ = serverPublicKeyHashBytes.withUnsafeBytes { (serverPublicKeyHashPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let serverPublicKeyHashBaseAddress: UnsafePointer<UInt8> = serverPublicKeyHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_reduce(kPtr, serverPublicKeyHashBaseAddress)
|
||||
return 0
|
||||
}
|
||||
|
||||
return Data(bytes: kPtr, count: Sodium.scalarLength).bytes
|
||||
}
|
||||
|
||||
/// Calculate k*a. To get 'a' (the Ed25519 private key scalar) we call the sodium function to
|
||||
/// convert to an *x* secret key, which seems wrong--but isn't because converted keys use the
|
||||
/// same secret scalar secret (and so this is just the most convenient way to get 'a' out of
|
||||
/// a sodium Ed25519 secret key)
|
||||
func generatePrivateKeyScalar(secretKey: Bytes) -> Bytes {
|
||||
/// a = s.to_curve25519_private_key().encode()
|
||||
let aPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarMultLength)
|
||||
|
||||
/// Looks like the `crypto_sign_ed25519_sk_to_curve25519` function can't actually fail so no need to verify the result
|
||||
/// See: https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_sign/ed25519/ref10/keypair.c#L70
|
||||
_ = secretKey.withUnsafeBytes { (secretKeyPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let secretKeyBaseAddress: UnsafePointer<UInt8> = secretKeyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
return crypto_sign_ed25519_sk_to_curve25519(aPtr, secretKeyBaseAddress)
|
||||
}
|
||||
|
||||
return Data(bytes: aPtr, count: Sodium.scalarMultLength).bytes
|
||||
}
|
||||
|
||||
/// Constructs a "blinded" key pair (`ka, kA`) based on an open group server `publicKey` and an ed25519 `keyPair`
|
||||
public func blindedKeyPair(serverPublicKey: String, edKeyPair: KeyPair, genericHash: GenericHashType) -> KeyPair? {
|
||||
guard edKeyPair.publicKey.count == Sodium.publicKeyLength && edKeyPair.secretKey.count == Sodium.secretKeyLength else {
|
||||
return nil
|
||||
}
|
||||
guard let kBytes: Bytes = generateBlindingFactor(serverPublicKey: serverPublicKey, genericHash: genericHash) else {
|
||||
return nil
|
||||
}
|
||||
let aBytes: Bytes = generatePrivateKeyScalar(secretKey: edKeyPair.secretKey)
|
||||
|
||||
/// Generate the blinded key pair `ka`, `kA`
|
||||
let kaPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.secretKeyLength)
|
||||
let kAPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.publicKeyLength)
|
||||
|
||||
_ = aBytes.withUnsafeBytes { (aPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
return kBytes.withUnsafeBytes { (kPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let kBaseAddress: UnsafePointer<UInt8> = kPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
guard let aBaseAddress: UnsafePointer<UInt8> = aPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_mul(kaPtr, kBaseAddress, aBaseAddress)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
guard crypto_scalarmult_ed25519_base_noclamp(kAPtr, kaPtr) == 0 else { return nil }
|
||||
|
||||
return KeyPair(
|
||||
publicKey: Data(bytes: kAPtr, count: Sodium.publicKeyLength).bytes,
|
||||
secretKey: Data(bytes: kaPtr, count: Sodium.secretKeyLength).bytes
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructs an Ed25519 signature from a root Ed25519 key and a blinded scalar/pubkey pair, with one tweak to the
|
||||
/// construction: we add kA into the hashed value that yields r so that we have domain separation for different blinded
|
||||
/// pubkeys (this doesn't affect verification at all)
|
||||
public func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? {
|
||||
/// H_rh = sha512(s.encode()).digest()[32:]
|
||||
let H_rh: Bytes = Bytes(SHA512.hash(data: secretKey).suffix(32))
|
||||
|
||||
/// r = salt.crypto_core_ed25519_scalar_reduce(sha512_multipart(H_rh, kA, message_parts))
|
||||
let combinedHashBytes: Bytes = SHA512.hash(data: H_rh + kA + message).bytes
|
||||
let rPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
|
||||
|
||||
_ = combinedHashBytes.withUnsafeBytes { (combinedHashPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let combinedHashBaseAddress: UnsafePointer<UInt8> = combinedHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_reduce(rPtr, combinedHashBaseAddress)
|
||||
return 0
|
||||
}
|
||||
|
||||
/// sig_R = salt.crypto_scalarmult_ed25519_base_noclamp(r)
|
||||
let sig_RPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.noClampLength)
|
||||
guard crypto_scalarmult_ed25519_base_noclamp(sig_RPtr, rPtr) == 0 else { return nil }
|
||||
|
||||
/// HRAM = salt.crypto_core_ed25519_scalar_reduce(sha512_multipart(sig_R, kA, message_parts))
|
||||
let sig_RBytes: Bytes = Data(bytes: sig_RPtr, count: Sodium.noClampLength).bytes
|
||||
let HRAMHashBytes: Bytes = SHA512.hash(data: sig_RBytes + kA + message).bytes
|
||||
let HRAMPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
|
||||
|
||||
_ = HRAMHashBytes.withUnsafeBytes { (HRAMHashPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let HRAMHashBaseAddress: UnsafePointer<UInt8> = HRAMHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_reduce(HRAMPtr, HRAMHashBaseAddress)
|
||||
return 0
|
||||
}
|
||||
|
||||
/// sig_s = salt.crypto_core_ed25519_scalar_add(r, salt.crypto_core_ed25519_scalar_mul(HRAM, ka))
|
||||
let sig_sMulPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
|
||||
let sig_sPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
|
||||
|
||||
_ = ka.withUnsafeBytes { (kaPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let kaBaseAddress: UnsafePointer<UInt8> = kaPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_mul(sig_sMulPtr, HRAMPtr, kaBaseAddress)
|
||||
Sodium.lib_crypto_core_ed25519_scalar_add(sig_sPtr, rPtr, sig_sMulPtr)
|
||||
return 0
|
||||
}
|
||||
|
||||
/// full_sig = sig_R + sig_s
|
||||
return (Data(bytes: sig_RPtr, count: Sodium.noClampLength).bytes + Data(bytes: sig_sPtr, count: Sodium.scalarLength).bytes)
|
||||
}
|
||||
|
||||
/// Combines two keys (`kA`)
|
||||
public func combineKeys(lhsKeyBytes: Bytes, rhsKeyBytes: Bytes) -> Bytes? {
|
||||
let combinedPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.noClampLength)
|
||||
|
||||
let result = rhsKeyBytes.withUnsafeBytes { (rhsKeyBytesPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
return lhsKeyBytes.withUnsafeBytes { (lhsKeyBytesPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let lhsKeyBytesBaseAddress: UnsafePointer<UInt8> = lhsKeyBytesPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
guard let rhsKeyBytesBaseAddress: UnsafePointer<UInt8> = rhsKeyBytesPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
return Sodium.lib_crypto_scalarmult_ed25519_noclamp(combinedPtr, lhsKeyBytesBaseAddress, rhsKeyBytesBaseAddress)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure the above worked
|
||||
guard result == 0 else { return nil }
|
||||
|
||||
return Data(bytes: combinedPtr, count: Sodium.noClampLength).bytes
|
||||
}
|
||||
|
||||
/// Calculate a shared secret for a message from A to B:
|
||||
///
|
||||
/// BLAKE2b(a kB || kA || kB)
|
||||
///
|
||||
/// The receiver can calulate the same value via:
|
||||
///
|
||||
/// BLAKE2b(b kA || kA || kB)
|
||||
public func sharedBlindedEncryptionKey(secretKey: Bytes, otherBlindedPublicKey: Bytes, fromBlindedPublicKey kA: Bytes, toBlindedPublicKey kB: Bytes, genericHash: GenericHashType) -> Bytes? {
|
||||
let aBytes: Bytes = generatePrivateKeyScalar(secretKey: secretKey)
|
||||
|
||||
guard let combinedKeyBytes: Bytes = combineKeys(lhsKeyBytes: aBytes, rhsKeyBytes: otherBlindedPublicKey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return genericHash.hash(message: (combinedKeyBytes + kA + kB), outputLength: 32)
|
||||
}
|
||||
|
||||
/// This method should be used to check if a users standard sessionId matches a blinded one
|
||||
public func sessionId(_ standardSessionId: String, matchesBlindedId blindedSessionId: String, serverPublicKey: String, genericHash: GenericHashType) -> Bool {
|
||||
// Only support generating blinded keys for standard session ids
|
||||
guard
|
||||
let sessionId: SessionId = SessionId(from: standardSessionId),
|
||||
sessionId.prefix == .standard,
|
||||
let blindedId: SessionId = SessionId(from: blindedSessionId),
|
||||
(
|
||||
blindedId.prefix == .blinded15 ||
|
||||
blindedId.prefix == .blinded25
|
||||
),
|
||||
let kBytes: Bytes = generateBlindingFactor(serverPublicKey: serverPublicKey, genericHash: genericHash)
|
||||
else { return false }
|
||||
|
||||
/// From the session id (ignoring 05 prefix) we have two possible ed25519 pubkeys; the first is the positive (which is what
|
||||
/// Signal's XEd25519 conversion always uses)
|
||||
///
|
||||
/// Note: The below method is code we have exposed from the `curve25519_verify` method within the Curve25519 library
|
||||
/// rather than custom code we have written
|
||||
guard let xEd25519Key: Data = try? Ed25519.publicKey(from: Data(hex: sessionId.publicKey)) else { return false }
|
||||
|
||||
/// Blind the positive public key
|
||||
guard let pk1: Bytes = combineKeys(lhsKeyBytes: kBytes, rhsKeyBytes: xEd25519Key.bytes) else { return false }
|
||||
|
||||
/// For the negative, what we're going to get out of the above is simply the negative of pk1, so flip the sign bit to get pk2
|
||||
/// pk2 = pk1[0:31] + bytes([pk1[31] ^ 0b1000_0000])
|
||||
let pk2: Bytes = (pk1[0..<31] + [(pk1[31] ^ 0b1000_0000)])
|
||||
|
||||
return (
|
||||
SessionId(.blinded15, publicKey: pk1).publicKey == blindedId.publicKey ||
|
||||
SessionId(.blinded15, publicKey: pk2).publicKey == blindedId.publicKey
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension GenericHash {
|
||||
public func hashSaltPersonal(
|
||||
message: Bytes,
|
||||
outputLength: Int,
|
||||
key: Bytes? = nil,
|
||||
salt: Bytes,
|
||||
personal: Bytes
|
||||
) -> Bytes? {
|
||||
var output: [UInt8] = [UInt8](repeating: 0, count: outputLength)
|
||||
|
||||
let result = crypto_generichash_blake2b_salt_personal(
|
||||
&output,
|
||||
outputLength,
|
||||
message,
|
||||
UInt64(message.count),
|
||||
key,
|
||||
(key?.count ?? 0),
|
||||
salt,
|
||||
personal
|
||||
)
|
||||
|
||||
guard result == 0 else { return nil }
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
extension AeadXChaCha20Poly1305IetfType {
|
||||
/// This method is the same as the standard AeadXChaCha20Poly1305IetfType `encrypt` method except it allows the
|
||||
/// specification of a nonce which allows for deterministic behaviour with unit testing
|
||||
public func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes? = nil) -> Bytes? {
|
||||
guard secretKey.count == KeyBytes else { return nil }
|
||||
|
||||
var authenticatedCipherText = Bytes(repeating: 0, count: message.count + ABytes)
|
||||
var authenticatedCipherTextLen: UInt64 = 0
|
||||
|
||||
let result = crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||
&authenticatedCipherText, &authenticatedCipherTextLen,
|
||||
message, UInt64(message.count),
|
||||
additionalData, UInt64(additionalData?.count ?? 0),
|
||||
nil, nonce, secretKey
|
||||
)
|
||||
|
||||
guard result == 0 else { return nil }
|
||||
|
||||
return authenticatedCipherText
|
||||
}
|
||||
}
|
|
@ -15,8 +15,8 @@ class MessageSendJobSpec: QuickSpec {
|
|||
override func spec() {
|
||||
var job: Job!
|
||||
var interaction: Interaction!
|
||||
var attachment1: Attachment!
|
||||
var interactionAttachment1: InteractionAttachment!
|
||||
var attachment: Attachment!
|
||||
var interactionAttachment: InteractionAttachment!
|
||||
var mockStorage: Storage!
|
||||
var mockJobRunner: MockJobRunner!
|
||||
var dependencies: Dependencies!
|
||||
|
@ -24,8 +24,10 @@ class MessageSendJobSpec: QuickSpec {
|
|||
// MARK: - JobRunner
|
||||
|
||||
describe("a MessageSendJob") {
|
||||
// MARK: - Configuration
|
||||
|
||||
beforeEach {
|
||||
mockStorage = Storage(
|
||||
mockStorage = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
|
@ -36,9 +38,9 @@ class MessageSendJobSpec: QuickSpec {
|
|||
dependencies = Dependencies(
|
||||
storage: mockStorage,
|
||||
jobRunner: mockJobRunner,
|
||||
date: Date(timeIntervalSince1970: 1234567890)
|
||||
dateNow: Date(timeIntervalSince1970: 1234567890)
|
||||
)
|
||||
attachment1 = Attachment(
|
||||
attachment = Attachment(
|
||||
id: "200",
|
||||
variant: .standard,
|
||||
state: .failedDownload,
|
||||
|
@ -60,7 +62,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
}
|
||||
.thenReturn([:])
|
||||
mockJobRunner
|
||||
.when { $0.insert(any(), job: any(), before: any(), dependencies: dependencies) }
|
||||
.when { $0.insert(any(), job: any(), before: any()) }
|
||||
.then { args in
|
||||
let db: Database = args[0] as! Database
|
||||
var job: Job = args[1] as! Job
|
||||
|
@ -77,6 +79,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
dependencies = nil
|
||||
}
|
||||
|
||||
// MARK: - fails when not given any details
|
||||
it("fails when not given any details") {
|
||||
job = Job(variant: .messageSend)
|
||||
|
||||
|
@ -92,14 +95,15 @@ class MessageSendJobSpec: QuickSpec {
|
|||
permanentFailure = runPermanentFailure
|
||||
},
|
||||
deferred: { _, _ in },
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
||||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
it("fails when not given incorrect details") {
|
||||
// MARK: - fails when given incorrect details
|
||||
it("fails when given incorrect details") {
|
||||
job = Job(
|
||||
variant: .messageSend,
|
||||
details: MessageReceiveJob.Details(messages: [], calledFromBackgroundPoller: false)
|
||||
|
@ -117,13 +121,14 @@ class MessageSendJobSpec: QuickSpec {
|
|||
permanentFailure = runPermanentFailure
|
||||
},
|
||||
deferred: { _, _ in },
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
||||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: - of VisibleMessage
|
||||
context("of VisibleMessage") {
|
||||
beforeEach {
|
||||
interaction = Interaction(
|
||||
|
@ -162,6 +167,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- fails when there is no job id
|
||||
it("fails when there is no job id") {
|
||||
job = Job(
|
||||
variant: .messageSend,
|
||||
|
@ -186,13 +192,14 @@ class MessageSendJobSpec: QuickSpec {
|
|||
permanentFailure = runPermanentFailure
|
||||
},
|
||||
deferred: { _, _ in },
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
||||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: -- fails when there is no interaction id
|
||||
it("fails when there is no interaction id") {
|
||||
job = Job(
|
||||
variant: .messageSend,
|
||||
|
@ -216,13 +223,14 @@ class MessageSendJobSpec: QuickSpec {
|
|||
permanentFailure = runPermanentFailure
|
||||
},
|
||||
deferred: { _, _ in },
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
||||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: -- fails when there is no interaction for the provided interaction id
|
||||
it("fails when there is no interaction for the provided interaction id") {
|
||||
job = Job(
|
||||
variant: .messageSend,
|
||||
|
@ -248,29 +256,32 @@ class MessageSendJobSpec: QuickSpec {
|
|||
permanentFailure = runPermanentFailure
|
||||
},
|
||||
deferred: { _, _ in },
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(error).to(matchError(StorageError.objectNotFound))
|
||||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: -- with an attachment
|
||||
context("with an attachment") {
|
||||
beforeEach {
|
||||
interactionAttachment1 = InteractionAttachment(
|
||||
interactionAttachment = InteractionAttachment(
|
||||
albumIndex: 0,
|
||||
interactionId: interaction.id!,
|
||||
attachmentId: attachment1.id
|
||||
attachmentId: attachment.id
|
||||
)
|
||||
|
||||
mockStorage.write { db in
|
||||
try attachment1.insert(db)
|
||||
try interactionAttachment1.insert(db)
|
||||
try attachment.insert(db)
|
||||
try interactionAttachment.insert(db)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ---- it fails when trying to send with an attachment which previously failed to download
|
||||
it("it fails when trying to send with an attachment which previously failed to download") {
|
||||
mockStorage.write { db in
|
||||
try attachment1.with(state: .failedDownload).save(db)
|
||||
try attachment.with(state: .failedDownload).save(db)
|
||||
}
|
||||
|
||||
var error: Error? = nil
|
||||
|
@ -285,54 +296,27 @@ class MessageSendJobSpec: QuickSpec {
|
|||
permanentFailure = runPermanentFailure
|
||||
},
|
||||
deferred: { _, _ in },
|
||||
dependencies: dependencies
|
||||
)
|
||||
|
||||
expect(error).to(matchError(AttachmentError.notUploaded))
|
||||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
it("it fails when trying to send with an attachment that has an invalid downloadUrl") {
|
||||
mockStorage.write { db in
|
||||
try attachment1
|
||||
.with(
|
||||
state: .uploaded,
|
||||
downloadUrl: nil
|
||||
)
|
||||
.save(db)
|
||||
}
|
||||
|
||||
var error: Error? = nil
|
||||
var permanentFailure: Bool = false
|
||||
|
||||
MessageSendJob.run(
|
||||
job,
|
||||
queue: .main,
|
||||
success: { _, _, _ in },
|
||||
failure: { _, runError, runPermanentFailure, _ in
|
||||
error = runError
|
||||
permanentFailure = runPermanentFailure
|
||||
},
|
||||
deferred: { _, _ in },
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(error).to(matchError(AttachmentError.notUploaded))
|
||||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- with a pending upload
|
||||
context("with a pending upload") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
try attachment1.with(state: .uploading).save(db)
|
||||
try attachment.with(state: .uploading).save(db)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ------ it defers when trying to send with an attachment which is still pending upload
|
||||
it("it defers when trying to send with an attachment which is still pending upload") {
|
||||
var didDefer: Bool = false
|
||||
|
||||
mockStorage.write { db in
|
||||
try attachment1.with(state: .uploading).save(db)
|
||||
try attachment.with(state: .uploading).save(db)
|
||||
}
|
||||
|
||||
MessageSendJob.run(
|
||||
|
@ -341,12 +325,38 @@ class MessageSendJobSpec: QuickSpec {
|
|||
success: { _, _, _ in },
|
||||
failure: { _, _, _, _ in },
|
||||
deferred: { _, _ in didDefer = true },
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(didDefer).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ it defers when trying to send with an uploaded attachment that has an invalid downloadUrl
|
||||
it("it defers when trying to send with an uploaded attachment that has an invalid downloadUrl") {
|
||||
var didDefer: Bool = false
|
||||
|
||||
mockStorage.write { db in
|
||||
try attachment
|
||||
.with(
|
||||
state: .uploaded,
|
||||
downloadUrl: nil
|
||||
)
|
||||
.save(db)
|
||||
}
|
||||
|
||||
MessageSendJob.run(
|
||||
job,
|
||||
queue: .main,
|
||||
success: { _, _, _ in },
|
||||
failure: { _, _, _, _ in },
|
||||
deferred: { _, _ in didDefer = true },
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(didDefer).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ inserts an attachment upload job before the message send job
|
||||
it("inserts an attachment upload job before the message send job") {
|
||||
mockJobRunner
|
||||
.when {
|
||||
|
@ -356,17 +366,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
variant: .attachmentUpload
|
||||
)
|
||||
}
|
||||
.thenReturn([
|
||||
2: JobRunner.JobInfo(
|
||||
variant: .attachmentUpload,
|
||||
threadId: nil,
|
||||
interactionId: 100,
|
||||
detailsData: try! JSONEncoder().encode(AttachmentUploadJob.Details(
|
||||
messageSendJobId: 1,
|
||||
attachmentId: "200"
|
||||
))
|
||||
)
|
||||
])
|
||||
.thenReturn([:])
|
||||
|
||||
MessageSendJob.run(
|
||||
job,
|
||||
|
@ -374,7 +374,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
success: { _, _, _ in },
|
||||
failure: { _, _, _, _ in },
|
||||
deferred: { _, _ in },
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(mockJobRunner)
|
||||
|
@ -392,12 +392,12 @@ class MessageSendJobSpec: QuickSpec {
|
|||
attachmentId: "200"
|
||||
)
|
||||
),
|
||||
before: job,
|
||||
dependencies: dependencies
|
||||
before: job
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: ------ creates a dependency between the new job and the existing one
|
||||
it("creates a dependency between the new job and the existing one") {
|
||||
MessageSendJob.run(
|
||||
job,
|
||||
|
@ -405,7 +405,7 @@ class MessageSendJobSpec: QuickSpec {
|
|||
success: { _, _, _ in },
|
||||
failure: { _, _, _, _ in },
|
||||
deferred: { _, _ in },
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(mockStorage.read { db in try JobDependencies.fetchOne(db) })
|
||||
|
|
|
@ -44,11 +44,13 @@ class BatchRequestInfoSpec: QuickSpec {
|
|||
)
|
||||
]
|
||||
)
|
||||
let requestData: Data = try! JSONEncoder().encode(request)
|
||||
let requestString: String? = String(data: requestData, encoding: .utf8)
|
||||
|
||||
expect(requestString)
|
||||
.to(equal("[{\"path\":\"\\/batch\",\"method\":\"GET\",\"b64\":\"testBody\"}]"))
|
||||
let requestData: Data? = try? JSONEncoder().encode(request)
|
||||
let requestJson: [[String: Any]]? = requestData
|
||||
.map { try? JSONSerialization.jsonObject(with: $0) as? [[String: Any]] }
|
||||
expect(requestJson?.first?["path"] as? String).to(equal("/batch"))
|
||||
expect(requestJson?.first?["method"] as? String).to(equal("GET"))
|
||||
expect(requestJson?.first?["b64"] as? String).to(equal("testBody"))
|
||||
}
|
||||
|
||||
it("successfully encodes a byte body") {
|
||||
|
@ -70,11 +72,13 @@ class BatchRequestInfoSpec: QuickSpec {
|
|||
)
|
||||
]
|
||||
)
|
||||
let requestData: Data = try! JSONEncoder().encode(request)
|
||||
let requestString: String? = String(data: requestData, encoding: .utf8)
|
||||
|
||||
expect(requestString)
|
||||
.to(equal("[{\"path\":\"\\/batch\",\"method\":\"GET\",\"bytes\":[1,2,3]}]"))
|
||||
let requestData: Data? = try? JSONEncoder().encode(request)
|
||||
let requestJson: [[String: Any]]? = requestData
|
||||
.map { try? JSONSerialization.jsonObject(with: $0) as? [[String: Any]] }
|
||||
expect(requestJson?.first?["path"] as? String).to(equal("/batch"))
|
||||
expect(requestJson?.first?["method"] as? String).to(equal("GET"))
|
||||
expect(requestJson?.first?["bytes"] as? [Int]).to(equal([1, 2, 3]))
|
||||
}
|
||||
|
||||
it("successfully encodes a JSON body") {
|
||||
|
@ -96,11 +100,13 @@ class BatchRequestInfoSpec: QuickSpec {
|
|||
)
|
||||
]
|
||||
)
|
||||
let requestData: Data = try! JSONEncoder().encode(request)
|
||||
let requestString: String? = String(data: requestData, encoding: .utf8)
|
||||
|
||||
expect(requestString)
|
||||
.to(equal("[{\"path\":\"\\/batch\",\"method\":\"GET\",\"json\":{\"stringValue\":\"testValue\"}}]"))
|
||||
let requestData: Data? = try? JSONEncoder().encode(request)
|
||||
let requestJson: [[String: Any]]? = requestData
|
||||
.map { try? JSONSerialization.jsonObject(with: $0) as? [[String: Any]] }
|
||||
expect(requestJson?.first?["path"] as? String).to(equal("/batch"))
|
||||
expect(requestJson?.first?["method"] as? String).to(equal("GET"))
|
||||
expect(requestJson?.first?["json"] as? [String: String]).to(equal(["stringValue": "testValue"]))
|
||||
}
|
||||
|
||||
it("strips authentication headers") {
|
||||
|
|
|
@ -16,9 +16,8 @@ class SOGSMessageSpec: QuickSpec {
|
|||
var messageJson: String!
|
||||
var messageData: Data!
|
||||
var decoder: JSONDecoder!
|
||||
var mockSign: MockSign!
|
||||
var mockEd25519: MockEd25519!
|
||||
var dependencies: SMKDependencies!
|
||||
var mockCrypto: MockCrypto!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
beforeEach {
|
||||
messageJson = """
|
||||
|
@ -35,18 +34,16 @@ class SOGSMessageSpec: QuickSpec {
|
|||
}
|
||||
"""
|
||||
messageData = messageJson.data(using: .utf8)!
|
||||
mockSign = MockSign()
|
||||
mockEd25519 = MockEd25519()
|
||||
dependencies = SMKDependencies(
|
||||
sign: mockSign,
|
||||
ed25519: mockEd25519
|
||||
mockCrypto = MockCrypto()
|
||||
dependencies = Dependencies(
|
||||
crypto: mockCrypto
|
||||
)
|
||||
decoder = JSONDecoder()
|
||||
decoder.userInfo = [ Dependencies.userInfoKey: dependencies as Any ]
|
||||
}
|
||||
|
||||
afterEach {
|
||||
mockSign = nil
|
||||
mockCrypto = nil
|
||||
}
|
||||
|
||||
context("when decoding") {
|
||||
|
@ -204,8 +201,10 @@ class SOGSMessageSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("succeeds if it succeeds verification") {
|
||||
mockSign
|
||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
$0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray()))
|
||||
}
|
||||
.thenReturn(true)
|
||||
|
||||
expect {
|
||||
|
@ -215,25 +214,31 @@ class SOGSMessageSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("provides the correct values as parameters") {
|
||||
mockSign
|
||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
$0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray()))
|
||||
}
|
||||
.thenReturn(true)
|
||||
|
||||
_ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
||||
|
||||
expect(mockSign)
|
||||
expect(mockCrypto)
|
||||
.to(call(matchingParameters: true) {
|
||||
$0.verify(
|
||||
message: Data(base64Encoded: "VGVzdERhdGE=")!.bytes,
|
||||
publicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
signature: Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!.bytes
|
||||
.signature(
|
||||
message: Data(base64Encoded: "VGVzdERhdGE=")!.bytes,
|
||||
publicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
signature: Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!.bytes
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
it("throws if it fails verification") {
|
||||
mockSign
|
||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
$0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray()))
|
||||
}
|
||||
.thenReturn(false)
|
||||
|
||||
expect {
|
||||
|
@ -245,7 +250,9 @@ class SOGSMessageSpec: QuickSpec {
|
|||
|
||||
context("that is unblinded") {
|
||||
it("succeeds if it succeeds verification") {
|
||||
mockEd25519.when { try $0.verifySignature(any(), publicKey: any(), data: any()) }.thenReturn(true)
|
||||
mockCrypto
|
||||
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
||||
.thenReturn(true)
|
||||
|
||||
expect {
|
||||
try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
||||
|
@ -254,22 +261,28 @@ class SOGSMessageSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("provides the correct values as parameters") {
|
||||
mockEd25519.when { try $0.verifySignature(any(), publicKey: any(), data: any()) }.thenReturn(true)
|
||||
mockCrypto
|
||||
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
||||
.thenReturn(true)
|
||||
|
||||
_ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
||||
|
||||
expect(mockEd25519)
|
||||
expect(mockCrypto)
|
||||
.to(call(matchingParameters: true) {
|
||||
try $0.verifySignature(
|
||||
Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!,
|
||||
publicKey: Data(hex: TestConstants.publicKey),
|
||||
data: Data(base64Encoded: "VGVzdERhdGE=")!
|
||||
$0.verify(
|
||||
.signatureEd25519(
|
||||
Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!,
|
||||
publicKey: Data(hex: TestConstants.publicKey),
|
||||
data: Data(base64Encoded: "VGVzdERhdGE=")!
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
it("throws if it fails verification") {
|
||||
mockEd25519.when { try $0.verifySignature(any(), publicKey: any(), data: any()) }.thenReturn(false)
|
||||
mockCrypto
|
||||
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
||||
.thenReturn(false)
|
||||
|
||||
expect {
|
||||
try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,98 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class SodiumProtocolsSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
describe("an AeadXChaCha20Poly1305IetfType") {
|
||||
let testValue: [UInt8] = [1, 2, 3]
|
||||
|
||||
it("provides the default values in it's extensions") {
|
||||
let mockAead: MockAeadXChaCha20Poly1305Ietf = MockAeadXChaCha20Poly1305Ietf()
|
||||
mockAead
|
||||
.when {
|
||||
$0.encrypt(
|
||||
message: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray(),
|
||||
additionalData: anyArray()
|
||||
)
|
||||
}
|
||||
.thenReturn(testValue)
|
||||
mockAead
|
||||
.when {
|
||||
$0.decrypt(
|
||||
authenticatedCipherText: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray(),
|
||||
additionalData: anyArray()
|
||||
)
|
||||
}
|
||||
.thenReturn(testValue)
|
||||
|
||||
_ = mockAead.encrypt(message: [], secretKey: [], nonce: [])
|
||||
_ = mockAead.decrypt(authenticatedCipherText: [], secretKey: [], nonce: [])
|
||||
|
||||
expect(mockAead)
|
||||
.to(call {
|
||||
$0.encrypt(message: anyArray(), secretKey: anyArray(), nonce: anyArray(), additionalData: anyArray())
|
||||
})
|
||||
|
||||
expect(mockAead)
|
||||
.to(call {
|
||||
$0.decrypt(
|
||||
authenticatedCipherText: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray(),
|
||||
additionalData: anyArray()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
describe("a GenericHashType") {
|
||||
let testValue: [UInt8] = [1, 2, 3]
|
||||
|
||||
it("provides the default values in it's extensions") {
|
||||
let mockGenericHash: MockGenericHash = MockGenericHash()
|
||||
mockGenericHash
|
||||
.when { $0.hash(message: anyArray(), key: anyArray()) }
|
||||
.thenReturn(testValue)
|
||||
mockGenericHash
|
||||
.when {
|
||||
$0.hashSaltPersonal(
|
||||
message: anyArray(),
|
||||
outputLength: any(),
|
||||
key: anyArray(),
|
||||
salt: anyArray(),
|
||||
personal: anyArray()
|
||||
)
|
||||
}
|
||||
.thenReturn(testValue)
|
||||
|
||||
_ = mockGenericHash.hash(message: [])
|
||||
_ = mockGenericHash.hashSaltPersonal(message: [], outputLength: 0, salt: [], personal: [])
|
||||
|
||||
expect(mockGenericHash)
|
||||
.to(call { $0.hash(message: anyArray(), key: anyArray()) })
|
||||
expect(mockGenericHash)
|
||||
.to(call {
|
||||
$0.hashSaltPersonal(
|
||||
message: anyArray(),
|
||||
outputLength: any(),
|
||||
key: anyArray(),
|
||||
salt: anyArray(),
|
||||
personal: anyArray()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,93 +15,110 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
|
||||
override func spec() {
|
||||
var mockStorage: Storage!
|
||||
var mockSodium: MockSodium!
|
||||
var mockBox: MockBox!
|
||||
var mockGenericHash: MockGenericHash!
|
||||
var mockSign: MockSign!
|
||||
var mockAeadXChaCha: MockAeadXChaCha20Poly1305Ietf!
|
||||
var mockNonce24Generator: MockNonce24Generator!
|
||||
var dependencies: SMKDependencies!
|
||||
var mockCrypto: MockCrypto!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
describe("a MessageReceiver") {
|
||||
beforeEach {
|
||||
mockStorage = Storage(
|
||||
mockStorage = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
SNMessagingKit.self
|
||||
]
|
||||
)
|
||||
mockSodium = MockSodium()
|
||||
mockBox = MockBox()
|
||||
mockGenericHash = MockGenericHash()
|
||||
mockSign = MockSign()
|
||||
mockAeadXChaCha = MockAeadXChaCha20Poly1305Ietf()
|
||||
mockNonce24Generator = MockNonce24Generator()
|
||||
|
||||
mockAeadXChaCha
|
||||
.when { $0.encrypt(message: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
||||
.thenReturn(nil)
|
||||
|
||||
dependencies = SMKDependencies(
|
||||
mockCrypto = MockCrypto()
|
||||
dependencies = Dependencies(
|
||||
storage: mockStorage,
|
||||
sodium: mockSodium,
|
||||
box: mockBox,
|
||||
genericHash: mockGenericHash,
|
||||
sign: mockSign,
|
||||
aeadXChaCha20Poly1305Ietf: mockAeadXChaCha,
|
||||
nonceGenerator24: mockNonce24Generator
|
||||
crypto: mockCrypto
|
||||
)
|
||||
|
||||
mockStorage.write { db in
|
||||
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
||||
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
|
||||
}
|
||||
mockBox
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
message: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
mockCrypto
|
||||
.when {
|
||||
$0.open(
|
||||
anonymousCipherText: anyArray(),
|
||||
recipientPublicKey: anyArray(),
|
||||
recipientSecretKey: anyArray()
|
||||
try $0.perform(
|
||||
.open(
|
||||
anonymousCipherText: anyArray(),
|
||||
recipientPublicKey: anyArray(),
|
||||
recipientSecretKey: anyArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn([UInt8](repeating: 0, count: 100))
|
||||
mockSodium
|
||||
.when { $0.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), genericHash: mockGenericHash) }
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: any(),
|
||||
edKeyPair: any(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(
|
||||
KeyPair(
|
||||
publicKey: Data(hex: TestConstants.blindedPublicKey).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
)
|
||||
)
|
||||
mockSodium
|
||||
.when {
|
||||
$0.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
genericHash: mockGenericHash
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn([])
|
||||
mockSodium
|
||||
.when { $0.generateBlindingFactor(serverPublicKey: any(), genericHash: mockGenericHash) }
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(.generateBlindingFactor(serverPublicKey: any(), using: dependencies))
|
||||
}
|
||||
.thenReturn([])
|
||||
mockSodium
|
||||
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
||||
mockCrypto
|
||||
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||
.thenReturn(Data(hex: TestConstants.blindedPublicKey).bytes)
|
||||
mockSign
|
||||
.when { $0.toX25519(ed25519PublicKey: anyArray()) }
|
||||
mockCrypto
|
||||
.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }
|
||||
.thenReturn(Data(hex: TestConstants.publicKey).bytes)
|
||||
mockSign
|
||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
||||
mockCrypto
|
||||
.when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) }
|
||||
.thenReturn(true)
|
||||
mockAeadXChaCha
|
||||
.when { $0.decrypt(authenticatedCipherText: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
try $0.perform(
|
||||
.decryptAeadXChaCha20(
|
||||
authenticatedCipherText: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn("TestMessage".data(using: .utf8)!.bytes + [UInt8](repeating: 0, count: 32))
|
||||
mockNonce24Generator
|
||||
.when { $0.nonce() }
|
||||
mockCrypto.when { $0.size(.nonce24) }.thenReturn(24)
|
||||
mockCrypto.when { $0.size(.publicKey) }.thenReturn(32)
|
||||
mockCrypto.when { $0.size(.signature) }.thenReturn(64)
|
||||
mockCrypto
|
||||
.when { try $0.perform(.generateNonce24()) }
|
||||
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
||||
}
|
||||
|
||||
|
@ -117,7 +134,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||
),
|
||||
dependencies: SMKDependencies()
|
||||
using: Dependencies()
|
||||
)
|
||||
|
||||
expect(String(data: (result?.plaintext ?? Data()), encoding: .utf8)).to(equal("TestMessage"))
|
||||
|
@ -126,12 +143,14 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot open the message") {
|
||||
mockBox
|
||||
mockCrypto
|
||||
.when {
|
||||
$0.open(
|
||||
anonymousCipherText: anyArray(),
|
||||
recipientPublicKey: anyArray(),
|
||||
recipientSecretKey: anyArray()
|
||||
try $0.perform(
|
||||
.open(
|
||||
anonymousCipherText: anyArray(),
|
||||
recipientPublicKey: anyArray(),
|
||||
recipientSecretKey: anyArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
@ -143,19 +162,21 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||
),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
}
|
||||
|
||||
it("throws an error if the open message is too short") {
|
||||
mockBox
|
||||
mockCrypto
|
||||
.when {
|
||||
$0.open(
|
||||
anonymousCipherText: anyArray(),
|
||||
recipientPublicKey: anyArray(),
|
||||
recipientSecretKey: anyArray()
|
||||
try $0.perform(
|
||||
.open(
|
||||
anonymousCipherText: anyArray(),
|
||||
recipientPublicKey: anyArray(),
|
||||
recipientSecretKey: anyArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn([1, 2, 3])
|
||||
|
@ -167,15 +188,15 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||
),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
}
|
||||
|
||||
it("throws an error if it cannot verify the message") {
|
||||
mockSign
|
||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
||||
mockCrypto
|
||||
.when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) }
|
||||
.thenReturn(false)
|
||||
|
||||
expect {
|
||||
|
@ -185,14 +206,14 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||
),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.to(throwError(MessageReceiverError.invalidSignature))
|
||||
}
|
||||
|
||||
it("throws an error if it cannot get the senders x25519 public key") {
|
||||
mockSign.when { $0.toX25519(ed25519PublicKey: anyArray()) }.thenReturn(nil)
|
||||
mockCrypto.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }.thenReturn(nil)
|
||||
|
||||
expect {
|
||||
try MessageReceiver.decryptWithSessionProtocol(
|
||||
|
@ -201,7 +222,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||
),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
|
@ -223,7 +244,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
||||
),
|
||||
using: SMKDependencies()
|
||||
using: Dependencies()
|
||||
)
|
||||
|
||||
expect(String(data: (result?.plaintext ?? Data()), encoding: .utf8)).to(equal("TestMessage"))
|
||||
|
@ -271,8 +292,16 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot get the blinded keyPair") {
|
||||
mockSodium
|
||||
.when { $0.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), genericHash: mockGenericHash) }
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: any(),
|
||||
edKeyPair: any(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
||||
expect {
|
||||
|
@ -296,14 +325,16 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot get the decryption key") {
|
||||
mockSodium
|
||||
.when {
|
||||
$0.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
genericHash: mockGenericHash
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
@ -350,8 +381,16 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot decrypt the data") {
|
||||
mockAeadXChaCha
|
||||
.when { $0.decrypt(authenticatedCipherText: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
try $0.perform(
|
||||
.decryptAeadXChaCha20(
|
||||
authenticatedCipherText: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
||||
expect {
|
||||
|
@ -375,8 +414,16 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if the inner bytes are too short") {
|
||||
mockAeadXChaCha
|
||||
.when { $0.decrypt(authenticatedCipherText: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
try $0.perform(
|
||||
.decryptAeadXChaCha20(
|
||||
authenticatedCipherText: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn([1, 2, 3])
|
||||
|
||||
expect {
|
||||
|
@ -400,8 +447,10 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot generate the blinding factor") {
|
||||
mockSodium
|
||||
.when { $0.generateBlindingFactor(serverPublicKey: any(), genericHash: mockGenericHash) }
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(.generateBlindingFactor(serverPublicKey: any(), using: dependencies))
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
||||
expect {
|
||||
|
@ -425,8 +474,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot generate the combined key") {
|
||||
mockSodium
|
||||
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
||||
mockCrypto
|
||||
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||
.thenReturn(nil)
|
||||
|
||||
expect {
|
||||
|
@ -450,8 +499,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if the combined key does not match kA") {
|
||||
mockSodium
|
||||
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
||||
mockCrypto
|
||||
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||
.thenReturn(Data(hex: TestConstants.publicKey).bytes)
|
||||
|
||||
expect {
|
||||
|
@ -475,8 +524,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot get the senders x25519 public key") {
|
||||
mockSign
|
||||
.when { $0.toX25519(ed25519PublicKey: anyArray()) }
|
||||
mockCrypto
|
||||
.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }
|
||||
.thenReturn(nil)
|
||||
|
||||
expect {
|
||||
|
|
|
@ -15,53 +15,55 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
|
||||
override func spec() {
|
||||
var mockStorage: Storage!
|
||||
var mockBox: MockBox!
|
||||
var mockSign: MockSign!
|
||||
var mockNonce24Generator: MockNonce24Generator!
|
||||
var dependencies: SMKDependencies!
|
||||
var mockCrypto: MockCrypto!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
describe("a MessageSender") {
|
||||
// MARK: - Configuration
|
||||
|
||||
beforeEach {
|
||||
mockStorage = Storage(
|
||||
mockStorage = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
SNMessagingKit.self
|
||||
]
|
||||
)
|
||||
mockBox = MockBox()
|
||||
mockSign = MockSign()
|
||||
mockNonce24Generator = MockNonce24Generator()
|
||||
mockCrypto = MockCrypto()
|
||||
|
||||
dependencies = SMKDependencies(
|
||||
dependencies = Dependencies(
|
||||
storage: mockStorage,
|
||||
box: mockBox,
|
||||
sign: mockSign,
|
||||
nonceGenerator24: mockNonce24Generator
|
||||
crypto: mockCrypto
|
||||
)
|
||||
|
||||
mockStorage.write { db in
|
||||
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
||||
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
|
||||
}
|
||||
mockNonce24Generator
|
||||
.when { $0.nonce() }
|
||||
mockCrypto
|
||||
.when { try $0.perform(.generateNonce24()) }
|
||||
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
||||
}
|
||||
|
||||
// MARK: - when encrypting with the session protocol
|
||||
context("when encrypting with the session protocol") {
|
||||
beforeEach {
|
||||
mockBox.when { $0.seal(message: anyArray(), recipientPublicKey: anyArray()) }.thenReturn([1, 2, 3])
|
||||
mockSign.when { $0.signature(message: anyArray(), secretKey: anyArray()) }.thenReturn([])
|
||||
mockCrypto
|
||||
.when { try $0.perform(.seal(message: anyArray(), recipientPublicKey: anyArray())) }
|
||||
.thenReturn([1, 2, 3])
|
||||
mockCrypto
|
||||
.when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) }
|
||||
.thenReturn([])
|
||||
}
|
||||
|
||||
// MARK: -- can encrypt correctly
|
||||
it("can encrypt correctly") {
|
||||
let result = mockStorage.write { db in
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
plaintext: "TestMessage".data(using: .utf8)!,
|
||||
for: "05\(TestConstants.publicKey)",
|
||||
using: SMKDependencies(storage: mockStorage)
|
||||
using: Dependencies() // Don't mock
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -70,8 +72,9 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
expect(result?.count).to(equal(155))
|
||||
}
|
||||
|
||||
// MARK: -- returns the correct value when mocked
|
||||
it("returns the correct value when mocked") {
|
||||
let result = mockStorage.write { db in
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
plaintext: "TestMessage".data(using: .utf8)!,
|
||||
|
@ -83,13 +86,14 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
expect(result?.bytes).to(equal([1, 2, 3]))
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if there is no ed25519 keyPair
|
||||
it("throws an error if there is no ed25519 keyPair") {
|
||||
mockStorage.write { db in
|
||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||
_ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db)
|
||||
}
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
|
@ -102,10 +106,13 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if the signature generation fails
|
||||
it("throws an error if the signature generation fails") {
|
||||
mockSign.when { $0.signature(message: anyArray(), secretKey: anyArray()) }.thenReturn(nil)
|
||||
mockCrypto
|
||||
.when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) }
|
||||
.thenReturn(nil)
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
|
@ -118,10 +125,13 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if the encryption fails
|
||||
it("throws an error if the encryption fails") {
|
||||
mockBox.when { $0.seal(message: anyArray(), recipientPublicKey: anyArray()) }.thenReturn(nil)
|
||||
mockCrypto
|
||||
.when { try $0.perform(.seal(message: anyArray(), recipientPublicKey: anyArray())) }
|
||||
.thenReturn(nil)
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
|
@ -135,9 +145,67 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when encrypting with the blinded session protocol
|
||||
context("when encrypting with the blinded session protocol") {
|
||||
it("successfully encrypts") {
|
||||
let result = mockStorage.write { db in
|
||||
beforeEach {
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
crypto.generate(.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), using: dependencies))
|
||||
}
|
||||
.thenReturn(
|
||||
KeyPair(
|
||||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
||||
)
|
||||
)
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn([1, 2, 3])
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
message: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray(),
|
||||
additionalData: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn([2, 3, 4])
|
||||
}
|
||||
|
||||
// MARK: -- can encrypt correctly
|
||||
it("can encrypt correctly") {
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
plaintext: "TestMessage".data(using: .utf8)!,
|
||||
for: "15\(TestConstants.blindedPublicKey)",
|
||||
openGroupPublicKey: TestConstants.serverPublicKey,
|
||||
using: Dependencies() // Don't mock
|
||||
)
|
||||
}
|
||||
|
||||
// Note: A Nonce is used for this so we can't compare the exact value when not mocked
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(84))
|
||||
}
|
||||
|
||||
// MARK: -- returns the correct value when mocked
|
||||
it("returns the correct value when mocked") {
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
plaintext: "TestMessage".data(using: .utf8)!,
|
||||
|
@ -148,15 +216,12 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal(
|
||||
"00db16b6687382811d69875a5376f66acad9c49fe5e26bcf770c7e6e9c230299" +
|
||||
"f61b315299dd1fa700dd7f34305c0465af9e64dc791d7f4123f1eeafa5b4d48b" +
|
||||
"3ade4f4b2a2764762e5a2c7900f254bd91633b43"
|
||||
))
|
||||
.to(equal("00020304a5b4d48b3ade4f4b2a2764762e5a2c7900f254bd91633b43"))
|
||||
}
|
||||
|
||||
// MARK: -- includes a version at the start of the encrypted value
|
||||
it("includes a version at the start of the encrypted value") {
|
||||
let result = mockStorage.write { db in
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
plaintext: "TestMessage".data(using: .utf8)!,
|
||||
|
@ -169,8 +234,9 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
expect(result?.toHexString().prefix(2)).to(equal("00"))
|
||||
}
|
||||
|
||||
// MARK: -- includes the nonce at the end of the encrypted value
|
||||
it("includes the nonce at the end of the encrypted value") {
|
||||
let maybeResult = mockStorage.write { db in
|
||||
let maybeResult: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
plaintext: "TestMessage".data(using: .utf8)!,
|
||||
|
@ -186,8 +252,9 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
.to(equal("pbTUizreT0sqJ2R2LloseQDyVL2RYztD"))
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if the recipient isn't a blinded id
|
||||
it("throws an error if the recipient isn't a blinded id") {
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
|
@ -201,13 +268,14 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if there is no ed25519 keyPair
|
||||
it("throws an error if there is no ed25519 keyPair") {
|
||||
mockStorage.write { db in
|
||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||
_ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db)
|
||||
}
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
|
@ -221,22 +289,21 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if it fails to generate a blinded keyPair
|
||||
it("throws an error if it fails to generate a blinded keyPair") {
|
||||
let mockSodium: MockSodium = MockSodium()
|
||||
let mockGenericHash: MockGenericHash = MockGenericHash()
|
||||
dependencies = dependencies.with(sodium: mockSodium, genericHash: mockGenericHash)
|
||||
|
||||
mockSodium
|
||||
.when {
|
||||
$0.blindedKeyPair(
|
||||
serverPublicKey: any(),
|
||||
edKeyPair: any(),
|
||||
genericHash: mockGenericHash
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: any(),
|
||||
edKeyPair: any(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
|
@ -250,38 +317,23 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if it fails to generate an encryption key
|
||||
it("throws an error if it fails to generate an encryption key") {
|
||||
let mockSodium: MockSodium = MockSodium()
|
||||
let mockGenericHash: MockGenericHash = MockGenericHash()
|
||||
dependencies = dependencies.with(sodium: mockSodium, genericHash: mockGenericHash)
|
||||
|
||||
mockSodium
|
||||
.when {
|
||||
$0.blindedKeyPair(
|
||||
serverPublicKey: any(),
|
||||
edKeyPair: any(),
|
||||
genericHash: mockGenericHash
|
||||
)
|
||||
}
|
||||
.thenReturn(
|
||||
KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
)
|
||||
)
|
||||
mockSodium
|
||||
.when {
|
||||
$0.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
genericHash: mockGenericHash
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
|
@ -295,15 +347,23 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if it fails to encrypt
|
||||
it("throws an error if it fails to encrypt") {
|
||||
let mockAeadXChaCha: MockAeadXChaCha20Poly1305Ietf = MockAeadXChaCha20Poly1305Ietf()
|
||||
dependencies = dependencies.with(aeadXChaCha20Poly1305Ietf: mockAeadXChaCha)
|
||||
|
||||
mockAeadXChaCha
|
||||
.when { $0.encrypt(message: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
try $0.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
message: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray(),
|
||||
additionalData: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
|
|
|
@ -0,0 +1,402 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import SessionUtilitiesKit
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class CryptoSMKSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
var crypto: Crypto!
|
||||
var mockCrypto: MockCrypto!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
beforeEach {
|
||||
crypto = Crypto()
|
||||
mockCrypto = MockCrypto()
|
||||
dependencies = Dependencies(crypto: crypto)
|
||||
}
|
||||
|
||||
describe("Crypto for SessionMessagingKit") {
|
||||
|
||||
// MARK: - when extending Sign
|
||||
context("when extending Sign") {
|
||||
// MARK: -- can convert an ed25519 public key into an x25519 public key
|
||||
it("can convert an ed25519 public key into an x25519 public key") {
|
||||
let result = try? crypto.perform(.toX25519(ed25519PublicKey: TestConstants.edPublicKey.bytes))
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("95ffb559d4e804e9b414a5178454c426f616b4a61089b217b41165dbb7c9fe2d"))
|
||||
}
|
||||
|
||||
// MARK: -- can convert an ed25519 private key into an x25519 private key
|
||||
it("can convert an ed25519 private key into an x25519 private key") {
|
||||
let result = try? crypto.perform(.toX25519(ed25519SecretKey: TestConstants.edSecretKey.bytes))
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("c83f9a1479b103c275d2db2d6c199fdc6f589b29b742f6405e01cc5a9a1d135d"))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - when extending Sodium
|
||||
context("when extending Sodium") {
|
||||
// MARK: -- and generating a blinding factor
|
||||
context("and generating a blinding factor") {
|
||||
// MARK: --- successfully generates a blinding factor
|
||||
it("successfully generates a blinding factor") {
|
||||
let result = try? crypto.perform(
|
||||
.generateBlindingFactor(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("84e3eb75028a9b73fec031b7448e322a68ca6485fad81ab1bead56f759ebeb0f"))
|
||||
}
|
||||
|
||||
// MARK: --- fails if the serverPublicKey is not a hex string
|
||||
it("fails if the serverPublicKey is not a hex string") {
|
||||
let result = try? crypto.perform(
|
||||
.generateBlindingFactor(
|
||||
serverPublicKey: "Test",
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: --- fails if it cannot hash the serverPublicKey bytes
|
||||
it("fails if it cannot hash the serverPublicKey bytes") {
|
||||
dependencies = Dependencies(crypto: mockCrypto)
|
||||
mockCrypto
|
||||
.when { try $0.perform(.hash(message: anyArray(), outputLength: any())) }
|
||||
.thenReturn(nil)
|
||||
|
||||
let result = try? crypto.perform(
|
||||
.generateBlindingFactor(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- and generating a blinded key pair
|
||||
context("and generating a blinded key pair") {
|
||||
// MARK: --- successfully generates a blinded key pair
|
||||
it("successfully generates a blinded key pair") {
|
||||
let result = crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
// Note: The first 64 characters of the secretKey are consistent but the chars after that always differ
|
||||
expect(result?.publicKey.toHexString()).to(equal(TestConstants.blindedPublicKey))
|
||||
expect(String(result?.secretKey.toHexString().prefix(64) ?? ""))
|
||||
.to(equal("16663322d6b684e1c9dcc02b9e8642c3affd3bc431a9ea9e63dbbac88ce7a305"))
|
||||
}
|
||||
|
||||
// MARK: --- fails if the edKeyPair public key length wrong
|
||||
it("fails if the edKeyPair public key length wrong") {
|
||||
let result = crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: String(TestConstants.edPublicKey.prefix(4))).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: --- fails if the edKeyPair secret key length wrong
|
||||
it("fails if the edKeyPair secret key length wrong") {
|
||||
let result = crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: String(TestConstants.edSecretKey.prefix(4))).bytes
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: --- fails if it cannot generate a blinding factor
|
||||
it("fails if it cannot generate a blinding factor") {
|
||||
let result = crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: "Test",
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- and generating a sogsSignature
|
||||
context("and generating a sogsSignature") {
|
||||
// MARK: --- generates a correct signature
|
||||
it("generates a correct signature") {
|
||||
let result = try? crypto.perform(
|
||||
.sogsSignature(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
blindedSecretKey: Data(hex: "44d82cc15c0a5056825cae7520b6b52d000a23eb0c5ed94c4be2d9dc41d2d409").bytes,
|
||||
blindedPublicKey: Data(hex: "0bb7815abb6ba5142865895f3e5286c0527ba4d31dbb75c53ce95e91ffe025a2").bytes
|
||||
)
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal(
|
||||
"dcc086abdd2a740d9260b008fb37e12aa0ff47bd2bd9e177bbbec37fd46705a9" +
|
||||
"072ce747bda66c788c3775cdd7ad60ad15a478e0886779aad5d795fd7bf8350d"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- and combining keys
|
||||
context("and combining keys") {
|
||||
// MARK: --- generates a correct combined key
|
||||
it("generates a correct combined key") {
|
||||
let result = try? crypto.perform(
|
||||
.combineKeys(
|
||||
lhsKeyBytes: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
rhsKeyBytes: Data(hex: TestConstants.edPublicKey).bytes
|
||||
)
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("1159b5d0fcfba21228eb2121a0f59712fa8276fc6e5547ff519685a40b9819e6"))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- and creating a shared blinded encryption key
|
||||
context("and creating a shared blinded encryption key") {
|
||||
// MARK: --- generates a correct combined key
|
||||
it("generates a correct combined key") {
|
||||
let result = try? crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
otherBlindedPublicKey: Data(hex: TestConstants.blindedPublicKey).bytes,
|
||||
fromBlindedPublicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
toBlindedPublicKey: Data(hex: TestConstants.blindedPublicKey).bytes,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("388ee09e4c356b91f1cce5cc0aa0cf59e8e8cade69af61685d09c2d2731bc99e"))
|
||||
}
|
||||
|
||||
// MARK: --- fails if the scalar multiplication fails
|
||||
it("fails if the scalar multiplication fails") {
|
||||
let result = try? crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
otherBlindedPublicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
fromBlindedPublicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
toBlindedPublicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result?.toHexString()).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- and checking if a session id matches a blinded id
|
||||
context("and checking if a session id matches a blinded id") {
|
||||
// MARK: --- returns true when they match
|
||||
it("returns true when they match") {
|
||||
let result = crypto.verify(
|
||||
.sessionId(
|
||||
"05\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "15\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: --- returns false if given an invalid session id
|
||||
it("returns false if given an invalid session id") {
|
||||
let result = crypto.verify(
|
||||
.sessionId(
|
||||
"AB\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "15\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: --- returns false if given an invalid blinded id
|
||||
it("returns false if given an invalid blinded id") {
|
||||
let result = crypto.verify(
|
||||
.sessionId(
|
||||
"05\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "AB\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: --- returns false if it fails to generate the blinding factor
|
||||
it("returns false if it fails to generate the blinding factor") {
|
||||
let result = crypto.verify(
|
||||
.sessionId(
|
||||
"05\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "15\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: "Test",
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beFalse())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - when extending GenericHash
|
||||
describe("when extending GenericHash") {
|
||||
// MARK: -- and generating a hash with salt and personal values
|
||||
context("and generating a hash with salt and personal values") {
|
||||
// MARK: --- generates a hash correctly
|
||||
it("generates a hash correctly") {
|
||||
let result = try? crypto.perform(
|
||||
.hashSaltPersonal(
|
||||
message: "TestMessage".bytes,
|
||||
outputLength: 32,
|
||||
key: "Key".bytes,
|
||||
salt: "Salt".bytes,
|
||||
personal: "Personal".bytes
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(32))
|
||||
}
|
||||
|
||||
// MARK: --- generates a hash correctly with no key
|
||||
it("generates a hash correctly with no key") {
|
||||
let result = try? crypto.perform(
|
||||
.hashSaltPersonal(
|
||||
message: "TestMessage".bytes,
|
||||
outputLength: 32,
|
||||
key: nil,
|
||||
salt: "Salt".bytes,
|
||||
personal: "Personal".bytes
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(32))
|
||||
}
|
||||
|
||||
// MARK: --- fails if given invalid options
|
||||
it("fails if given invalid options") {
|
||||
let result = try? crypto.perform(
|
||||
.hashSaltPersonal(
|
||||
message: "TestMessage".bytes,
|
||||
outputLength: 65, // Max of 64
|
||||
key: "Key".bytes,
|
||||
salt: "Salt".bytes,
|
||||
personal: "Personal".bytes
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - when extending AeadXChaCha20Poly1305Ietf
|
||||
context("when extending AeadXChaCha20Poly1305Ietf") {
|
||||
// MARK: -- when encrypting
|
||||
context("when encrypting") {
|
||||
// MARK: --- encrypts correctly
|
||||
it("encrypts correctly") {
|
||||
let result = try? crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
nonce: "TestNonce".bytes,
|
||||
additionalData: nil,
|
||||
using: Dependencies()
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(27))
|
||||
}
|
||||
|
||||
// MARK: --- encrypts correctly with additional data
|
||||
it("encrypts correctly with additional data") {
|
||||
let result = try? crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
nonce: "TestNonce".bytes,
|
||||
additionalData: "TestData".bytes,
|
||||
using: Dependencies()
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(27))
|
||||
}
|
||||
|
||||
// MARK: --- fails if given an invalid key
|
||||
it("fails if given an invalid key") {
|
||||
let result = try? crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: "TestKey".bytes,
|
||||
nonce: "TestNonce".bytes,
|
||||
additionalData: "TestData".bytes,
|
||||
using: Dependencies()
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,352 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import SessionUtilitiesKit
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class SodiumUtilitiesSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
// MARK: - Sign
|
||||
|
||||
describe("an extended Sign") {
|
||||
var sign: Sign!
|
||||
|
||||
beforeEach {
|
||||
sign = Sodium().sign
|
||||
}
|
||||
|
||||
it("can convert an ed25519 public key into an x25519 public key") {
|
||||
let result = sign.toX25519(ed25519PublicKey: TestConstants.edPublicKey.bytes)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("95ffb559d4e804e9b414a5178454c426f616b4a61089b217b41165dbb7c9fe2d"))
|
||||
}
|
||||
|
||||
it("can convert an ed25519 private key into an x25519 private key") {
|
||||
let result = sign.toX25519(ed25519SecretKey: TestConstants.edSecretKey.bytes)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("c83f9a1479b103c275d2db2d6c199fdc6f589b29b742f6405e01cc5a9a1d135d"))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sodium
|
||||
|
||||
describe("an extended Sodium") {
|
||||
var sodium: Sodium!
|
||||
var genericHash: GenericHashType!
|
||||
|
||||
beforeEach {
|
||||
sodium = Sodium()
|
||||
genericHash = sodium.genericHash
|
||||
}
|
||||
|
||||
context("when generating a blinding factor") {
|
||||
it("successfully generates a blinding factor") {
|
||||
let result = sodium.generateBlindingFactor(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("84e3eb75028a9b73fec031b7448e322a68ca6485fad81ab1bead56f759ebeb0f"))
|
||||
}
|
||||
|
||||
it("fails if the serverPublicKey is not a hex string") {
|
||||
let result = sodium.generateBlindingFactor(
|
||||
serverPublicKey: "Test",
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
it("fails if it cannot hash the serverPublicKey bytes") {
|
||||
genericHash = MockGenericHash()
|
||||
(genericHash as? MockGenericHash)?
|
||||
.when { $0.hash(message: anyArray(), outputLength: any()) }
|
||||
.thenReturn(nil)
|
||||
|
||||
let result = sodium.generateBlindingFactor(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("when generating a blinded key pair") {
|
||||
it("successfully generates a blinded key pair") {
|
||||
let result = sodium.blindedKeyPair(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
),
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
// Note: The first 64 characters of the secretKey are consistent but the chars after that always differ
|
||||
expect(result?.publicKey.toHexString()).to(equal(TestConstants.blindedPublicKey))
|
||||
expect(String(result?.secretKey.toHexString().prefix(64) ?? ""))
|
||||
.to(equal("16663322d6b684e1c9dcc02b9e8642c3affd3bc431a9ea9e63dbbac88ce7a305"))
|
||||
}
|
||||
|
||||
it("fails if the edKeyPair public key length wrong") {
|
||||
let result = sodium.blindedKeyPair(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: String(TestConstants.edPublicKey.prefix(4))).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
),
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
it("fails if the edKeyPair secret key length wrong") {
|
||||
let result = sodium.blindedKeyPair(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: String(TestConstants.edSecretKey.prefix(4))).bytes
|
||||
),
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
it("fails if it cannot generate a blinding factor") {
|
||||
let result = sodium.blindedKeyPair(
|
||||
serverPublicKey: "Test",
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
),
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("when generating a sogsSignature") {
|
||||
it("generates a correct signature") {
|
||||
let result = sodium.sogsSignature(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
blindedSecretKey: Data(hex: "44d82cc15c0a5056825cae7520b6b52d000a23eb0c5ed94c4be2d9dc41d2d409").bytes,
|
||||
blindedPublicKey: Data(hex: "0bb7815abb6ba5142865895f3e5286c0527ba4d31dbb75c53ce95e91ffe025a2").bytes
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal(
|
||||
"dcc086abdd2a740d9260b008fb37e12aa0ff47bd2bd9e177bbbec37fd46705a9" +
|
||||
"072ce747bda66c788c3775cdd7ad60ad15a478e0886779aad5d795fd7bf8350d"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
context("when combining keys") {
|
||||
it("generates a correct combined key") {
|
||||
let result = sodium.combineKeys(
|
||||
lhsKeyBytes: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
rhsKeyBytes: Data(hex: TestConstants.edPublicKey).bytes
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("1159b5d0fcfba21228eb2121a0f59712fa8276fc6e5547ff519685a40b9819e6"))
|
||||
}
|
||||
|
||||
it("fails if the scalar multiplication fails") {
|
||||
let result = sodium.combineKeys(
|
||||
lhsKeyBytes: sodium.generatePrivateKeyScalar(secretKey: Data(hex: TestConstants.edSecretKey).bytes),
|
||||
rhsKeyBytes: Data(hex: TestConstants.publicKey).bytes
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("when creating a shared blinded encryption key") {
|
||||
it("generates a correct combined key") {
|
||||
let result = sodium.sharedBlindedEncryptionKey(
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
otherBlindedPublicKey: Data(hex: TestConstants.blindedPublicKey).bytes,
|
||||
fromBlindedPublicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
toBlindedPublicKey: Data(hex: TestConstants.blindedPublicKey).bytes,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("388ee09e4c356b91f1cce5cc0aa0cf59e8e8cade69af61685d09c2d2731bc99e"))
|
||||
}
|
||||
|
||||
it("fails if the scalar multiplication fails") {
|
||||
let result = sodium.sharedBlindedEncryptionKey(
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
otherBlindedPublicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
fromBlindedPublicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
toBlindedPublicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result?.toHexString()).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("when checking if a session id matches a blinded id") {
|
||||
it("returns true when they match") {
|
||||
let result = sodium.sessionId(
|
||||
"05\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "15\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beTrue())
|
||||
}
|
||||
|
||||
it("returns false if given an invalid session id") {
|
||||
let result = sodium.sessionId(
|
||||
"AB\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "15\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beFalse())
|
||||
}
|
||||
|
||||
it("returns false if given an invalid blinded id") {
|
||||
let result = sodium.sessionId(
|
||||
"05\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "AB\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beFalse())
|
||||
}
|
||||
|
||||
it("returns false if it fails to generate the blinding factor") {
|
||||
let result = sodium.sessionId(
|
||||
"05\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "15\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: "Test",
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beFalse())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - GenericHash
|
||||
|
||||
describe("an extended GenericHash") {
|
||||
var genericHash: GenericHashType!
|
||||
|
||||
beforeEach {
|
||||
genericHash = Sodium().genericHash
|
||||
}
|
||||
|
||||
context("when generating a hash with salt and personal values") {
|
||||
it("generates a hash correctly") {
|
||||
let result = genericHash.hashSaltPersonal(
|
||||
message: "TestMessage".bytes,
|
||||
outputLength: 32,
|
||||
key: "Key".bytes,
|
||||
salt: "Salt".bytes,
|
||||
personal: "Personal".bytes
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(32))
|
||||
}
|
||||
|
||||
it("generates a hash correctly with no key") {
|
||||
let result = genericHash.hashSaltPersonal(
|
||||
message: "TestMessage".bytes,
|
||||
outputLength: 32,
|
||||
key: nil,
|
||||
salt: "Salt".bytes,
|
||||
personal: "Personal".bytes
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(32))
|
||||
}
|
||||
|
||||
it("fails if given invalid options") {
|
||||
let result = genericHash.hashSaltPersonal(
|
||||
message: "TestMessage".bytes,
|
||||
outputLength: 65, // Max of 64
|
||||
key: "Key".bytes,
|
||||
salt: "Salt".bytes,
|
||||
personal: "Personal".bytes
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AeadXChaCha20Poly1305IetfType
|
||||
|
||||
describe("an extended AeadXChaCha20Poly1305IetfType") {
|
||||
var aeadXchacha20poly1305ietf: AeadXChaCha20Poly1305IetfType!
|
||||
|
||||
beforeEach {
|
||||
aeadXchacha20poly1305ietf = Sodium().aead.xchacha20poly1305ietf
|
||||
}
|
||||
|
||||
context("when encrypting") {
|
||||
it("encrypts correctly") {
|
||||
let result = aeadXchacha20poly1305ietf.encrypt(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
nonce: "TestNonce".bytes,
|
||||
additionalData: nil
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(27))
|
||||
}
|
||||
|
||||
it("encrypts correctly with additional data") {
|
||||
let result = aeadXchacha20poly1305ietf.encrypt(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
nonce: "TestNonce".bytes,
|
||||
additionalData: "TestData".bytes
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(27))
|
||||
}
|
||||
|
||||
it("fails if given an invalid key") {
|
||||
let result = aeadXchacha20poly1305ietf.encrypt(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: "TestKey".bytes,
|
||||
nonce: "TestNonce".bytes,
|
||||
additionalData: "TestData".bytes
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import SessionSnodeKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
extension SMKDependencies {
|
||||
public func with(
|
||||
onionApi: OnionRequestAPIType.Type? = nil,
|
||||
generalCache: MutableGeneralCacheType? = nil,
|
||||
storage: Storage? = nil,
|
||||
scheduler: ValueObservationScheduler? = nil,
|
||||
sodium: SodiumType? = nil,
|
||||
box: BoxType? = nil,
|
||||
genericHash: GenericHashType? = nil,
|
||||
sign: SignType? = nil,
|
||||
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
|
||||
ed25519: Ed25519Type? = nil,
|
||||
nonceGenerator16: NonceGenerator16ByteType? = nil,
|
||||
nonceGenerator24: NonceGenerator24ByteType? = nil,
|
||||
standardUserDefaults: UserDefaultsType? = nil,
|
||||
date: Date? = nil
|
||||
) -> SMKDependencies {
|
||||
return SMKDependencies(
|
||||
onionApi: (onionApi ?? self._onionApi.wrappedValue),
|
||||
generalCache: (generalCache ?? self._mutableGeneralCache.wrappedValue),
|
||||
storage: (storage ?? self._storage.wrappedValue),
|
||||
scheduler: (scheduler ?? self._scheduler.wrappedValue),
|
||||
sodium: (sodium ?? self._sodium.wrappedValue),
|
||||
box: (box ?? self._box.wrappedValue),
|
||||
genericHash: (genericHash ?? self._genericHash.wrappedValue),
|
||||
sign: (sign ?? self._sign.wrappedValue),
|
||||
aeadXChaCha20Poly1305Ietf: (aeadXChaCha20Poly1305Ietf ?? self._aeadXChaCha20Poly1305Ietf.wrappedValue),
|
||||
ed25519: (ed25519 ?? self._ed25519.wrappedValue),
|
||||
nonceGenerator16: (nonceGenerator16 ?? self._nonceGenerator16.wrappedValue),
|
||||
nonceGenerator24: (nonceGenerator24 ?? self._nonceGenerator24.wrappedValue),
|
||||
standardUserDefaults: (standardUserDefaults ?? self._standardUserDefaults.wrappedValue),
|
||||
date: (date ?? self._date.wrappedValue)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockAeadXChaCha20Poly1305Ietf: Mock<AeadXChaCha20Poly1305IetfType>, AeadXChaCha20Poly1305IetfType {
|
||||
var KeyBytes: Int = 32
|
||||
var ABytes: Int = 16
|
||||
|
||||
func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes? {
|
||||
return accept(args: [message, secretKey, nonce, additionalData]) as? Bytes
|
||||
}
|
||||
|
||||
func decrypt(authenticatedCipherText: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes? {
|
||||
return accept(args: [authenticatedCipherText, secretKey, nonce, additionalData]) as? Bytes
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockBox: Mock<BoxType>, BoxType {
|
||||
func seal(message: Bytes, recipientPublicKey: Bytes) -> Bytes? {
|
||||
return accept(args: [message, recipientPublicKey]) as? Bytes
|
||||
}
|
||||
|
||||
func open(anonymousCipherText: Bytes, recipientPublicKey: Bytes, recipientSecretKey: Bytes) -> Bytes? {
|
||||
return accept(args: [anonymousCipherText, recipientPublicKey, recipientSecretKey]) as? Bytes
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import SessionUtilitiesKit
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockEd25519: Mock<Ed25519Type>, Ed25519Type {
|
||||
func sign(data: Bytes, keyPair: KeyPair) throws -> Bytes? {
|
||||
return accept(args: [data, keyPair]) as? Bytes
|
||||
}
|
||||
|
||||
func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool {
|
||||
return accept(args: [signature, publicKey, data]) as! Bool
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockGenericHash: Mock<GenericHashType>, GenericHashType {
|
||||
func hash(message: Bytes, key: Bytes?) -> Bytes? {
|
||||
return accept(args: [message, key]) as? Bytes
|
||||
}
|
||||
|
||||
func hash(message: Bytes, outputLength: Int) -> Bytes? {
|
||||
return accept(args: [message, outputLength]) as? Bytes
|
||||
}
|
||||
|
||||
func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes? {
|
||||
return accept(args: [message, outputLength, key, salt, personal]) as? Bytes
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockNonce16Generator: Mock<NonceGenerator16ByteType>, NonceGenerator16ByteType {
|
||||
var NonceBytes: Int = 16
|
||||
|
||||
func nonce() -> Array<UInt8> { return accept() as! [UInt8] }
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockNonce24Generator: Mock<NonceGenerator24ByteType>, NonceGenerator24ByteType {
|
||||
var NonceBytes: Int = 24
|
||||
|
||||
func nonce() -> Array<UInt8> { return accept() as! [UInt8] }
|
||||
}
|
|
@ -6,7 +6,7 @@ import SessionUtilitiesKit
|
|||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockOGMCache: Mock<OGMMutableCacheType>, OGMMutableCacheType {
|
||||
class MockOGMCache: Mock<OGMCacheType>, OGMCacheType {
|
||||
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? {
|
||||
get { return accept() as? AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error> }
|
||||
set { accept(args: [newValue]) }
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockSign: Mock<SignType>, SignType {
|
||||
var Bytes: Int = 64
|
||||
var PublicKeyBytes: Int = 32
|
||||
|
||||
func signature(message: Bytes, secretKey: Bytes) -> Bytes? {
|
||||
return accept(args: [message, secretKey]) as? Bytes
|
||||
}
|
||||
|
||||
func verify(message: Bytes, publicKey: Bytes, signature: Bytes) -> Bool {
|
||||
return accept(args: [message, publicKey, signature]) as! Bool
|
||||
}
|
||||
|
||||
func toX25519(ed25519PublicKey: Bytes) -> Bytes? {
|
||||
return accept(args: [ed25519PublicKey]) as? Bytes
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import SessionUtilitiesKit
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockSodium: Mock<SodiumType>, SodiumType {
|
||||
func getBox() -> BoxType { return accept() as! BoxType }
|
||||
func getGenericHash() -> GenericHashType { return accept() as! GenericHashType }
|
||||
func getSign() -> SignType { return accept() as! SignType }
|
||||
func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return accept() as! AeadXChaCha20Poly1305IetfType }
|
||||
|
||||
func generateBlindingFactor(serverPublicKey: String, genericHash: GenericHashType) -> Bytes? {
|
||||
return accept(args: [serverPublicKey, genericHash]) as? Bytes
|
||||
}
|
||||
|
||||
func blindedKeyPair(serverPublicKey: String, edKeyPair: KeyPair, genericHash: GenericHashType) -> KeyPair? {
|
||||
return accept(args: [serverPublicKey, edKeyPair, genericHash]) as? KeyPair
|
||||
}
|
||||
|
||||
func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? {
|
||||
return accept(args: [message, secretKey, ka, kA]) as? Bytes
|
||||
}
|
||||
|
||||
func combineKeys(lhsKeyBytes: Bytes, rhsKeyBytes: Bytes) -> Bytes? {
|
||||
return accept(args: [lhsKeyBytes, rhsKeyBytes]) as? Bytes
|
||||
}
|
||||
|
||||
func sharedBlindedEncryptionKey(secretKey a: Bytes, otherBlindedPublicKey: Bytes, fromBlindedPublicKey kA: Bytes, toBlindedPublicKey kB: Bytes, genericHash: GenericHashType) -> Bytes? {
|
||||
return accept(args: [a, otherBlindedPublicKey, kA, kB, genericHash]) as? Bytes
|
||||
}
|
||||
|
||||
func sessionId(_ sessionId: String, matchesBlindedId blindedSessionId: String, serverPublicKey: String, genericHash: GenericHashType) -> Bool {
|
||||
return accept(args: [sessionId, blindedSessionId, serverPublicKey, genericHash]) as! Bool
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import SessionSnodeKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
extension OpenGroupManager.OGMDependencies {
|
||||
public func with(
|
||||
cache: OGMMutableCacheType? = nil,
|
||||
onionApi: OnionRequestAPIType.Type? = nil,
|
||||
generalCache: MutableGeneralCacheType? = nil,
|
||||
storage: Storage? = nil,
|
||||
scheduler: ValueObservationScheduler? = nil,
|
||||
sodium: SodiumType? = nil,
|
||||
box: BoxType? = nil,
|
||||
genericHash: GenericHashType? = nil,
|
||||
sign: SignType? = nil,
|
||||
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
|
||||
ed25519: Ed25519Type? = nil,
|
||||
nonceGenerator16: NonceGenerator16ByteType? = nil,
|
||||
nonceGenerator24: NonceGenerator24ByteType? = nil,
|
||||
standardUserDefaults: UserDefaultsType? = nil,
|
||||
date: Date? = nil
|
||||
) -> OpenGroupManager.OGMDependencies {
|
||||
return OpenGroupManager.OGMDependencies(
|
||||
cache: (cache ?? self._mutableCache.wrappedValue),
|
||||
onionApi: (onionApi ?? self._onionApi.wrappedValue),
|
||||
generalCache: (generalCache ?? self._mutableGeneralCache.wrappedValue),
|
||||
storage: (storage ?? self._storage.wrappedValue),
|
||||
scheduler: (scheduler ?? self._scheduler.wrappedValue),
|
||||
sodium: (sodium ?? self._sodium.wrappedValue),
|
||||
box: (box ?? self._box.wrappedValue),
|
||||
genericHash: (genericHash ?? self._genericHash.wrappedValue),
|
||||
sign: (sign ?? self._sign.wrappedValue),
|
||||
aeadXChaCha20Poly1305Ietf: (aeadXChaCha20Poly1305Ietf ?? self._aeadXChaCha20Poly1305Ietf.wrappedValue),
|
||||
ed25519: (ed25519 ?? self._ed25519.wrappedValue),
|
||||
nonceGenerator16: (nonceGenerator16 ?? self._nonceGenerator16.wrappedValue),
|
||||
nonceGenerator24: (nonceGenerator24 ?? self._nonceGenerator24.wrappedValue),
|
||||
standardUserDefaults: (standardUserDefaults ?? self._standardUserDefaults.wrappedValue),
|
||||
date: (date ?? self._date.wrappedValue)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import SessionSnodeKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
// FIXME: Change 'OnionRequestAPIType' to have instance methods instead of static methods once everything is updated to use 'Dependencies'
|
||||
class TestOnionRequestAPI: OnionRequestAPIType {
|
||||
struct RequestData: Codable {
|
||||
let urlString: String?
|
||||
let httpMethod: String
|
||||
let headers: [String: String]
|
||||
let body: Data?
|
||||
let destination: OnionRequestAPIDestination
|
||||
|
||||
var publicKey: String? {
|
||||
switch destination {
|
||||
case .snode: return nil
|
||||
case .server(_, _, let x25519PublicKey, _, _): return x25519PublicKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ResponseInfo: ResponseInfoType {
|
||||
let requestData: RequestData
|
||||
let code: Int
|
||||
let headers: [String: String]
|
||||
|
||||
init(requestData: RequestData, code: Int, headers: [String: String]) {
|
||||
self.requestData = requestData
|
||||
self.code = code
|
||||
self.headers = headers
|
||||
}
|
||||
}
|
||||
|
||||
class var mockResponse: Data? { return nil }
|
||||
|
||||
static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String, timeout: TimeInterval) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
|
||||
let responseInfo: ResponseInfo = ResponseInfo(
|
||||
requestData: RequestData(
|
||||
urlString: request.url?.absoluteString,
|
||||
httpMethod: (request.httpMethod ?? "GET"),
|
||||
headers: (request.allHTTPHeaderFields ?? [:]),
|
||||
body: request.httpBody,
|
||||
destination: OnionRequestAPIDestination.server(
|
||||
host: (request.url?.host ?? ""),
|
||||
target: OnionRequestAPIVersion.v4.rawValue,
|
||||
x25519PublicKey: x25519PublicKey,
|
||||
scheme: request.url!.scheme,
|
||||
port: request.url!.port.map { UInt16($0) }
|
||||
)
|
||||
),
|
||||
code: 200,
|
||||
headers: [:]
|
||||
)
|
||||
|
||||
return Just((responseInfo, mockResponse))
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
static func sendOnionRequest(_ payload: Data, to snode: Snode, timeout: TimeInterval) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
|
||||
let responseInfo: ResponseInfo = ResponseInfo(
|
||||
requestData: RequestData(
|
||||
urlString: "\(snode.address):\(snode.port)/onion_req/v2",
|
||||
httpMethod: "POST",
|
||||
headers: [:],
|
||||
body: payload,
|
||||
destination: OnionRequestAPIDestination.snode(snode)
|
||||
),
|
||||
code: 200,
|
||||
headers: [:]
|
||||
)
|
||||
|
||||
return Just((responseInfo, mockResponse))
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
|
@ -175,7 +175,13 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
|
|||
)
|
||||
}
|
||||
|
||||
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?) {
|
||||
func attachmentApproval(
|
||||
_ attachmentApproval: AttachmentApprovalViewController,
|
||||
didApproveAttachments attachments: [SignalAttachment],
|
||||
forThreadId threadId: String,
|
||||
messageText: String?,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
// Sharing a URL or plain text will populate the 'messageText' field so in those
|
||||
// cases we should ignore the attachments
|
||||
let isSharingUrl: Bool = (attachments.count == 1 && attachments[0].isUrl)
|
||||
|
@ -198,7 +204,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
|
|||
// Resume database
|
||||
NotificationCenter.default.post(name: Database.resumeNotification, object: self)
|
||||
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { db -> MessageSender.PreparedSendData in
|
||||
guard
|
||||
let threadVariant: SessionThread.Variant = try SessionThread
|
||||
|
@ -262,12 +268,13 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
|
|||
db,
|
||||
interaction: interaction,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0, using: dependencies) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sinkUntilComplete(
|
||||
|
|
|
@ -76,11 +76,16 @@ public extension SnodeReceivedMessageInfo {
|
|||
// MARK: - GRDB Interactions
|
||||
|
||||
public extension SnodeReceivedMessageInfo {
|
||||
static func pruneExpiredMessageHashInfo(for snode: Snode, namespace: SnodeAPI.Namespace, associatedWith publicKey: String) {
|
||||
static func pruneExpiredMessageHashInfo(
|
||||
for snode: Snode,
|
||||
namespace: SnodeAPI.Namespace,
|
||||
associatedWith publicKey: String,
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// Delete any expired SnodeReceivedMessageInfo values associated to a specific node (even
|
||||
// though this runs very quickly we fetch the rowIds we want to delete from a 'read' call
|
||||
// to avoid blocking the write queue since this method is called very frequently)
|
||||
let rowIds: [Int64] = Storage.shared
|
||||
let rowIds: [Int64] = dependencies.storage
|
||||
.read { db in
|
||||
// Only prune the hashes if new hashes exist for this Snode (if they don't then
|
||||
// we don't want to clear out the legacy hashes)
|
||||
|
@ -102,7 +107,7 @@ public extension SnodeReceivedMessageInfo {
|
|||
// If there are no rowIds to delete then do nothing
|
||||
guard !rowIds.isEmpty else { return }
|
||||
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
try SnodeReceivedMessageInfo
|
||||
.filter(rowIds.contains(Column.rowID))
|
||||
.deleteAll(db)
|
||||
|
@ -114,8 +119,13 @@ public extension SnodeReceivedMessageInfo {
|
|||
/// **Note:** This method uses a `write` instead of a read because there is a single write queue for the database and it's
|
||||
/// very common for this method to be called after the hash value has been updated but before the various `read` threads
|
||||
/// have been updated, resulting in a pointless fetch for data the app has already received
|
||||
static func fetchLastNotExpired(for snode: Snode, namespace: SnodeAPI.Namespace, associatedWith publicKey: String) -> SnodeReceivedMessageInfo? {
|
||||
return Storage.shared.read { db in
|
||||
static func fetchLastNotExpired(
|
||||
for snode: Snode,
|
||||
namespace: SnodeAPI.Namespace,
|
||||
associatedWith publicKey: String,
|
||||
using dependencies: Dependencies
|
||||
) -> SnodeReceivedMessageInfo? {
|
||||
return dependencies.storage.read { db in
|
||||
let nonLegacyHash: SnodeReceivedMessageInfo? = try SnodeReceivedMessageInfo
|
||||
.filter(
|
||||
SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == nil ||
|
||||
|
|
|
@ -17,7 +17,7 @@ public enum GetSnodePoolJob: JobExecutor {
|
|||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// If we already have cached Snodes then we still want to trigger the 'SnodeAPI.getSnodePool'
|
||||
// but we want to succeed this job immediately (since it's marked as blocking), this allows us
|
||||
|
@ -35,7 +35,7 @@ public enum GetSnodePoolJob: JobExecutor {
|
|||
// If we don't have the snode pool cached then we should also try to build the path (this will
|
||||
// speed up the onboarding process for new users because it can run before the user is created)
|
||||
SnodeAPI.getSnodePool()
|
||||
.flatMap { _ in OnionRequestAPI.getPath(excluding: nil) }
|
||||
.flatMap { _ in OnionRequestAPI.getPath(excluding: nil, using: dependencies) }
|
||||
.subscribe(on: queue)
|
||||
.receive(on: queue)
|
||||
.sinkUntilComplete(
|
||||
|
@ -53,13 +53,14 @@ public enum GetSnodePoolJob: JobExecutor {
|
|||
)
|
||||
}
|
||||
|
||||
public static func run() {
|
||||
public static func run(using dependencies: Dependencies = Dependencies()) {
|
||||
GetSnodePoolJob.run(
|
||||
Job(variant: .getSnodePool),
|
||||
queue: .global(qos: .background),
|
||||
success: { _, _, _ in },
|
||||
failure: { _, _, _, _ in },
|
||||
deferred: { _, _ in }
|
||||
deferred: { _, _ in },
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
|
@ -6,23 +6,31 @@ import CryptoKit
|
|||
import GRDB
|
||||
import SessionUtilitiesKit
|
||||
|
||||
public protocol OnionRequestAPIType {
|
||||
static func sendOnionRequest(_ payload: Data, to snode: Snode, timeout: TimeInterval) -> AnyPublisher<(ResponseInfoType, Data?), Error>
|
||||
static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String, timeout: TimeInterval) -> AnyPublisher<(ResponseInfoType, Data?), Error>
|
||||
}
|
||||
|
||||
public extension OnionRequestAPIType {
|
||||
static func sendOnionRequest(_ payload: Data, to snode: Snode) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
|
||||
return sendOnionRequest(payload, to: snode, timeout: HTTP.defaultTimeout)
|
||||
public extension Network.RequestType {
|
||||
static func onionRequest(_ payload: Data, to snode: Snode, timeout: TimeInterval = HTTP.defaultTimeout) -> Network.RequestType<Data?> {
|
||||
return Network.RequestType(
|
||||
id: "onionRequest",
|
||||
url: snode.address,
|
||||
method: "POST",
|
||||
body: payload,
|
||||
args: [payload, snode, timeout]
|
||||
) { OnionRequestAPI.sendOnionRequest(payload, to: snode, timeout: timeout) }
|
||||
}
|
||||
|
||||
static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
|
||||
return sendOnionRequest(request, to: server, with: x25519PublicKey, timeout: HTTP.defaultTimeout)
|
||||
static func onionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String, timeout: TimeInterval = HTTP.defaultTimeout) -> Network.RequestType<Data?> {
|
||||
return Network.RequestType(
|
||||
id: "onionRequest",
|
||||
url: request.url?.absoluteString,
|
||||
method: request.httpMethod,
|
||||
headers: request.allHTTPHeaderFields,
|
||||
body: request.httpBody,
|
||||
args: [request, server, x25519PublicKey, timeout]
|
||||
) { OnionRequestAPI.sendOnionRequest(request, to: server, with: x25519PublicKey, timeout: timeout) }
|
||||
}
|
||||
}
|
||||
|
||||
/// See the "Onion Requests" section of [The Session Whitepaper](https://arxiv.org/pdf/2002.04609.pdf) for more information.
|
||||
public enum OnionRequestAPI: OnionRequestAPIType {
|
||||
public enum OnionRequestAPI {
|
||||
private static var buildPathsPublisher: Atomic<AnyPublisher<[[Snode]], Error>?> = Atomic(nil)
|
||||
private static var pathFailureCount: Atomic<[[Snode]: UInt]> = Atomic([:])
|
||||
private static var snodeFailureCount: Atomic<[Snode: UInt]> = Atomic([:])
|
||||
|
@ -66,12 +74,12 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
// MARK: - Private API
|
||||
|
||||
/// Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise.
|
||||
private static func testSnode(_ snode: Snode) -> AnyPublisher<Void, Error> {
|
||||
private static func testSnode(_ snode: Snode, using dependencies: Dependencies) -> AnyPublisher<Void, Error> {
|
||||
let url = "\(snode.address):\(snode.port)/get_stats/v1"
|
||||
let timeout: TimeInterval = 3 // Use a shorter timeout for testing
|
||||
|
||||
return HTTP.execute(.get, url, timeout: timeout)
|
||||
.decoded(as: SnodeAPI.GetStatsResponse.self)
|
||||
.decoded(as: SnodeAPI.GetStatsResponse.self, using: dependencies)
|
||||
.tryMap { response -> Void in
|
||||
guard let version: Version = response.version else { throw OnionRequestAPIError.missingSnodeVersion }
|
||||
guard version >= Version(major: 2, minor: 0, patch: 7) else {
|
||||
|
@ -86,7 +94,10 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
|
||||
/// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with
|
||||
/// `Error.insufficientSnodes` if not enough (reliable) snodes are available.
|
||||
private static func getGuardSnodes(reusing reusableGuardSnodes: [Snode]) -> AnyPublisher<Set<Snode>, Error> {
|
||||
private static func getGuardSnodes(
|
||||
reusing reusableGuardSnodes: [Snode],
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Set<Snode>, Error> {
|
||||
guard guardSnodes.wrappedValue.count < targetGuardSnodeCount else {
|
||||
return Just(guardSnodes.wrappedValue)
|
||||
.setFailureType(to: Error.self)
|
||||
|
@ -115,7 +126,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
SNLog("Testing guard snode: \(candidate).")
|
||||
|
||||
// Loop until a reliable guard snode is found
|
||||
return testSnode(candidate)
|
||||
return testSnode(candidate, using: dependencies)
|
||||
.map { _ in candidate }
|
||||
.catch { _ in
|
||||
return Just(())
|
||||
|
@ -143,7 +154,10 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
/// Builds and returns `targetPathCount` paths. The returned promise errors out with `Error.insufficientSnodes`
|
||||
/// if not enough (reliable) snodes are available.
|
||||
@discardableResult
|
||||
private static func buildPaths(reusing reusablePaths: [[Snode]]) -> AnyPublisher<[[Snode]], Error> {
|
||||
private static func buildPaths(
|
||||
reusing reusablePaths: [[Snode]],
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<[[Snode]], Error> {
|
||||
if let existingBuildPathsPublisher = buildPathsPublisher.wrappedValue {
|
||||
return existingBuildPathsPublisher
|
||||
}
|
||||
|
@ -164,7 +178,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
/// Need to include the post-request code and a `shareReplay` within the publisher otherwise it can still be executed
|
||||
/// multiple times as a result of multiple subscribers
|
||||
let reusableGuardSnodes = reusablePaths.map { $0[0] }
|
||||
let publisher: AnyPublisher<[[Snode]], Error> = getGuardSnodes(reusing: reusableGuardSnodes)
|
||||
let publisher: AnyPublisher<[[Snode]], Error> = getGuardSnodes(reusing: reusableGuardSnodes, using: dependencies)
|
||||
.flatMap { (guardSnodes: Set<Snode>) -> AnyPublisher<[[Snode]], Error> in
|
||||
var unusedSnodes: Set<Snode> = SnodeAPI.snodePool.wrappedValue
|
||||
.subtracting(guardSnodes)
|
||||
|
@ -227,7 +241,10 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
}
|
||||
|
||||
/// Returns a `Path` to be used for building an onion request. Builds new paths as needed.
|
||||
internal static func getPath(excluding snode: Snode?) -> AnyPublisher<[Snode], Error> {
|
||||
internal static func getPath(
|
||||
excluding snode: Snode?,
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<[Snode], Error> {
|
||||
guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") }
|
||||
|
||||
let paths: [[Snode]] = OnionRequestAPI.paths
|
||||
|
@ -257,7 +274,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
else if !paths.isEmpty {
|
||||
if let snode = snode {
|
||||
if let path = paths.first(where: { !$0.contains(snode) }) {
|
||||
buildPaths(reusing: paths) // Re-build paths in the background
|
||||
buildPaths(reusing: paths, using: dependencies) // Re-build paths in the background
|
||||
.subscribe(on: DispatchQueue.global(qos: .background))
|
||||
.sink(receiveCompletion: { _ in cancellable = [] }, receiveValue: { _ in })
|
||||
.store(in: &cancellable)
|
||||
|
@ -267,7 +284,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
else {
|
||||
return buildPaths(reusing: paths)
|
||||
return buildPaths(reusing: paths, using: dependencies)
|
||||
.flatMap { paths in
|
||||
guard let path: [Snode] = paths.filter({ !$0.contains(snode) }).randomElement() else {
|
||||
return Fail<[Snode], Error>(error: OnionRequestAPIError.insufficientSnodes)
|
||||
|
@ -282,7 +299,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
}
|
||||
}
|
||||
else {
|
||||
buildPaths(reusing: paths) // Re-build paths in the background
|
||||
buildPaths(reusing: paths, using: dependencies) // Re-build paths in the background
|
||||
.subscribe(on: DispatchQueue.global(qos: .background))
|
||||
.sink(receiveCompletion: { _ in cancellable = [] }, receiveValue: { _ in })
|
||||
.store(in: &cancellable)
|
||||
|
@ -298,7 +315,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
}
|
||||
}
|
||||
else {
|
||||
return buildPaths(reusing: [])
|
||||
return buildPaths(reusing: [], using: dependencies)
|
||||
.flatMap { paths in
|
||||
if let snode = snode {
|
||||
if let path = paths.filter({ !$0.contains(snode) }).randomElement() {
|
||||
|
@ -330,7 +347,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
|
||||
private static func drop(_ snode: Snode) throws {
|
||||
// We repair the path here because we can do it sync. In the case where we drop a whole
|
||||
// path we leave the re-building up to getPath(excluding:) because re-building the path
|
||||
// path we leave the re-building up to getPath(excluding:using:) because re-building the path
|
||||
// in that case is async.
|
||||
OnionRequestAPI.snodeFailureCount.mutate { $0[snode] = 0 }
|
||||
var oldPaths = paths
|
||||
|
@ -375,7 +392,8 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
/// Builds an onion around `payload` and returns the result.
|
||||
private static func buildOnion(
|
||||
around payload: Data,
|
||||
targetedAt destination: OnionRequestAPIDestination
|
||||
targetedAt destination: OnionRequestAPIDestination,
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<OnionBuildingResult, Error> {
|
||||
var guardSnode: Snode!
|
||||
var targetSnodeSymmetricKey: Data! // Needed by invoke(_:on:with:) to decrypt the response sent back by the destination
|
||||
|
@ -384,7 +402,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
|
||||
if case .snode(let snode) = destination { snodeToExclude = snode }
|
||||
|
||||
return getPath(excluding: snodeToExclude)
|
||||
return getPath(excluding: snodeToExclude, using: dependencies)
|
||||
.flatMap { path -> AnyPublisher<AES.GCM.EncryptionResult, Error> in
|
||||
guardSnode = path.first!
|
||||
|
||||
|
@ -490,11 +508,12 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
|||
with payload: Data,
|
||||
to destination: OnionRequestAPIDestination,
|
||||
version: OnionRequestAPIVersion,
|
||||
timeout: TimeInterval = HTTP.defaultTimeout
|
||||
timeout: TimeInterval = HTTP.defaultTimeout,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
|
||||
var guardSnode: Snode?
|
||||
|
||||
return buildOnion(around: payload, targetedAt: destination)
|
||||
return buildOnion(around: payload, targetedAt: destination, using: dependencies)
|
||||
.flatMap { intermediate -> AnyPublisher<(ResponseInfoType, Data?), Error> in
|
||||
guardSnode = intermediate.guardSnode
|
||||
let url = "\(guardSnode!.address):\(guardSnode!.port)/onion_req/v2"
|
||||
|
|
|
@ -6,6 +6,18 @@ import Sodium
|
|||
import GRDB
|
||||
import SessionUtilitiesKit
|
||||
|
||||
public extension Network.RequestType {
|
||||
static func message(
|
||||
_ message: SnodeMessage,
|
||||
in namespace: SnodeAPI.Namespace,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Network.RequestType<SendMessagesResponse> {
|
||||
return Network.RequestType(id: "snodeAPI.sendMessage", args: [message, namespace]) {
|
||||
SnodeAPI.sendMessage(message, in: namespace, using: dependencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class SnodeAPI {
|
||||
internal static let sodium: Atomic<Sodium> = Atomic(Sodium())
|
||||
|
||||
|
@ -135,11 +147,13 @@ public final class SnodeAPI {
|
|||
return !hasInsufficientSnodes
|
||||
}
|
||||
|
||||
public static func getSnodePool() -> AnyPublisher<Set<Snode>, Error> {
|
||||
public static func getSnodePool(
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Set<Snode>, Error> {
|
||||
loadSnodePoolIfNeeded()
|
||||
|
||||
let now: Date = Date()
|
||||
let hasSnodePoolExpired: Bool = Storage.shared[.lastSnodePoolRefreshDate]
|
||||
let hasSnodePoolExpired: Bool = dependencies.storage[.lastSnodePoolRefreshDate]
|
||||
.map { now.timeIntervalSince($0) > 2 * 60 * 60 }
|
||||
.defaulting(to: true)
|
||||
let snodePool: Set<Snode> = SnodeAPI.snodePool.wrappedValue
|
||||
|
@ -163,10 +177,10 @@ public final class SnodeAPI {
|
|||
}
|
||||
|
||||
let targetPublisher: AnyPublisher<Set<Snode>, Error> = {
|
||||
guard snodePool.count >= minSnodePoolCount else { return getSnodePoolFromSeedNode() }
|
||||
guard snodePool.count >= minSnodePoolCount else { return getSnodePoolFromSeedNode(using: dependencies) }
|
||||
|
||||
return getSnodePoolFromSnode()
|
||||
.catch { _ in getSnodePoolFromSeedNode() }
|
||||
return getSnodePoolFromSnode(using: dependencies)
|
||||
.catch { _ in getSnodePoolFromSeedNode(using: dependencies) }
|
||||
.eraseToAnyPublisher()
|
||||
}()
|
||||
|
||||
|
@ -199,7 +213,10 @@ public final class SnodeAPI {
|
|||
}
|
||||
}
|
||||
|
||||
public static func getSessionID(for onsName: String) -> AnyPublisher<String, Error> {
|
||||
public static func getSessionID(
|
||||
for onsName: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<String, Error> {
|
||||
let validationCount = 3
|
||||
|
||||
// The name must be lowercased
|
||||
|
@ -236,7 +253,8 @@ public final class SnodeAPI {
|
|||
)
|
||||
),
|
||||
to: snode,
|
||||
associatedWith: nil
|
||||
associatedWith: nil,
|
||||
using: dependencies
|
||||
)
|
||||
.decoded(as: ONSResolveResponse.self)
|
||||
.tryMap { _, response -> String in
|
||||
|
@ -264,7 +282,7 @@ public final class SnodeAPI {
|
|||
|
||||
public static func getSwarm(
|
||||
for publicKey: String,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Set<Snode>, Error> {
|
||||
loadSwarmIfNeeded(for: publicKey)
|
||||
|
||||
|
@ -304,14 +322,14 @@ public final class SnodeAPI {
|
|||
refreshingConfigHashes: [String] = [],
|
||||
from snode: Snode,
|
||||
associatedWith publicKey: String,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<[SnodeAPI.Namespace: (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?)], Error> {
|
||||
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||
return Fail(error: SnodeAPIError.noKeyPair)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let userX25519PublicKey: String = getUserHexEncodedPublicKey()
|
||||
let userX25519PublicKey: String = getUserHexEncodedPublicKey(using: dependencies)
|
||||
|
||||
return Just(())
|
||||
.setFailureType(to: Error.self)
|
||||
|
@ -324,14 +342,16 @@ public final class SnodeAPI {
|
|||
SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo(
|
||||
for: snode,
|
||||
namespace: namespace,
|
||||
associatedWith: publicKey
|
||||
associatedWith: publicKey,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
result[namespace] = SnodeReceivedMessageInfo
|
||||
.fetchLastNotExpired(
|
||||
for: snode,
|
||||
namespace: namespace,
|
||||
associatedWith: publicKey
|
||||
associatedWith: publicKey,
|
||||
using: dependencies
|
||||
)?
|
||||
.hash
|
||||
}
|
||||
|
@ -445,7 +465,7 @@ public final class SnodeAPI {
|
|||
.grouped(by: \.expiry)
|
||||
.mapValues({ groupedResults in groupedResults.map { $0.hash } })
|
||||
{
|
||||
Storage.shared.writeAsync { db in
|
||||
dependencies.storage.writeAsync { db in
|
||||
try groupedExpiryResult.forEach { updatedExpiry, hashes in
|
||||
try SnodeReceivedMessageInfo
|
||||
.filter(hashes.contains(SnodeReceivedMessageInfo.Columns.hash))
|
||||
|
@ -493,7 +513,7 @@ public final class SnodeAPI {
|
|||
in namespace: SnodeAPI.Namespace,
|
||||
from snode: Snode,
|
||||
associatedWith publicKey: String,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<(info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?), Error> {
|
||||
return Deferred {
|
||||
Future<String?, Error> { resolver in
|
||||
|
@ -501,14 +521,16 @@ public final class SnodeAPI {
|
|||
SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo(
|
||||
for: snode,
|
||||
namespace: namespace,
|
||||
associatedWith: publicKey
|
||||
associatedWith: publicKey,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
let maybeLastHash: String? = SnodeReceivedMessageInfo
|
||||
.fetchLastNotExpired(
|
||||
for: snode,
|
||||
namespace: namespace,
|
||||
associatedWith: publicKey
|
||||
associatedWith: publicKey,
|
||||
using: dependencies
|
||||
)?
|
||||
.hash
|
||||
|
||||
|
@ -592,7 +614,7 @@ public final class SnodeAPI {
|
|||
public static func sendMessage(
|
||||
_ message: SnodeMessage,
|
||||
in namespace: Namespace,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<(ResponseInfoType, SendMessagesResponse), Error> {
|
||||
let publicKey: String = message.recipient
|
||||
let userX25519PublicKey: String = getUserHexEncodedPublicKey()
|
||||
|
@ -661,7 +683,7 @@ public final class SnodeAPI {
|
|||
public static func sendConfigMessages(
|
||||
_ messages: [(message: SnodeMessage, namespace: Namespace)],
|
||||
allObsoleteHashes: [String],
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<HTTP.BatchResponse, Error> {
|
||||
guard
|
||||
!messages.isEmpty,
|
||||
|
@ -755,7 +777,7 @@ public final class SnodeAPI {
|
|||
publicKey: String,
|
||||
serverHashes: [String],
|
||||
updatedExpiryMs: UInt64,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<[String: [(hash: String, expiry: UInt64)]], Error> {
|
||||
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||
return Fail(error: SnodeAPIError.noKeyPair)
|
||||
|
@ -796,7 +818,7 @@ public final class SnodeAPI {
|
|||
public static func revokeSubkey(
|
||||
publicKey: String,
|
||||
subkeyToRevoke: String,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||
return Fail(error: SnodeAPIError.noKeyPair)
|
||||
|
@ -839,14 +861,14 @@ public final class SnodeAPI {
|
|||
public static func deleteMessages(
|
||||
publicKey: String,
|
||||
serverHashes: [String],
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<[String: Bool], Error> {
|
||||
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||
return Fail(error: SnodeAPIError.noKeyPair)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
let userX25519PublicKey: String = getUserHexEncodedPublicKey()
|
||||
let userX25519PublicKey: String = getUserHexEncodedPublicKey(using: dependencies)
|
||||
|
||||
return getSwarm(for: publicKey)
|
||||
.tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: Bool], Error> in
|
||||
|
@ -894,7 +916,7 @@ public final class SnodeAPI {
|
|||
/// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation.
|
||||
public static func deleteAllMessages(
|
||||
namespace: SnodeAPI.Namespace,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<[String: Bool], Error> {
|
||||
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||
return Fail(error: SnodeAPIError.noKeyPair)
|
||||
|
@ -941,7 +963,7 @@ public final class SnodeAPI {
|
|||
public static func deleteAllMessages(
|
||||
beforeMs: UInt64,
|
||||
namespace: SnodeAPI.Namespace,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<[String: Bool], Error> {
|
||||
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||
return Fail(error: SnodeAPIError.noKeyPair)
|
||||
|
@ -989,7 +1011,7 @@ public final class SnodeAPI {
|
|||
|
||||
private static func getNetworkTime(
|
||||
from snode: Snode,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<UInt64, Error> {
|
||||
return SnodeAPI
|
||||
.send(
|
||||
|
@ -998,7 +1020,8 @@ public final class SnodeAPI {
|
|||
body: [:]
|
||||
),
|
||||
to: snode,
|
||||
associatedWith: nil
|
||||
associatedWith: nil,
|
||||
using: dependencies
|
||||
)
|
||||
.decoded(as: GetNetworkTimestampResponse.self, using: dependencies)
|
||||
.map { _, response in response.timestamp }
|
||||
|
@ -1013,7 +1036,7 @@ public final class SnodeAPI {
|
|||
}
|
||||
|
||||
private static func getSnodePoolFromSeedNode(
|
||||
dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Set<Snode>, Error> {
|
||||
let request: SnodeRequest = SnodeRequest(
|
||||
endpoint: .jsonGetNServiceNodes,
|
||||
|
@ -1073,7 +1096,7 @@ public final class SnodeAPI {
|
|||
}
|
||||
|
||||
private static func getSnodePoolFromSnode(
|
||||
dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Set<Snode>, Error> {
|
||||
var snodePool = SnodeAPI.snodePool.wrappedValue
|
||||
var snodes: Set<Snode> = []
|
||||
|
@ -1110,7 +1133,8 @@ public final class SnodeAPI {
|
|||
)
|
||||
),
|
||||
to: snode,
|
||||
associatedWith: nil
|
||||
associatedWith: nil,
|
||||
using: dependencies
|
||||
)
|
||||
.decoded(as: SnodePoolResponse.self, using: dependencies)
|
||||
.mapError { error -> Error in
|
||||
|
@ -1149,7 +1173,7 @@ public final class SnodeAPI {
|
|||
request: SnodeRequest<T>,
|
||||
to snode: Snode,
|
||||
associatedWith publicKey: String?,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
|
||||
guard let payload: Data = try? JSONEncoder().encode(request) else {
|
||||
return Fail(error: HTTPError.invalidJSON)
|
||||
|
@ -1175,11 +1199,8 @@ public final class SnodeAPI {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return dependencies.onionApi
|
||||
.sendOnionRequest(
|
||||
payload,
|
||||
to: snode
|
||||
)
|
||||
return dependencies.network
|
||||
.send(.onionRequest(payload, to: snode))
|
||||
.mapError { error in
|
||||
switch error {
|
||||
case HTTPError.httpRequestFailed(let statusCode, let data):
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import SessionUtilitiesKit
|
||||
|
||||
open class SSKDependencies: Dependencies {
|
||||
public var _onionApi: Atomic<OnionRequestAPIType.Type?>
|
||||
public var onionApi: OnionRequestAPIType.Type {
|
||||
get { Dependencies.getValueSettingIfNull(&_onionApi) { OnionRequestAPI.self } }
|
||||
set { _onionApi.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
public init(
|
||||
subscribeQueue: DispatchQueue? = nil,
|
||||
receiveQueue: DispatchQueue? = nil,
|
||||
onionApi: OnionRequestAPIType.Type? = nil,
|
||||
generalCache: MutableGeneralCacheType? = nil,
|
||||
storage: Storage? = nil,
|
||||
scheduler: ValueObservationScheduler? = nil,
|
||||
standardUserDefaults: UserDefaultsType? = nil,
|
||||
date: Date? = nil
|
||||
) {
|
||||
_onionApi = Atomic(onionApi)
|
||||
|
||||
super.init(
|
||||
subscribeQueue: subscribeQueue,
|
||||
receiveQueue: receiveQueue,
|
||||
generalCache: generalCache,
|
||||
storage: storage,
|
||||
scheduler: scheduler,
|
||||
standardUserDefaults: standardUserDefaults,
|
||||
date: date
|
||||
)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue