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',
|
name: 'Run Unit Tests',
|
||||||
commands: [
|
commands: [
|
||||||
'mkdir build',
|
'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
|
update_cocoapods_cache
|
||||||
|
@ -91,7 +91,7 @@ local update_cocoapods_cache = {
|
||||||
name: 'Build',
|
name: 'Build',
|
||||||
commands: [
|
commands: [
|
||||||
'mkdir build',
|
'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,
|
update_cocoapods_cache,
|
||||||
|
@ -118,7 +118,7 @@ local update_cocoapods_cache = {
|
||||||
name: 'Build',
|
name: 'Build',
|
||||||
commands: [
|
commands: [
|
||||||
'mkdir build',
|
'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,
|
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}"
|
base="session-ios-$(date --date=@$DRONE_BUILD_CREATED +%Y%m%dT%H%M%SZ)-${DRONE_COMMIT:0:9}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -v "$base"
|
mkdir -vp "$base"
|
||||||
|
|
||||||
# Copy over the build products
|
# Copy over the build products
|
||||||
prod_path="build/Session.xcarchive"
|
prod_path="build/Session.xcarchive"
|
||||||
sim_path="build/Session_sim.xcarchive/Products/Applications/Session.app"
|
sim_path="build/Session_sim.xcarchive/Products/Applications/Session.app"
|
||||||
|
|
||||||
mkdir build
|
mkdir -p build
|
||||||
echo "Test" > "build/test.txt"
|
echo "Test" > "build/test.txt"
|
||||||
|
|
||||||
if [ ! -d $prod_path ]; then
|
if [ ! -d $prod_path ]; then
|
||||||
|
|
|
@ -470,11 +470,10 @@
|
||||||
FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4727E02561000769AF /* CommonMockedExtensions.swift */; };
|
FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4727E02561000769AF /* CommonMockedExtensions.swift */; };
|
||||||
FD078E4927E02576000769AF /* 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 */; };
|
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 */; };
|
FD078E5427E197CA000769AF /* OpenGroupManagerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */; };
|
||||||
FD078E5A27E29F09000769AF /* MockNonce16Generator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E5927E29F09000769AF /* MockNonce16Generator.swift */; };
|
FD0969F92A69FFE700C5C365 /* Mocked.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0969F82A69FFE700C5C365 /* Mocked.swift */; };
|
||||||
FD078E5C27E29F78000769AF /* MockNonce24Generator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E5B27E29F78000769AF /* MockNonce24Generator.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 */; };
|
FD09796B27F6C67500936362 /* Failable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796A27F6C67500936362 /* Failable.swift */; };
|
||||||
FD09796E27FA6D0000936362 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796D27FA6D0000936362 /* Contact.swift */; };
|
FD09796E27FA6D0000936362 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796D27FA6D0000936362 /* Contact.swift */; };
|
||||||
FD09797027FA6FF300936362 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796F27FA6FF300936362 /* Profile.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 */; };
|
FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */; };
|
||||||
FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FD2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.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 */; };
|
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 */; };
|
FD23EA5C28ED00F80058676E /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; };
|
||||||
FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; };
|
FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; };
|
||||||
FD23EA5E28ED00FD0058676E /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.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 */; };
|
FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */; };
|
||||||
FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */; };
|
FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */; };
|
||||||
FD3C906727E416AF00CD579F /* BlindedIdLookupSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906627E416AF00CD579F /* BlindedIdLookupSpec.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 */; };
|
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 */; };
|
FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907027E445E500CD579F /* MessageReceiverDecryptionSpec.swift */; };
|
||||||
FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */; };
|
FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */; };
|
||||||
FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.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 */; };
|
FD52090528B4915F006098F6 /* PrivacySettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */; };
|
||||||
FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090828B59411006098F6 /* ScreenLockUI.swift */; };
|
FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090828B59411006098F6 /* ScreenLockUI.swift */; };
|
||||||
FD52090B28B59BB4006098F6 /* ScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090A28B59BB4006098F6 /* ScreenLockViewController.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 */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */; };
|
||||||
FD7728962849E7E90018502F /* String+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728952849E7E90018502F /* String+Utilities.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 */; };
|
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 */; };
|
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 */; };
|
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; };
|
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 */; };
|
FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */; };
|
||||||
FD83B9CC27D179BC005E1583 /* FSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9CB27D179BC005E1583 /* FSEndpoint.swift */; };
|
FD83B9CC27D179BC005E1583 /* FSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9CB27D179BC005E1583 /* FSEndpoint.swift */; };
|
||||||
FD83B9D227D59495005E1583 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.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 */; };
|
FD848B8B283DC509000E298B /* PagedDatabaseObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8A283DC509000E298B /* PagedDatabaseObserver.swift */; };
|
||||||
FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8C283E0B26000E298B /* MessageInputTypes.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 */; };
|
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 */; };
|
FD848B9828422F1A000E298B /* Date+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9728422F1A000E298B /* Date+Utilities.swift */; };
|
||||||
FD848B9A28442CE6000E298B /* StorageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9928442CE6000E298B /* StorageError.swift */; };
|
FD848B9A28442CE6000E298B /* StorageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9928442CE6000E298B /* StorageError.swift */; };
|
||||||
FD848B9C284435D7000E298B /* AppSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9B284435D7000E298B /* AppSetup.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 */; };
|
FD87DCFA28B74DB300AF0F98 /* ConversationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCF928B74DB300AF0F98 /* ConversationSettingsViewModel.swift */; };
|
||||||
FD87DCFE28B7582C00AF0F98 /* BlockedContactsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */; };
|
FD87DCFE28B7582C00AF0F98 /* BlockedContactsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */; };
|
||||||
FD87DD0028B820F200AF0F98 /* BlockedContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFF28B820F200AF0F98 /* BlockedContactCell.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 */; };
|
FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */; };
|
||||||
FD9BDE002A5D22B7005F1EBC /* libSessionUtil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */; };
|
FD9BDE002A5D22B7005F1EBC /* libSessionUtil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */; };
|
||||||
FD9BDE012A5D24EA005F1EBC /* SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C331FF1B2558F9D300070591 /* SessionUIKit.framework */; };
|
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 */; };
|
FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */; };
|
||||||
FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83829A5771A00C5C3BD /* LibSessionSpec.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 */; };
|
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 */; };
|
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */; };
|
||||||
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */; };
|
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */; };
|
||||||
FDC2909A27D71376005DAE71 /* NonceGeneratorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909927D71376005DAE71 /* NonceGeneratorSpec.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 */; };
|
FDC290A627D860CE005DAE71 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; };
|
||||||
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; };
|
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; };
|
||||||
FDC290A927D9B46D005DAE71 /* 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 */; };
|
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 */; };
|
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 */; };
|
FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381627B32EC700C60D73 /* Personalization.swift */; };
|
||||||
FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */; };
|
FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */; };
|
||||||
FDC4382F27B383AF00C60D73 /* PushServerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382E27B383AF00C60D73 /* PushServerResponse.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 */; };
|
FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */; };
|
||||||
FDC438AA27BB12BB00C60D73 /* UserModeratorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */; };
|
FDC438AA27BB12BB00C60D73 /* UserModeratorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */; };
|
||||||
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438BC27BB2AB400C60D73 /* Mockable.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 */; };
|
FDC438C727BB6DF000C60D73 /* DirectMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C627BB6DF000C60D73 /* DirectMessage.swift */; };
|
||||||
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; };
|
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; };
|
||||||
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.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 */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
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 */; };
|
FDE658A129418C7900A33BC1 /* CryptoKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */; };
|
||||||
FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A229418E2F00A33BC1 /* KeyPair.swift */; };
|
FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A229418E2F00A33BC1 /* KeyPair.swift */; };
|
||||||
FDE6E99829F8E63A00F93C5D /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE6E99729F8E63A00F93C5D /* Accessibility.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 */; };
|
FDF8488629405A61007DCAE5 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488529405A60007DCAE5 /* Request.swift */; };
|
||||||
FDF8488829405A9A007DCAE5 /* SOGSBatchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488729405A9A007DCAE5 /* SOGSBatchRequest.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 */; };
|
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 */; };
|
FDF8488E29405C04007DCAE5 /* GetSnodePoolJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488D29405C04007DCAE5 /* GetSnodePoolJob.swift */; };
|
||||||
FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */; };
|
FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */; };
|
||||||
FDF8489429405C1B007DCAE5 /* SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489329405C1B007DCAE5 /* SnodeAPI.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 */; };
|
FDFDE128282D05530098B17F /* MediaPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE127282D05530098B17F /* MediaPresentationContext.swift */; };
|
||||||
FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE129282D056B0098B17F /* MediaZoomAnimationController.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 */; };
|
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 */; };
|
FE5FDED6D91BB4B3FA5C104D /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A9C113D2086D3C8A68A371C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* 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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
FD0969F82A69FFE700C5C365 /* Mocked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mocked.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>"; };
|
|
||||||
FD09796A27F6C67500936362 /* Failable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Failable.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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
@ -2556,6 +2560,7 @@
|
||||||
FDC438B227BB15B400C60D73 /* ResponseInfo.swift */,
|
FDC438B227BB15B400C60D73 /* ResponseInfo.swift */,
|
||||||
C33FDAF2255A580500E217F9 /* ProxiedContentDownloader.swift */,
|
C33FDAF2255A580500E217F9 /* ProxiedContentDownloader.swift */,
|
||||||
C3C2A5BC255385EE00C340D1 /* HTTP.swift */,
|
C3C2A5BC255385EE00C340D1 /* HTTP.swift */,
|
||||||
|
FD23CE1A2A651E6D0000B97C /* NetworkType.swift */,
|
||||||
FDF848EE294067E4007DCAE5 /* URLResponse+Utilities.swift */,
|
FDF848EE294067E4007DCAE5 /* URLResponse+Utilities.swift */,
|
||||||
);
|
);
|
||||||
path = Networking;
|
path = Networking;
|
||||||
|
@ -2583,14 +2588,15 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
7BD477A727EC39F5004E2822 /* Atomic.swift */,
|
7BD477A727EC39F5004E2822 /* Atomic.swift */,
|
||||||
|
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */,
|
||||||
7BAF54D527ACD0E2003D12F8 /* ReusableView.swift */,
|
7BAF54D527ACD0E2003D12F8 /* ReusableView.swift */,
|
||||||
C33FDB8A255A581200E217F9 /* AppContext.h */,
|
C33FDB8A255A581200E217F9 /* AppContext.h */,
|
||||||
C33FDB85255A581100E217F9 /* AppContext.m */,
|
C33FDB85255A581100E217F9 /* AppContext.m */,
|
||||||
C3C2A5D12553860800C340D1 /* Array+Utilities.swift */,
|
C3C2A5D12553860800C340D1 /* Array+Utilities.swift */,
|
||||||
FDC4383D27B4708600C60D73 /* Atomic.swift */,
|
FDC4383D27B4708600C60D73 /* Atomic.swift */,
|
||||||
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */,
|
|
||||||
C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */,
|
C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */,
|
||||||
B8F5F58225EC94A6003BF8D4 /* Collection+Utilities.swift */,
|
B8F5F58225EC94A6003BF8D4 /* Collection+Utilities.swift */,
|
||||||
|
FD23CE2F2A67B8820000B97C /* Caches.swift */,
|
||||||
FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */,
|
FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */,
|
||||||
FDC6D75F2862B3F600B04575 /* Dependencies.swift */,
|
FDC6D75F2862B3F600B04575 /* Dependencies.swift */,
|
||||||
C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */,
|
C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */,
|
||||||
|
@ -3180,6 +3186,7 @@
|
||||||
C3A721332558BDDF0043A11F /* Open Groups */ = {
|
C3A721332558BDDF0043A11F /* Open Groups */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
FD23CE202A661CE80000B97C /* Crypto */,
|
||||||
FDC4381827B34EAD00C60D73 /* Models */,
|
FDC4381827B34EAD00C60D73 /* Models */,
|
||||||
FDC4380727B31D3A00C60D73 /* Types */,
|
FDC4380727B31D3A00C60D73 /* Types */,
|
||||||
FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */,
|
FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */,
|
||||||
|
@ -3204,9 +3211,10 @@
|
||||||
children = (
|
children = (
|
||||||
C33FDB01255A580700E217F9 /* AppReadiness.h */,
|
C33FDB01255A580700E217F9 /* AppReadiness.h */,
|
||||||
C33FDB75255A581000E217F9 /* AppReadiness.m */,
|
C33FDB75255A581000E217F9 /* AppReadiness.m */,
|
||||||
FDF0B7542807C4BB004C14C5 /* Environment.swift */,
|
FD23CE232A675C440000B97C /* Crypto+SessionMessagingKit.swift */,
|
||||||
FD859EF127BF6BA200510D0C /* Data+Utilities.swift */,
|
FD859EF127BF6BA200510D0C /* Data+Utilities.swift */,
|
||||||
C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */,
|
C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */,
|
||||||
|
FDF0B7542807C4BB004C14C5 /* Environment.swift */,
|
||||||
C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */,
|
C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */,
|
||||||
C3A71D0A2558989C0043A11F /* MessageWrapper.swift */,
|
C3A71D0A2558989C0043A11F /* MessageWrapper.swift */,
|
||||||
C38EF2F5255B6DBC007E1867 /* OWSAudioPlayer.h */,
|
C38EF2F5255B6DBC007E1867 /* OWSAudioPlayer.h */,
|
||||||
|
@ -3220,7 +3228,6 @@
|
||||||
C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */,
|
C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */,
|
||||||
C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */,
|
C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */,
|
||||||
FDC4386827B4E6B700C60D73 /* String+Utlities.swift */,
|
FDC4386827B4E6B700C60D73 /* String+Utlities.swift */,
|
||||||
FD772899284AF1BD0018502F /* Sodium+Utilities.swift */,
|
|
||||||
FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */,
|
FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */,
|
||||||
C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */,
|
C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */,
|
||||||
C3ECBF7A257056B700EA7FCE /* Threading.swift */,
|
C3ECBF7A257056B700EA7FCE /* Threading.swift */,
|
||||||
|
@ -3239,7 +3246,6 @@
|
||||||
FDF8488C29405C04007DCAE5 /* Jobs */,
|
FDF8488C29405C04007DCAE5 /* Jobs */,
|
||||||
FDF8489229405C1B007DCAE5 /* Networking */,
|
FDF8489229405C1B007DCAE5 /* Networking */,
|
||||||
C3C2A5CD255385F300C340D1 /* Utilities */,
|
C3C2A5CD255385F300C340D1 /* Utilities */,
|
||||||
FDF8488A29405BF2007DCAE5 /* SSKDependencies.swift */,
|
|
||||||
C3C2A5B9255385ED00C340D1 /* Configuration.swift */,
|
C3C2A5B9255385ED00C340D1 /* Configuration.swift */,
|
||||||
);
|
);
|
||||||
path = SessionSnodeKit;
|
path = SessionSnodeKit;
|
||||||
|
@ -3306,7 +3312,6 @@
|
||||||
FD8ECF7529340F4800C0D1BB /* SessionUtil */,
|
FD8ECF7529340F4800C0D1BB /* SessionUtil */,
|
||||||
FD3E0C82283B581F002A425C /* Shared Models */,
|
FD3E0C82283B581F002A425C /* Shared Models */,
|
||||||
C3BBE0B32554F0D30050F1E3 /* Utilities */,
|
C3BBE0B32554F0D30050F1E3 /* Utilities */,
|
||||||
FDC438C027BB4E6800C60D73 /* SMKDependencies.swift */,
|
|
||||||
FD245C612850664300B966DD /* Configuration.swift */,
|
FD245C612850664300B966DD /* Configuration.swift */,
|
||||||
);
|
);
|
||||||
path = SessionMessagingKit;
|
path = SessionMessagingKit;
|
||||||
|
@ -3552,7 +3557,10 @@
|
||||||
FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */,
|
FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */,
|
||||||
FD3003692A3ADD6000B5A5FB /* CExceptionHelper.h */,
|
FD3003692A3ADD6000B5A5FB /* CExceptionHelper.h */,
|
||||||
FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */,
|
FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */,
|
||||||
|
FD23CE1E2A65269C0000B97C /* Crypto.swift */,
|
||||||
|
FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */,
|
||||||
FD09796A27F6C67500936362 /* Failable.swift */,
|
FD09796A27F6C67500936362 /* Failable.swift */,
|
||||||
|
FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */,
|
||||||
FD09797127FAA2F500936362 /* Optional+Utilities.swift */,
|
FD09797127FAA2F500936362 /* Optional+Utilities.swift */,
|
||||||
FD09797C27FBDB2000936362 /* Notification+Utilities.swift */,
|
FD09797C27FBDB2000936362 /* Notification+Utilities.swift */,
|
||||||
FDF222082818D2B0000A4995 /* NSAttributedString+Utilities.swift */,
|
FDF222082818D2B0000A4995 /* NSAttributedString+Utilities.swift */,
|
||||||
|
@ -3736,6 +3744,14 @@
|
||||||
path = Utilities;
|
path = Utilities;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
FD23CE202A661CE80000B97C /* Crypto */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */,
|
||||||
|
);
|
||||||
|
path = Crypto;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
FD29598E2A43BE5400888A17 /* Utilities */ = {
|
FD29598E2A43BE5400888A17 /* Utilities */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -3838,7 +3854,7 @@
|
||||||
FD3C906827E417B100CD579F /* Utilities */ = {
|
FD3C906827E417B100CD579F /* Utilities */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
FD3C906927E417CE00CD579F /* SodiumUtilitiesSpec.swift */,
|
FD3C906927E417CE00CD579F /* CryptoSMKSpec.swift */,
|
||||||
);
|
);
|
||||||
path = Utilities;
|
path = Utilities;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -3876,6 +3892,7 @@
|
||||||
children = (
|
children = (
|
||||||
FD7115F628C8150D00B47552 /* Disposable Views */,
|
FD7115F628C8150D00B47552 /* Disposable Views */,
|
||||||
FD7115FD28C8202D00B47552 /* ReplaySubject.swift */,
|
FD7115FD28C8202D00B47552 /* ReplaySubject.swift */,
|
||||||
|
FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */,
|
||||||
FD7115FB28C8155800B47552 /* Publisher+Utilities.swift */,
|
FD7115FB28C8155800B47552 /* Publisher+Utilities.swift */,
|
||||||
FD71160128C8255900B47552 /* UIControl+Combine.swift */,
|
FD71160128C8255900B47552 /* UIControl+Combine.swift */,
|
||||||
FD7115F928C8153400B47552 /* UIBarButtonItem+Combine.swift */,
|
FD7115F928C8153400B47552 /* UIBarButtonItem+Combine.swift */,
|
||||||
|
@ -4011,6 +4028,7 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
FD0B77B129B82B7A009169BA /* ArrayUtilitiesSpec.swift */,
|
FD0B77B129B82B7A009169BA /* ArrayUtilitiesSpec.swift */,
|
||||||
|
FD23CE252A676B5B0000B97C /* DependenciesSpec.swift */,
|
||||||
FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */,
|
FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */,
|
||||||
);
|
);
|
||||||
path = General;
|
path = General;
|
||||||
|
@ -4020,9 +4038,14 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
FDC290A527D860CE005DAE71 /* Mock.swift */,
|
FDC290A527D860CE005DAE71 /* Mock.swift */,
|
||||||
|
FD0969F82A69FFE700C5C365 /* Mocked.swift */,
|
||||||
|
FD23CE272A67755C0000B97C /* MockCrypto.swift */,
|
||||||
|
FD23CE2B2A678DF80000B97C /* MockCaches.swift */,
|
||||||
FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */,
|
FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */,
|
||||||
|
FD23CE312A67C38D0000B97C /* MockNetwork.swift */,
|
||||||
FD96F3A629DBD43D00401309 /* MockJobRunner.swift */,
|
FD96F3A629DBD43D00401309 /* MockJobRunner.swift */,
|
||||||
FD83B9BD27CF2243005E1583 /* TestConstants.swift */,
|
FD83B9BD27CF2243005E1583 /* TestConstants.swift */,
|
||||||
|
FD9DD2702A72516D00ECB68E /* TestExtensions.swift */,
|
||||||
FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */,
|
FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */,
|
||||||
FD078E4727E02561000769AF /* CommonMockedExtensions.swift */,
|
FD078E4727E02561000769AF /* CommonMockedExtensions.swift */,
|
||||||
FD23EA6028ED0B260058676E /* CombineExtensions.swift */,
|
FD23EA6028ED0B260058676E /* CombineExtensions.swift */,
|
||||||
|
@ -4141,7 +4164,6 @@
|
||||||
FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */,
|
FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */,
|
||||||
FDC2909327D710B4005DAE71 /* SOGSEndpointSpec.swift */,
|
FDC2909327D710B4005DAE71 /* SOGSEndpointSpec.swift */,
|
||||||
FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */,
|
FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */,
|
||||||
FDC2909B27D713D2005DAE71 /* SodiumProtocolsSpec.swift */,
|
|
||||||
);
|
);
|
||||||
path = Types;
|
path = Types;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -4155,8 +4177,6 @@
|
||||||
FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */,
|
FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */,
|
||||||
FDC4381627B32EC700C60D73 /* Personalization.swift */,
|
FDC4381627B32EC700C60D73 /* Personalization.swift */,
|
||||||
FD2959912A4417A900888A17 /* PreparedSendData.swift */,
|
FD2959912A4417A900888A17 /* PreparedSendData.swift */,
|
||||||
FDC4381427B329CE00C60D73 /* NonceGenerator.swift */,
|
|
||||||
FDC438C227BB512200C60D73 /* SodiumProtocols.swift */,
|
|
||||||
);
|
);
|
||||||
path = Types;
|
path = Types;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -4248,19 +4268,8 @@
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
FDC438BC27BB2AB400C60D73 /* Mockable.swift */,
|
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 */,
|
FD83B9D127D59495005E1583 /* MockUserDefaults.swift */,
|
||||||
FD078E4C27E17156000769AF /* MockOGMCache.swift */,
|
FD078E4C27E17156000769AF /* MockOGMCache.swift */,
|
||||||
FDC290B227DFF9F5005DAE71 /* TestOnionRequestAPI.swift */,
|
|
||||||
FD078E4E27E175F1000769AF /* DependencyExtensions.swift */,
|
|
||||||
FD078E5127E1760A000769AF /* OGMDependencyExtensions.swift */,
|
|
||||||
);
|
);
|
||||||
path = _TestUtilities;
|
path = _TestUtilities;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -4427,9 +4436,6 @@
|
||||||
C33FDDB0255A582000E217F9 /* NSURLSessionDataTask+StatusCode.h in Headers */,
|
C33FDDB0255A582000E217F9 /* NSURLSessionDataTask+StatusCode.h in Headers */,
|
||||||
C33FDDD0255A582000E217F9 /* FunctionalUtil.h in Headers */,
|
C33FDDD0255A582000E217F9 /* FunctionalUtil.h in Headers */,
|
||||||
C33FDD5B255A582000E217F9 /* OWSOperation.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 */,
|
C33FDDCC255A582000E217F9 /* TSConstants.h in Headers */,
|
||||||
C33FDDBD255A582000E217F9 /* ByteParser.h in Headers */,
|
C33FDDBD255A582000E217F9 /* ByteParser.h in Headers */,
|
||||||
C33FDDB3255A582000E217F9 /* OWSError.h in Headers */,
|
C33FDDB3255A582000E217F9 /* OWSError.h in Headers */,
|
||||||
|
@ -4631,9 +4637,9 @@
|
||||||
D221A085169C9E5E00537ABF /* Sources */,
|
D221A085169C9E5E00537ABF /* Sources */,
|
||||||
D221A086169C9E5E00537ABF /* Frameworks */,
|
D221A086169C9E5E00537ABF /* Frameworks */,
|
||||||
D221A087169C9E5E00537ABF /* Resources */,
|
D221A087169C9E5E00537ABF /* Resources */,
|
||||||
FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */,
|
|
||||||
453518771FC635DD00210559 /* Embed Foundation Extensions */,
|
453518771FC635DD00210559 /* Embed Foundation Extensions */,
|
||||||
4535189F1FC63DBF00210559 /* Embed Frameworks */,
|
4535189F1FC63DBF00210559 /* Embed Frameworks */,
|
||||||
|
FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */,
|
||||||
90DF4725BB1271EBA2C66A12 /* [CP] Embed Pods Frameworks */,
|
90DF4725BB1271EBA2C66A12 /* [CP] Embed Pods Frameworks */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
|
@ -5557,7 +5563,6 @@
|
||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
FDF8488B29405BF2007DCAE5 /* SSKDependencies.swift in Sources */,
|
|
||||||
FDF8488E29405C04007DCAE5 /* GetSnodePoolJob.swift in Sources */,
|
FDF8488E29405C04007DCAE5 /* GetSnodePoolJob.swift in Sources */,
|
||||||
FDF848C329405C5A007DCAE5 /* DeleteMessagesRequest.swift in Sources */,
|
FDF848C329405C5A007DCAE5 /* DeleteMessagesRequest.swift in Sources */,
|
||||||
FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */,
|
FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */,
|
||||||
|
@ -5629,10 +5634,13 @@
|
||||||
FDFD645927F26C6800808CA1 /* Array+Utilities.swift in Sources */,
|
FDFD645927F26C6800808CA1 /* Array+Utilities.swift in Sources */,
|
||||||
7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */,
|
7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */,
|
||||||
C32C5D83256DD5B6003C73A2 /* SSKKeychainStorage.swift in Sources */,
|
C32C5D83256DD5B6003C73A2 /* SSKKeychainStorage.swift in Sources */,
|
||||||
|
FD559DF52A7368CB00C7C62A /* DispatchQueue+Utilities.swift in Sources */,
|
||||||
FDF8488329405A12007DCAE5 /* BatchResponse.swift in Sources */,
|
FDF8488329405A12007DCAE5 /* BatchResponse.swift in Sources */,
|
||||||
C3D9E39B256763C20040E4F3 /* AppContext.m in Sources */,
|
C3D9E39B256763C20040E4F3 /* AppContext.m in Sources */,
|
||||||
|
FDFF9FDF2A787F57005E0628 /* JSONEncoder+Utilities.swift in Sources */,
|
||||||
C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */,
|
C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */,
|
||||||
FD17D7C127F5200100122BE0 /* TypedTableDefinition.swift in Sources */,
|
FD17D7C127F5200100122BE0 /* TypedTableDefinition.swift in Sources */,
|
||||||
|
FD23CE1B2A651E6D0000B97C /* NetworkType.swift in Sources */,
|
||||||
FD17D7EA27F6A1C600122BE0 /* SUKLegacy.swift in Sources */,
|
FD17D7EA27F6A1C600122BE0 /* SUKLegacy.swift in Sources */,
|
||||||
FDA8EB10280F8238002B68E5 /* Codable+Utilities.swift in Sources */,
|
FDA8EB10280F8238002B68E5 /* Codable+Utilities.swift in Sources */,
|
||||||
FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */,
|
FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */,
|
||||||
|
@ -5690,8 +5698,10 @@
|
||||||
C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */,
|
C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */,
|
||||||
B8F5F58325EC94A6003BF8D4 /* Collection+Utilities.swift in Sources */,
|
B8F5F58325EC94A6003BF8D4 /* Collection+Utilities.swift in Sources */,
|
||||||
7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */,
|
7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */,
|
||||||
|
FD23CE1F2A65269C0000B97C /* Crypto.swift in Sources */,
|
||||||
B8BC00C0257D90E30032E807 /* General.swift in Sources */,
|
B8BC00C0257D90E30032E807 /* General.swift in Sources */,
|
||||||
FDF8488629405A61007DCAE5 /* Request.swift in Sources */,
|
FDF8488629405A61007DCAE5 /* Request.swift in Sources */,
|
||||||
|
FD23CE302A67B8820000B97C /* Caches.swift in Sources */,
|
||||||
FD17D7A127F40D2500122BE0 /* Storage.swift in Sources */,
|
FD17D7A127F40D2500122BE0 /* Storage.swift in Sources */,
|
||||||
FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */,
|
FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */,
|
||||||
FD5D201E27B0D87C00FEA984 /* SessionId.swift in Sources */,
|
FD5D201E27B0D87C00FEA984 /* SessionId.swift in Sources */,
|
||||||
|
@ -5701,6 +5711,7 @@
|
||||||
FDF22211281B5E0B000A4995 /* TableRecord+Utilities.swift in Sources */,
|
FDF22211281B5E0B000A4995 /* TableRecord+Utilities.swift in Sources */,
|
||||||
B88FA7FB26114EA70049422F /* Hex.swift in Sources */,
|
B88FA7FB26114EA70049422F /* Hex.swift in Sources */,
|
||||||
FD7728962849E7E90018502F /* String+Utilities.swift in Sources */,
|
FD7728962849E7E90018502F /* String+Utilities.swift in Sources */,
|
||||||
|
FD83DCDD2A739D350065FFAE /* RetryWithDependencies.swift in Sources */,
|
||||||
C35D0DB525AE5F1200B6BF49 /* UIEdgeInsets.swift in Sources */,
|
C35D0DB525AE5F1200B6BF49 /* UIEdgeInsets.swift in Sources */,
|
||||||
C3D9E4F4256778AF0040E4F3 /* NSData+Image.m in Sources */,
|
C3D9E4F4256778AF0040E4F3 /* NSData+Image.m in Sources */,
|
||||||
FDF222092818D2B0000A4995 /* NSAttributedString+Utilities.swift in Sources */,
|
FDF222092818D2B0000A4995 /* NSAttributedString+Utilities.swift in Sources */,
|
||||||
|
@ -5753,7 +5764,6 @@
|
||||||
FDF0B74928060D13004C14C5 /* QuotedReplyModel.swift in Sources */,
|
FDF0B74928060D13004C14C5 /* QuotedReplyModel.swift in Sources */,
|
||||||
FD3003662A25D5B300B5A5FB /* ConfigMessageReceiveJob.swift in Sources */,
|
FD3003662A25D5B300B5A5FB /* ConfigMessageReceiveJob.swift in Sources */,
|
||||||
7B81682C28B72F480069F315 /* PendingChange.swift in Sources */,
|
7B81682C28B72F480069F315 /* PendingChange.swift in Sources */,
|
||||||
FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */,
|
|
||||||
FD5C7309285007920029977D /* BlindedIdLookup.swift in Sources */,
|
FD5C7309285007920029977D /* BlindedIdLookup.swift in Sources */,
|
||||||
FD71161C28D194FB00B47552 /* MentionInfo.swift in Sources */,
|
FD71161C28D194FB00B47552 /* MentionInfo.swift in Sources */,
|
||||||
7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */,
|
7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */,
|
||||||
|
@ -5851,9 +5861,9 @@
|
||||||
FD432434299C6985008A0213 /* PendingReadReceipt.swift in Sources */,
|
FD432434299C6985008A0213 /* PendingReadReceipt.swift in Sources */,
|
||||||
FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */,
|
FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */,
|
||||||
FD245C51285065CC00B966DD /* MessageReceiver.swift in Sources */,
|
FD245C51285065CC00B966DD /* MessageReceiver.swift in Sources */,
|
||||||
|
FD23CE222A661D000000B97C /* OpenGroupAPI+Crypto.swift in Sources */,
|
||||||
FD245C652850665400B966DD /* ClosedGroupControlMessage.swift in Sources */,
|
FD245C652850665400B966DD /* ClosedGroupControlMessage.swift in Sources */,
|
||||||
FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */,
|
FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */,
|
||||||
FDC4381527B329CE00C60D73 /* NonceGenerator.swift in Sources */,
|
|
||||||
7B93D07027CF194000811CB6 /* ConfigurationMessage+Convenience.swift in Sources */,
|
7B93D07027CF194000811CB6 /* ConfigurationMessage+Convenience.swift in Sources */,
|
||||||
FD5C72FD284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift in Sources */,
|
FD5C72FD284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift in Sources */,
|
||||||
C32C5A88256DBCF9003C73A2 /* MessageReceiver+ClosedGroups.swift in Sources */,
|
C32C5A88256DBCF9003C73A2 /* MessageReceiver+ClosedGroups.swift in Sources */,
|
||||||
|
@ -5882,7 +5892,7 @@
|
||||||
FDA1E83B29A5F2D500C5C3BD /* SessionUtil+Shared.swift in Sources */,
|
FDA1E83B29A5F2D500C5C3BD /* SessionUtil+Shared.swift in Sources */,
|
||||||
C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */,
|
C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */,
|
||||||
FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.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 */,
|
B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */,
|
||||||
C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */,
|
C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */,
|
||||||
FD716E722850647600C96BF4 /* Data+Utilities.swift in Sources */,
|
FD716E722850647600C96BF4 /* Data+Utilities.swift in Sources */,
|
||||||
|
@ -5914,7 +5924,6 @@
|
||||||
FD09798127FCFEE800936362 /* SessionThread.swift in Sources */,
|
FD09798127FCFEE800936362 /* SessionThread.swift in Sources */,
|
||||||
FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */,
|
FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */,
|
||||||
FDF0B75E280AAF35004C14C5 /* Preferences.swift in Sources */,
|
FDF0B75E280AAF35004C14C5 /* Preferences.swift in Sources */,
|
||||||
FDC438C127BB4E6800C60D73 /* SMKDependencies.swift in Sources */,
|
|
||||||
FDC4383827B3863200C60D73 /* VersionResponse.swift in Sources */,
|
FDC4383827B3863200C60D73 /* VersionResponse.swift in Sources */,
|
||||||
B806ECA126C4A7E4008BDA44 /* WebRTCSession+UI.swift in Sources */,
|
B806ECA126C4A7E4008BDA44 /* WebRTCSession+UI.swift in Sources */,
|
||||||
FD432432299C6933008A0213 /* _011_AddPendingReadReceipts.swift in Sources */,
|
FD432432299C6933008A0213 /* _011_AddPendingReadReceipts.swift in Sources */,
|
||||||
|
@ -6140,14 +6149,18 @@
|
||||||
files = (
|
files = (
|
||||||
FD71161728D00DA400B47552 /* ThreadSettingsViewModelSpec.swift in Sources */,
|
FD71161728D00DA400B47552 /* ThreadSettingsViewModelSpec.swift in Sources */,
|
||||||
FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */,
|
FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */,
|
||||||
|
FD23CE292A6775650000B97C /* MockCrypto.swift in Sources */,
|
||||||
|
FD23CE332A67C4D90000B97C /* MockNetwork.swift in Sources */,
|
||||||
FD71161528D00D6700B47552 /* ThreadDisappearingMessagesViewModelSpec.swift in Sources */,
|
FD71161528D00D6700B47552 /* ThreadDisappearingMessagesViewModelSpec.swift in Sources */,
|
||||||
|
FD23CE2D2A678E1E0000B97C /* MockCaches.swift in Sources */,
|
||||||
FD23EA5E28ED00FD0058676E /* NimbleExtensions.swift in Sources */,
|
FD23EA5E28ED00FD0058676E /* NimbleExtensions.swift in Sources */,
|
||||||
FD23EA5F28ED00FF0058676E /* CommonMockedExtensions.swift in Sources */,
|
FD23EA5F28ED00FF0058676E /* CommonMockedExtensions.swift in Sources */,
|
||||||
FD96F3A829DBD4AD00401309 /* MockJobRunner.swift in Sources */,
|
|
||||||
FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */,
|
FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */,
|
||||||
FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */,
|
FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */,
|
||||||
|
FD0969FA2A6A00B000C5C365 /* Mocked.swift in Sources */,
|
||||||
FD23EA5C28ED00F80058676E /* Mock.swift in Sources */,
|
FD23EA5C28ED00F80058676E /* Mock.swift in Sources */,
|
||||||
FD23EA6128ED0B260058676E /* CombineExtensions.swift in Sources */,
|
FD23EA6128ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||||
|
FD9DD2712A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||||
FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */,
|
FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -6157,18 +6170,23 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
FD2AAAF228ED57B500A49611 /* SynchronousStorage.swift in Sources */,
|
FD2AAAF228ED57B500A49611 /* SynchronousStorage.swift in Sources */,
|
||||||
FD96F3A929DBD4AD00401309 /* MockJobRunner.swift in Sources */,
|
|
||||||
FD078E4927E02576000769AF /* CommonMockedExtensions.swift in Sources */,
|
FD078E4927E02576000769AF /* CommonMockedExtensions.swift in Sources */,
|
||||||
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
|
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||||
|
FD9DD2732A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||||
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
|
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
|
||||||
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||||
FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */,
|
FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||||
|
FD23CE352A67C4DA0000B97C /* MockNetwork.swift in Sources */,
|
||||||
|
FD23CE282A67755C0000B97C /* MockCrypto.swift in Sources */,
|
||||||
FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */,
|
FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */,
|
||||||
FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */,
|
FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */,
|
||||||
FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */,
|
FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */,
|
||||||
|
FD23CE2C2A678DF80000B97C /* MockCaches.swift in Sources */,
|
||||||
FD0B77B229B82B7A009169BA /* ArrayUtilitiesSpec.swift in Sources */,
|
FD0B77B229B82B7A009169BA /* ArrayUtilitiesSpec.swift in Sources */,
|
||||||
FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */,
|
FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */,
|
||||||
|
FD23CE262A676B5B0000B97C /* DependenciesSpec.swift in Sources */,
|
||||||
FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */,
|
FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */,
|
||||||
|
FD0969FB2A6A00B100C5C365 /* Mocked.swift in Sources */,
|
||||||
FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */,
|
FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */,
|
||||||
FD2959902A43BE5F00888A17 /* VersionSpec.swift in Sources */,
|
FD2959902A43BE5F00888A17 /* VersionSpec.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
@ -6180,56 +6198,49 @@
|
||||||
files = (
|
files = (
|
||||||
FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */,
|
FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */,
|
||||||
FDDC08F229A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift in Sources */,
|
FDDC08F229A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift in Sources */,
|
||||||
FD859EFA27C2F5C500510D0C /* MockGenericHash.swift in Sources */,
|
|
||||||
FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */,
|
FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */,
|
||||||
FD96F3A529DBC3DC00401309 /* MessageSendJobSpec.swift in Sources */,
|
FD96F3A529DBC3DC00401309 /* MessageSendJobSpec.swift in Sources */,
|
||||||
FDC290B327DFF9F5005DAE71 /* TestOnionRequestAPI.swift in Sources */,
|
|
||||||
FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */,
|
FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */,
|
||||||
FD3C906A27E417CE00CD579F /* SodiumUtilitiesSpec.swift in Sources */,
|
FD3C906A27E417CE00CD579F /* CryptoSMKSpec.swift in Sources */,
|
||||||
FD96F3A729DBD43D00401309 /* MockJobRunner.swift in Sources */,
|
FD96F3A729DBD43D00401309 /* MockJobRunner.swift in Sources */,
|
||||||
FDBB25E12983909300F1508E /* ConfigConvoInfoVolatileSpec.swift in Sources */,
|
FDBB25E12983909300F1508E /* ConfigConvoInfoVolatileSpec.swift in Sources */,
|
||||||
FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */,
|
FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */,
|
||||||
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */,
|
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */,
|
||||||
FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */,
|
FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */,
|
||||||
FD078E4F27E175F1000769AF /* DependencyExtensions.swift in Sources */,
|
|
||||||
FDC2909C27D713D2005DAE71 /* SodiumProtocolsSpec.swift in Sources */,
|
|
||||||
FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */,
|
FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */,
|
||||||
FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */,
|
FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */,
|
||||||
FDC2909A27D71376005DAE71 /* NonceGeneratorSpec.swift in Sources */,
|
FDC2909A27D71376005DAE71 /* NonceGeneratorSpec.swift in Sources */,
|
||||||
FD2AAAF128ED57B500A49611 /* SynchronousStorage.swift in Sources */,
|
FD2AAAF128ED57B500A49611 /* SynchronousStorage.swift in Sources */,
|
||||||
FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */,
|
FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */,
|
||||||
FD859EF827C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift in Sources */,
|
FD23CE2A2A6775660000B97C /* MockCrypto.swift in Sources */,
|
||||||
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */,
|
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */,
|
||||||
FD859EF427C2F49200510D0C /* MockSodium.swift in Sources */,
|
|
||||||
FD078E4D27E17156000769AF /* MockOGMCache.swift in Sources */,
|
FD078E4D27E17156000769AF /* MockOGMCache.swift in Sources */,
|
||||||
FD078E5227E1760A000769AF /* OGMDependencyExtensions.swift in Sources */,
|
FD9DD2722A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||||
FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */,
|
FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */,
|
||||||
FD859EFC27C2F60700510D0C /* MockEd25519.swift in Sources */,
|
|
||||||
FDC290A627D860CE005DAE71 /* Mock.swift in Sources */,
|
FDC290A627D860CE005DAE71 /* Mock.swift in Sources */,
|
||||||
FD2B4AFB29429D1000AB4848 /* ConfigContactsSpec.swift in Sources */,
|
FD2B4AFB29429D1000AB4848 /* ConfigContactsSpec.swift in Sources */,
|
||||||
FDA1E83D29AC71A800C5C3BD /* SessionUtilSpec.swift in Sources */,
|
FDA1E83D29AC71A800C5C3BD /* SessionUtilSpec.swift in Sources */,
|
||||||
FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */,
|
FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||||
FD3C906F27E43E8700CD579F /* MockBox.swift in Sources */,
|
|
||||||
FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */,
|
FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */,
|
||||||
|
FD23CE2E2A678E1E0000B97C /* MockCaches.swift in Sources */,
|
||||||
FD23EA6228ED0B260058676E /* CombineExtensions.swift in Sources */,
|
FD23EA6228ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||||
FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */,
|
FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */,
|
||||||
|
FD23CE342A67C4D90000B97C /* MockNetwork.swift in Sources */,
|
||||||
FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */,
|
FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */,
|
||||||
FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */,
|
FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */,
|
||||||
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||||
FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */,
|
FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */,
|
||||||
|
FD0969F92A69FFE700C5C365 /* Mocked.swift in Sources */,
|
||||||
FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */,
|
FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */,
|
||||||
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */,
|
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */,
|
||||||
FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */,
|
FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */,
|
||||||
FD859EF627C2F52C00510D0C /* MockSign.swift in Sources */,
|
|
||||||
FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */,
|
FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */,
|
||||||
FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */,
|
FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */,
|
||||||
FD078E5A27E29F09000769AF /* MockNonce16Generator.swift in Sources */,
|
|
||||||
FD3C906D27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift in Sources */,
|
FD3C906D27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift in Sources */,
|
||||||
FDC2908D27D70905005DAE71 /* UpdateMessageRequestSpec.swift in Sources */,
|
FDC2908D27D70905005DAE71 /* UpdateMessageRequestSpec.swift in Sources */,
|
||||||
FD078E5427E197CA000769AF /* OpenGroupManagerSpec.swift in Sources */,
|
FD078E5427E197CA000769AF /* OpenGroupManagerSpec.swift in Sources */,
|
||||||
FD3C906727E416AF00CD579F /* BlindedIdLookupSpec.swift in Sources */,
|
FD3C906727E416AF00CD579F /* BlindedIdLookupSpec.swift in Sources */,
|
||||||
FD83B9D227D59495005E1583 /* MockUserDefaults.swift in Sources */,
|
FD83B9D227D59495005E1583 /* MockUserDefaults.swift in Sources */,
|
||||||
FD078E5C27E29F78000769AF /* MockNonce24Generator.swift in Sources */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,15 +35,15 @@ extension ContextMenuVC {
|
||||||
|
|
||||||
// MARK: - Actions
|
// MARK: - Actions
|
||||||
|
|
||||||
static func info(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
static func info(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||||
return Action(
|
return Action(
|
||||||
icon: UIImage(named: "ic_info"),
|
icon: UIImage(named: "ic_info"),
|
||||||
title: "context_menu_info".localized(),
|
title: "context_menu_info".localized(),
|
||||||
accessibilityLabel: "Message info"
|
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(
|
return Action(
|
||||||
icon: UIImage(systemName: "arrow.triangle.2.circlepath"),
|
icon: UIImage(systemName: "arrow.triangle.2.circlepath"),
|
||||||
title: (cellViewModel.state == .failedToSync ?
|
title: (cellViewModel.state == .failedToSync ?
|
||||||
|
@ -51,23 +51,23 @@ extension ContextMenuVC {
|
||||||
"context_menu_resend".localized()
|
"context_menu_resend".localized()
|
||||||
),
|
),
|
||||||
accessibilityLabel: (cellViewModel.state == .failedToSync ? "Resync message" : "Resend message")
|
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(
|
return Action(
|
||||||
icon: UIImage(named: "ic_reply"),
|
icon: UIImage(named: "ic_reply"),
|
||||||
title: "context_menu_reply".localized(),
|
title: "context_menu_reply".localized(),
|
||||||
accessibilityLabel: "Reply to message"
|
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(
|
return Action(
|
||||||
icon: UIImage(named: "ic_copy"),
|
icon: UIImage(named: "ic_copy"),
|
||||||
title: "copy".localized(),
|
title: "copy".localized(),
|
||||||
accessibilityLabel: "Copy text"
|
accessibilityLabel: "Copy text"
|
||||||
) { delegate?.copy(cellViewModel) }
|
) { delegate?.copy(cellViewModel, using: dependencies) }
|
||||||
}
|
}
|
||||||
|
|
||||||
static func copySessionID(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
static func copySessionID(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||||
|
@ -79,50 +79,50 @@ extension ContextMenuVC {
|
||||||
) { delegate?.copySessionID(cellViewModel) }
|
) { delegate?.copySessionID(cellViewModel) }
|
||||||
}
|
}
|
||||||
|
|
||||||
static func delete(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
static func delete(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||||
return Action(
|
return Action(
|
||||||
icon: UIImage(named: "ic_trash"),
|
icon: UIImage(named: "ic_trash"),
|
||||||
title: "TXT_DELETE_TITLE".localized(),
|
title: "TXT_DELETE_TITLE".localized(),
|
||||||
accessibilityLabel: "Delete message"
|
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(
|
return Action(
|
||||||
icon: UIImage(named: "ic_download"),
|
icon: UIImage(named: "ic_download"),
|
||||||
title: "context_menu_save".localized(),
|
title: "context_menu_save".localized(),
|
||||||
accessibilityLabel: "Save attachment"
|
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(
|
return Action(
|
||||||
icon: UIImage(named: "ic_block"),
|
icon: UIImage(named: "ic_block"),
|
||||||
title: "context_menu_ban_user".localized(),
|
title: "context_menu_ban_user".localized(),
|
||||||
accessibilityLabel: "Ban user"
|
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(
|
return Action(
|
||||||
icon: UIImage(named: "ic_block"),
|
icon: UIImage(named: "ic_block"),
|
||||||
title: "context_menu_ban_and_delete_all".localized(),
|
title: "context_menu_ban_and_delete_all".localized(),
|
||||||
accessibilityLabel: "Ban user and delete"
|
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(
|
return Action(
|
||||||
title: emoji.rawValue,
|
title: emoji.rawValue,
|
||||||
isEmojiAction: true
|
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(
|
return Action(
|
||||||
isEmojiPlus: true,
|
isEmojiPlus: true,
|
||||||
accessibilityLabel: "Add emoji"
|
accessibilityLabel: "Add emoji"
|
||||||
) { delegate?.showFullEmojiKeyboard(cellViewModel) }
|
) { delegate?.showFullEmojiKeyboard(cellViewModel, using: dependencies) }
|
||||||
}
|
}
|
||||||
|
|
||||||
static func dismiss(_ delegate: ContextMenuActionDelegate?) -> Action {
|
static func dismiss(_ delegate: ContextMenuActionDelegate?) -> Action {
|
||||||
|
@ -150,7 +150,8 @@ extension ContextMenuVC {
|
||||||
currentUserBlinded25PublicKey: String?,
|
currentUserBlinded25PublicKey: String?,
|
||||||
currentUserIsOpenGroupModerator: Bool,
|
currentUserIsOpenGroupModerator: Bool,
|
||||||
currentThreadIsMessageRequest: Bool,
|
currentThreadIsMessageRequest: Bool,
|
||||||
delegate: ContextMenuActionDelegate?
|
delegate: ContextMenuActionDelegate?,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> [Action]? {
|
) -> [Action]? {
|
||||||
switch cellViewModel.variant {
|
switch cellViewModel.variant {
|
||||||
case .standardIncomingDeleted, .infoCall,
|
case .standardIncomingDeleted, .infoCall,
|
||||||
|
@ -159,7 +160,7 @@ extension ContextMenuVC {
|
||||||
.infoClosedGroupCurrentUserLeft, .infoClosedGroupCurrentUserLeaving, .infoClosedGroupCurrentUserErrorLeaving,
|
.infoClosedGroupCurrentUserLeft, .infoClosedGroupCurrentUserLeaving, .infoClosedGroupCurrentUserErrorLeaving,
|
||||||
.infoMessageRequestAccepted, .infoDisappearingMessagesUpdate:
|
.infoMessageRequestAccepted, .infoDisappearingMessagesUpdate:
|
||||||
// Let the user delete info messages and unsent messages
|
// 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
|
case .standardOutgoing, .standardIncoming: break
|
||||||
}
|
}
|
||||||
|
@ -227,18 +228,21 @@ extension ContextMenuVC {
|
||||||
let shouldShowInfo: Bool = (cellViewModel.attachments?.isEmpty == false)
|
let shouldShowInfo: Bool = (cellViewModel.attachments?.isEmpty == false)
|
||||||
|
|
||||||
let generatedActions: [Action] = [
|
let generatedActions: [Action] = [
|
||||||
(canRetry ? Action.retry(cellViewModel, delegate) : nil),
|
(canRetry ? Action.retry(cellViewModel, delegate, using: dependencies) : nil),
|
||||||
(viewModelCanReply(cellViewModel) ? Action.reply(cellViewModel, delegate) : nil),
|
(viewModelCanReply(cellViewModel) ? Action.reply(cellViewModel, delegate, using: dependencies) : nil),
|
||||||
(canCopy ? Action.copy(cellViewModel, delegate) : nil),
|
(canCopy ? Action.copy(cellViewModel, delegate, using: dependencies) : nil),
|
||||||
(canSave ? Action.save(cellViewModel, delegate) : nil),
|
(canSave ? Action.save(cellViewModel, delegate, using: dependencies) : nil),
|
||||||
(canCopySessionId ? Action.copySessionID(cellViewModel, delegate) : nil),
|
(canCopySessionId ? Action.copySessionID(cellViewModel, delegate) : nil),
|
||||||
(canDelete ? Action.delete(cellViewModel, delegate) : nil),
|
(canDelete ? Action.delete(cellViewModel, delegate, using: dependencies) : nil),
|
||||||
(canBan ? Action.ban(cellViewModel, delegate) : nil),
|
(canBan ? Action.ban(cellViewModel, delegate, using: dependencies) : nil),
|
||||||
(canBan ? Action.banAndDeleteAllMessages(cellViewModel, delegate) : nil),
|
(canBan ? Action.banAndDeleteAllMessages(cellViewModel, delegate, using: dependencies) : nil),
|
||||||
(shouldShowInfo ? Action.info(cellViewModel, delegate) : nil),
|
(shouldShowInfo ? Action.info(cellViewModel, delegate, using: dependencies) : nil),
|
||||||
]
|
]
|
||||||
.appending(contentsOf: (shouldShowEmojiActions ? recentEmojis : []).map { Action.react(cellViewModel, $0, delegate) })
|
.appending(
|
||||||
.appending(Action.emojiPlusButton(cellViewModel, delegate))
|
contentsOf: (shouldShowEmojiActions ? recentEmojis : [])
|
||||||
|
.map { Action.react(cellViewModel, $0, delegate, using: dependencies) }
|
||||||
|
)
|
||||||
|
.appending(Action.emojiPlusButton(cellViewModel, delegate, using: dependencies))
|
||||||
.compactMap { $0 }
|
.compactMap { $0 }
|
||||||
|
|
||||||
guard !generatedActions.isEmpty else { return [] }
|
guard !generatedActions.isEmpty else { return [] }
|
||||||
|
@ -250,16 +254,16 @@ extension ContextMenuVC {
|
||||||
// MARK: - Delegate
|
// MARK: - Delegate
|
||||||
|
|
||||||
protocol ContextMenuActionDelegate {
|
protocol ContextMenuActionDelegate {
|
||||||
func info(_ cellViewModel: MessageViewModel)
|
func info(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||||
func retry(_ cellViewModel: MessageViewModel)
|
func retry(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||||
func reply(_ cellViewModel: MessageViewModel)
|
func reply(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||||
func copy(_ cellViewModel: MessageViewModel)
|
func copy(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||||
func copySessionID(_ cellViewModel: MessageViewModel)
|
func copySessionID(_ cellViewModel: MessageViewModel)
|
||||||
func delete(_ cellViewModel: MessageViewModel)
|
func delete(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||||
func save(_ cellViewModel: MessageViewModel)
|
func save(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||||
func ban(_ cellViewModel: MessageViewModel)
|
func ban(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||||
func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel)
|
func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones)
|
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones, using dependencies: Dependencies)
|
||||||
func showFullEmojiKeyboard(_ cellViewModel: MessageViewModel)
|
func showFullEmojiKeyboard(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||||
func contextMenuDismissed()
|
func contextMenuDismissed()
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,8 +173,8 @@ extension ConversationVC:
|
||||||
|
|
||||||
// MARK: - AttachmentApprovalViewControllerDelegate
|
// MARK: - AttachmentApprovalViewControllerDelegate
|
||||||
|
|
||||||
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) {
|
||||||
sendMessage(text: (messageText ?? ""), attachments: attachments)
|
sendMessage(text: (messageText ?? ""), attachments: attachments, using: dependencies)
|
||||||
resetMentions()
|
resetMentions()
|
||||||
|
|
||||||
dismiss(animated: true) { [weak self] in
|
dismiss(animated: true) { [weak self] in
|
||||||
|
@ -409,7 +409,8 @@ extension ConversationVC:
|
||||||
attachments: [SignalAttachment] = [],
|
attachments: [SignalAttachment] = [],
|
||||||
linkPreviewDraft: LinkPreviewDraft? = nil,
|
linkPreviewDraft: LinkPreviewDraft? = nil,
|
||||||
quoteModel: QuotedReplyModel? = nil,
|
quoteModel: QuotedReplyModel? = nil,
|
||||||
hasPermissionToSendSeed: Bool = false
|
hasPermissionToSendSeed: Bool = false,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
guard !showBlockedModalIfNeeded() else { return }
|
guard !showBlockedModalIfNeeded() else { return }
|
||||||
|
|
||||||
|
@ -488,7 +489,7 @@ extension ConversationVC:
|
||||||
let quoteThumbnailAttachment: Attachment? = quoteModel?.attachment?.cloneAsQuoteThumbnail()
|
let quoteThumbnailAttachment: Attachment? = quoteModel?.attachment?.cloneAsQuoteThumbnail()
|
||||||
|
|
||||||
// Actually send the message
|
// Actually send the message
|
||||||
Storage.shared
|
dependencies.storage
|
||||||
.writePublisher { [weak self] db in
|
.writePublisher { [weak self] db in
|
||||||
// Update the thread to be visible (if it isn't already)
|
// Update the thread to be visible (if it isn't already)
|
||||||
if self?.viewModel.threadData.threadShouldBeVisible == false {
|
if self?.viewModel.threadData.threadShouldBeVisible == false {
|
||||||
|
@ -536,7 +537,8 @@ extension ConversationVC:
|
||||||
db,
|
db,
|
||||||
interaction: insertedInteraction,
|
interaction: insertedInteraction,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: threadVariant
|
threadVariant: threadVariant,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||||
|
@ -787,10 +789,14 @@ extension ConversationVC:
|
||||||
self.contextMenuWindow?.makeKeyAndVisible()
|
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 {
|
guard cellViewModel.variant != .standardOutgoing || (cellViewModel.state != .failed && cellViewModel.state != .failedToSync) else {
|
||||||
// Show the failed message sheet
|
// Show the failed message sheet
|
||||||
showFailedMessageSheet(for: cellViewModel)
|
showFailedMessageSheet(for: cellViewModel, using: dependencies)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -864,8 +870,8 @@ extension ConversationVC:
|
||||||
let threadId: String = self.viewModel.threadData.threadId
|
let threadId: String = self.viewModel.threadData.threadId
|
||||||
|
|
||||||
// Retry downloading the failed attachment
|
// Retry downloading the failed attachment
|
||||||
Storage.shared.writeAsync { db in
|
dependencies.storage.writeAsync { db in
|
||||||
JobRunner.add(
|
dependencies.jobRunner.add(
|
||||||
db,
|
db,
|
||||||
job: Job(
|
job: Job(
|
||||||
variant: .attachmentDownload,
|
variant: .attachmentDownload,
|
||||||
|
@ -874,7 +880,9 @@ extension ConversationVC:
|
||||||
details: AttachmentDownloadJob.Details(
|
details: AttachmentDownloadJob.Details(
|
||||||
attachmentId: mediaView.attachment.id
|
attachmentId: mediaView.attachment.id
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
canStartJob: true,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -1013,8 +1021,8 @@ extension ConversationVC:
|
||||||
self.present(actionSheet, animated: true)
|
self.present(actionSheet, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleReplyButtonTapped(for cellViewModel: MessageViewModel) {
|
func handleReplyButtonTapped(for cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||||
reply(cellViewModel)
|
reply(cellViewModel, using: dependencies)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startThread(with sessionId: String, openGroupServer: String?, openGroupPublicKey: String?) {
|
func startThread(with sessionId: String, openGroupServer: String?, openGroupPublicKey: String?) {
|
||||||
|
@ -1123,15 +1131,15 @@ extension ConversationVC:
|
||||||
UIView.setAnimationsEnabled(true)
|
UIView.setAnimationsEnabled(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones) {
|
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones, using dependencies: Dependencies) {
|
||||||
react(cellViewModel, with: emoji.rawValue, remove: false)
|
react(cellViewModel, with: emoji.rawValue, remove: false, using: dependencies)
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones) {
|
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones, using dependencies: Dependencies) {
|
||||||
react(cellViewModel, with: emoji.rawValue, remove: true)
|
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 }
|
guard cellViewModel.threadVariant == .community else { return }
|
||||||
|
|
||||||
Storage.shared
|
Storage.shared
|
||||||
|
@ -1208,7 +1216,7 @@ extension ConversationVC:
|
||||||
let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant
|
let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant
|
||||||
let openGroupRoom: String? = self.viewModel.threadData.openGroupRoomToken
|
let openGroupRoom: String? = self.viewModel.threadData.openGroupRoomToken
|
||||||
let sentTimestamp: Int64 = SnodeAPI.currentOffsetTimestampMs()
|
let sentTimestamp: Int64 = SnodeAPI.currentOffsetTimestampMs()
|
||||||
let recentReactionTimestamps: [Int64] = dependencies.generalCache.recentReactionTimestamps
|
let recentReactionTimestamps: [Int64] = dependencies.caches[.general].recentReactionTimestamps
|
||||||
|
|
||||||
guard
|
guard
|
||||||
recentReactionTimestamps.count < 20 ||
|
recentReactionTimestamps.count < 20 ||
|
||||||
|
@ -1226,7 +1234,7 @@ extension ConversationVC:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies.mutableGeneralCache.mutate {
|
dependencies.caches.mutate(cache: .general) {
|
||||||
$0.recentReactionTimestamps = Array($0.recentReactionTimestamps
|
$0.recentReactionTimestamps = Array($0.recentReactionTimestamps
|
||||||
.suffix(19))
|
.suffix(19))
|
||||||
.appending(sentTimestamp)
|
.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
|
.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)
|
// Update the thread to be visible (if it isn't already)
|
||||||
if self?.viewModel.threadData.threadShouldBeVisible == false {
|
if self?.viewModel.threadData.threadShouldBeVisible == false {
|
||||||
_ = try SessionThread
|
_ = try SessionThread
|
||||||
|
@ -1372,7 +1380,8 @@ extension ConversationVC:
|
||||||
namespace: try Message.Destination
|
namespace: try Message.Destination
|
||||||
.from(db, threadId: cellViewModel.threadId, threadVariant: cellViewModel.threadVariant)
|
.from(db, threadId: cellViewModel.threadId, threadVariant: cellViewModel.threadVariant)
|
||||||
.defaultNamespace,
|
.defaultNamespace,
|
||||||
interactionId: cellViewModel.id
|
interactionId: cellViewModel.id,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
return (sendData, nil)
|
return (sendData, nil)
|
||||||
|
@ -1382,7 +1391,7 @@ extension ConversationVC:
|
||||||
.tryFlatMap { messageSendData, openGroupInfo -> AnyPublisher<Void, Error> in
|
.tryFlatMap { messageSendData, openGroupInfo -> AnyPublisher<Void, Error> in
|
||||||
switch (messageSendData, openGroupInfo) {
|
switch (messageSendData, openGroupInfo) {
|
||||||
case (.some(let sendData), _):
|
case (.some(let sendData), _):
|
||||||
return MessageSender.sendImmediate(preparedSendData: sendData)
|
return MessageSender.sendImmediate(data: sendData, using: dependencies)
|
||||||
|
|
||||||
case (_, .some(let info)):
|
case (_, .some(let info)):
|
||||||
return OpenGroupAPI.send(data: info.sendData)
|
return OpenGroupAPI.send(data: info.sendData)
|
||||||
|
@ -1433,14 +1442,14 @@ extension ConversationVC:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func showFullEmojiKeyboard(_ cellViewModel: MessageViewModel) {
|
func showFullEmojiKeyboard(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||||
hideInputAccessoryView()
|
hideInputAccessoryView()
|
||||||
|
|
||||||
let emojiPicker = EmojiPickerSheet(
|
let emojiPicker = EmojiPickerSheet(
|
||||||
completionHandler: { [weak self] emoji in
|
completionHandler: { [weak self] emoji in
|
||||||
guard let emoji: EmojiWithSkinTones = emoji else { return }
|
guard let emoji: EmojiWithSkinTones = emoji else { return }
|
||||||
|
|
||||||
self?.react(cellViewModel, with: emoji)
|
self?.react(cellViewModel, with: emoji, using: dependencies)
|
||||||
},
|
},
|
||||||
dismissHandler: { [weak self] in
|
dismissHandler: { [weak self] in
|
||||||
self?.showInputAccessoryView()
|
self?.showInputAccessoryView()
|
||||||
|
@ -1456,7 +1465,7 @@ extension ConversationVC:
|
||||||
|
|
||||||
// MARK: --action handling
|
// MARK: --action handling
|
||||||
|
|
||||||
func showFailedMessageSheet(for cellViewModel: MessageViewModel) {
|
private func showFailedMessageSheet(for cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||||
let sheet = UIAlertController(
|
let sheet = UIAlertController(
|
||||||
title: (cellViewModel.state == .failedToSync ?
|
title: (cellViewModel.state == .failedToSync ?
|
||||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE".localized() :
|
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE".localized() :
|
||||||
|
@ -1483,7 +1492,7 @@ extension ConversationVC:
|
||||||
"context_menu_resend".localized()
|
"context_menu_resend".localized()
|
||||||
),
|
),
|
||||||
style: .default,
|
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
|
// HACK: Extracting this info from the error string is pretty dodgy
|
||||||
|
@ -1596,7 +1605,7 @@ extension ConversationVC:
|
||||||
|
|
||||||
// MARK: - ContextMenuActionDelegate
|
// MARK: - ContextMenuActionDelegate
|
||||||
|
|
||||||
func info(_ cellViewModel: MessageViewModel) {
|
func info(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||||
let mediaInfoVC = MediaInfoVC(
|
let mediaInfoVC = MediaInfoVC(
|
||||||
attachments: (cellViewModel.attachments ?? []),
|
attachments: (cellViewModel.attachments ?? []),
|
||||||
isOutgoing: (cellViewModel.variant == .standardOutgoing),
|
isOutgoing: (cellViewModel.variant == .standardOutgoing),
|
||||||
|
@ -1607,8 +1616,8 @@ extension ConversationVC:
|
||||||
navigationController?.pushViewController(mediaInfoVC, animated: true)
|
navigationController?.pushViewController(mediaInfoVC, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func retry(_ cellViewModel: MessageViewModel) {
|
func retry(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||||
Storage.shared.writeAsync { [weak self] db in
|
dependencies.storage.writeAsync { [weak self] db in
|
||||||
guard
|
guard
|
||||||
let threadId: String = self?.viewModel.threadData.threadId,
|
let threadId: String = self?.viewModel.threadData.threadId,
|
||||||
let threadVariant: SessionThread.Variant = self?.viewModel.threadData.threadVariant,
|
let threadVariant: SessionThread.Variant = self?.viewModel.threadData.threadVariant,
|
||||||
|
@ -1649,12 +1658,13 @@ extension ConversationVC:
|
||||||
interaction: interaction,
|
interaction: interaction,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: threadVariant,
|
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(
|
let maybeQuoteDraft: QuotedReplyModel? = QuotedReplyModel.quotedReplyForSending(
|
||||||
threadId: self.viewModel.threadData.threadId,
|
threadId: self.viewModel.threadData.threadId,
|
||||||
authorId: cellViewModel.authorId,
|
authorId: cellViewModel.authorId,
|
||||||
|
@ -1677,7 +1687,7 @@ extension ConversationVC:
|
||||||
snInputView.becomeFirstResponder()
|
snInputView.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
func copy(_ cellViewModel: MessageViewModel) {
|
func copy(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||||
switch cellViewModel.cellType {
|
switch cellViewModel.cellType {
|
||||||
case .typingIndicator, .dateHeader, .unreadMarker: break
|
case .typingIndicator, .dateHeader, .unreadMarker: break
|
||||||
|
|
||||||
|
@ -1715,7 +1725,7 @@ extension ConversationVC:
|
||||||
UIPasteboard.general.string = cellViewModel.authorId
|
UIPasteboard.general.string = cellViewModel.authorId
|
||||||
}
|
}
|
||||||
|
|
||||||
func delete(_ cellViewModel: MessageViewModel) {
|
func delete(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||||
switch cellViewModel.variant {
|
switch cellViewModel.variant {
|
||||||
case .standardIncomingDeleted, .infoCall,
|
case .standardIncomingDeleted, .infoCall,
|
||||||
.infoScreenshotNotification, .infoMediaSavedNotification,
|
.infoScreenshotNotification, .infoMediaSavedNotification,
|
||||||
|
@ -1911,7 +1921,8 @@ extension ConversationVC:
|
||||||
message: unsendRequest,
|
message: unsendRequest,
|
||||||
threadId: cellViewModel.threadId,
|
threadId: cellViewModel.threadId,
|
||||||
interactionId: nil,
|
interactionId: nil,
|
||||||
to: .contact(publicKey: userPublicKey)
|
to: .contact(publicKey: userPublicKey),
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -1934,7 +1945,8 @@ extension ConversationVC:
|
||||||
message: unsendRequest,
|
message: unsendRequest,
|
||||||
threadId: cellViewModel.threadId,
|
threadId: cellViewModel.threadId,
|
||||||
interactionId: nil,
|
interactionId: nil,
|
||||||
to: .contact(publicKey: userPublicKey)
|
to: .contact(publicKey: userPublicKey),
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
self?.showInputAccessoryView()
|
self?.showInputAccessoryView()
|
||||||
|
@ -1962,7 +1974,8 @@ extension ConversationVC:
|
||||||
message: unsendRequest,
|
message: unsendRequest,
|
||||||
interactionId: nil,
|
interactionId: nil,
|
||||||
threadId: cellViewModel.threadId,
|
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 }
|
guard cellViewModel.cellType == .mediaMessage else { return }
|
||||||
|
|
||||||
let mediaAttachments: [(Attachment, String)] = (cellViewModel.attachments ?? [])
|
let mediaAttachments: [(Attachment, String)] = (cellViewModel.attachments ?? [])
|
||||||
|
@ -2038,24 +2051,10 @@ extension ConversationVC:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let threadId: String = self.viewModel.threadData.threadId
|
sendDataExtraction(kind: .mediaSaved(timestamp: UInt64(cellViewModel.timestampMs)))
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ban(_ cellViewModel: MessageViewModel) {
|
func ban(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||||
guard cellViewModel.threadVariant == .community else { return }
|
guard cellViewModel.threadVariant == .community else { return }
|
||||||
|
|
||||||
let threadId: String = self.viewModel.threadData.threadId
|
let threadId: String = self.viewModel.threadData.threadId
|
||||||
|
@ -2111,7 +2110,7 @@ extension ConversationVC:
|
||||||
self.present(modal, animated: true)
|
self.present(modal, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel) {
|
func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||||
guard cellViewModel.threadVariant == .community else { return }
|
guard cellViewModel.threadVariant == .community else { return }
|
||||||
|
|
||||||
let threadId: String = self.viewModel.threadData.threadId
|
let threadId: String = self.viewModel.threadData.threadId
|
||||||
|
@ -2303,23 +2302,29 @@ extension ConversationVC:
|
||||||
|
|
||||||
// MARK: - Data Extraction Notifications
|
// 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
|
// Only send screenshot notifications to one-to-one conversations
|
||||||
guard self.viewModel.threadData.threadVariant == .contact else { return }
|
guard self.viewModel.threadData.threadVariant == .contact else { return }
|
||||||
|
|
||||||
let threadId: String = self.viewModel.threadData.threadId
|
let threadId: String = self.viewModel.threadData.threadId
|
||||||
let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant
|
let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant
|
||||||
|
|
||||||
Storage.shared.writeAsync { db in
|
dependencies.storage.writeAsync { db in
|
||||||
try MessageSender.send(
|
try MessageSender.send(
|
||||||
db,
|
db,
|
||||||
message: DataExtractionNotification(
|
message: DataExtractionNotification(
|
||||||
kind: .screenshot,
|
kind: kind,
|
||||||
sentTimestamp: UInt64(SnodeAPI.currentOffsetTimestampMs())
|
sentTimestamp: UInt64(SnodeAPI.currentOffsetTimestampMs())
|
||||||
),
|
),
|
||||||
interactionId: nil,
|
interactionId: nil,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: threadVariant
|
threadVariant: threadVariant,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2355,7 +2360,8 @@ extension ConversationVC {
|
||||||
for threadId: String,
|
for threadId: String,
|
||||||
threadVariant: SessionThread.Variant,
|
threadVariant: SessionThread.Variant,
|
||||||
isNewThread: Bool,
|
isNewThread: Bool,
|
||||||
timestampMs: Int64
|
timestampMs: Int64,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
guard threadVariant == .contact else { return }
|
guard threadVariant == .contact else { return }
|
||||||
|
|
||||||
|
@ -2396,7 +2402,8 @@ extension ConversationVC {
|
||||||
),
|
),
|
||||||
interactionId: nil,
|
interactionId: nil,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: threadVariant
|
threadVariant: threadVariant,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,12 +87,18 @@ public class MessageCell: UITableViewCell {
|
||||||
|
|
||||||
protocol MessageCellDelegate: ReactionDelegate {
|
protocol MessageCellDelegate: ReactionDelegate {
|
||||||
func handleItemLongPressed(_ cellViewModel: MessageViewModel)
|
func handleItemLongPressed(_ cellViewModel: MessageViewModel)
|
||||||
func handleItemTapped(_ cellViewModel: MessageViewModel, gestureRecognizer: UITapGestureRecognizer)
|
func handleItemTapped(_ cellViewModel: MessageViewModel, gestureRecognizer: UITapGestureRecognizer, using dependencies: Dependencies)
|
||||||
func handleItemDoubleTapped(_ cellViewModel: MessageViewModel)
|
func handleItemDoubleTapped(_ cellViewModel: MessageViewModel)
|
||||||
func handleItemSwiped(_ cellViewModel: MessageViewModel, state: SwipeState)
|
func handleItemSwiped(_ cellViewModel: MessageViewModel, state: SwipeState)
|
||||||
func openUrl(_ urlString: String)
|
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 startThread(with sessionId: String, openGroupServer: String?, openGroupPublicKey: String?)
|
||||||
func showReactionList(_ cellViewModel: MessageViewModel, selectedReaction: EmojiWithSkinTones?)
|
func showReactionList(_ cellViewModel: MessageViewModel, selectedReaction: EmojiWithSkinTones?)
|
||||||
func needsLayout(for cellViewModel: MessageViewModel, expandingReactions: Bool)
|
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
|
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 }
|
guard let cellViewModel: MessageViewModel = self.viewModel else { return }
|
||||||
|
|
||||||
let location = gestureRecognizer.location(in: self)
|
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 reactionContainerView.convert(reactionView.frame, from: reactionView.superview).contains(convertedLocation) {
|
||||||
|
|
||||||
if reactionView.viewModel.showBorder {
|
if reactionView.viewModel.showBorder {
|
||||||
delegate?.removeReact(cellViewModel, for: reactionView.viewModel.emoji)
|
delegate?.removeReact(cellViewModel, for: reactionView.viewModel.emoji, using: dependencies)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
delegate?.react(cellViewModel, with: reactionView.viewModel.emoji)
|
delegate?.react(cellViewModel, with: reactionView.viewModel.emoji, using: dependencies)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -917,7 +919,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if snContentView.bounds.contains(snContentView.convert(location, from: self)) {
|
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 }
|
guard let cellViewModel: MessageViewModel = self.viewModel else { return }
|
||||||
|
|
||||||
resetReply()
|
resetReply()
|
||||||
delegate?.handleReplyButtonTapped(for: cellViewModel)
|
delegate?.handleReplyButtonTapped(for: cellViewModel, using: dependencies)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Convenience
|
// MARK: - Convenience
|
||||||
|
|
|
@ -39,10 +39,10 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
|
|
||||||
init(
|
init(
|
||||||
dependencies: Dependencies = Dependencies(),
|
|
||||||
threadId: String,
|
threadId: String,
|
||||||
threadVariant: SessionThread.Variant,
|
threadVariant: SessionThread.Variant,
|
||||||
config: DisappearingMessagesConfiguration
|
config: DisappearingMessagesConfiguration,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
self.dependencies = dependencies
|
self.dependencies = dependencies
|
||||||
self.threadId = threadId
|
self.threadId = threadId
|
||||||
|
@ -68,7 +68,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
||||||
currentSelection
|
currentSelection
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.map { [weak self] currentSelection in (self?.storedSelection != currentSelection) }
|
.map { [weak self] currentSelection in (self?.storedSelection != currentSelection) }
|
||||||
.map { isChanged in
|
.map { [weak self, dependencies] isChanged in
|
||||||
guard isChanged else { return [] }
|
guard isChanged else { return [] }
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -76,8 +76,8 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
||||||
id: .save,
|
id: .save,
|
||||||
systemItem: .save,
|
systemItem: .save,
|
||||||
accessibilityIdentifier: "Save button"
|
accessibilityIdentifier: "Save button"
|
||||||
) { [weak self] in
|
) {
|
||||||
self?.saveChanges()
|
self?.saveChanges(using: dependencies)
|
||||||
self?.dismissScreen()
|
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
|
/// 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
|
private lazy var _observableTableData: ObservableData = ValueObservation
|
||||||
.trackingConstantRegion { [weak self, config, dependencies, threadId = self.threadId] db -> [SectionModel] in
|
.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
|
let maybeThreadViewModel: SessionThreadViewModel? = try SessionThreadViewModel
|
||||||
.conversationSettingsQuery(threadId: threadId, userPublicKey: userPublicKey)
|
.conversationSettingsQuery(threadId: threadId, userPublicKey: userPublicKey)
|
||||||
.fetchOne(db)
|
.fetchOne(db)
|
||||||
|
@ -156,7 +156,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
||||||
|
|
||||||
// MARK: - Functions
|
// MARK: - Functions
|
||||||
|
|
||||||
private func saveChanges() {
|
private func saveChanges(using dependencies: Dependencies = Dependencies()) {
|
||||||
let threadId: String = self.threadId
|
let threadId: String = self.threadId
|
||||||
let threadVariant: SessionThread.Variant = self.threadVariant
|
let threadVariant: SessionThread.Variant = self.threadVariant
|
||||||
let currentSelection: TimeInterval = self.currentSelection.value
|
let currentSelection: TimeInterval = self.currentSelection.value
|
||||||
|
@ -195,7 +195,8 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
||||||
),
|
),
|
||||||
interactionId: interaction.id,
|
interactionId: interaction.id,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: threadVariant
|
threadVariant: threadVariant,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
// Legacy closed groups
|
// Legacy closed groups
|
||||||
|
|
|
@ -60,10 +60,10 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
||||||
// MARK: - Initialization
|
// MARK: - Initialization
|
||||||
|
|
||||||
init(
|
init(
|
||||||
dependencies: Dependencies = Dependencies(),
|
|
||||||
threadId: String,
|
threadId: String,
|
||||||
threadVariant: SessionThread.Variant,
|
threadVariant: SessionThread.Variant,
|
||||||
didTriggerSearch: @escaping () -> ()
|
didTriggerSearch: @escaping () -> (),
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
self.dependencies = dependencies
|
self.dependencies = dependencies
|
||||||
self.threadId = threadId
|
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
|
/// 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
|
private lazy var _observableTableData: ObservableData = ValueObservation
|
||||||
.trackingConstantRegion { [weak self, dependencies, threadId = self.threadId, threadVariant = self.threadVariant] db -> [SectionModel] in
|
.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
|
let maybeThreadViewModel: SessionThreadViewModel? = try SessionThreadViewModel
|
||||||
.conversationSettingsQuery(threadId: threadId, userPublicKey: userPublicKey)
|
.conversationSettingsQuery(threadId: threadId, userPublicKey: userPublicKey)
|
||||||
.fetchOne(db)
|
.fetchOne(db)
|
||||||
|
@ -755,7 +755,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
||||||
publicKey: publicKey
|
publicKey: publicKey
|
||||||
)
|
)
|
||||||
|
|
||||||
dependencies.storage.writeAsync { db in
|
dependencies.storage.writeAsync { [dependencies] db in
|
||||||
try selectedUsers.forEach { userId in
|
try selectedUsers.forEach { userId in
|
||||||
let thread: SessionThread = try SessionThread
|
let thread: SessionThread = try SessionThread
|
||||||
.fetchOrCreate(db, id: userId, variant: .contact, shouldBeVisible: nil)
|
.fetchOrCreate(db, id: userId, variant: .contact, shouldBeVisible: nil)
|
||||||
|
@ -786,7 +786,8 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
||||||
db,
|
db,
|
||||||
interaction: interaction,
|
interaction: interaction,
|
||||||
threadId: thread.id,
|
threadId: thread.id,
|
||||||
threadVariant: thread.variant
|
threadVariant: thread.variant,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -368,10 +368,12 @@ final class ReactionListSheet: BaseVC {
|
||||||
dismiss(animated: true, completion: nil)
|
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 }
|
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
|
// MARK: - Delegate
|
||||||
|
|
||||||
protocol ReactionDelegate: AnyObject {
|
protocol ReactionDelegate: AnyObject {
|
||||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones)
|
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones, using dependencies: Dependencies)
|
||||||
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones)
|
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones, using dependencies: Dependencies)
|
||||||
func removeAllReactions(_ cellViewModel: MessageViewModel, for emoji: String)
|
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)
|
dismissSelf(animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc
|
@objc public func didPressShare(_ sender: Any) { share() }
|
||||||
public func didPressShare(_ sender: Any) {
|
|
||||||
|
public func share(using dependencies: Dependencies = Dependencies()) {
|
||||||
guard let currentViewController = self.viewControllers?[0] as? MediaDetailViewController else {
|
guard let currentViewController = self.viewControllers?[0] as? MediaDetailViewController else {
|
||||||
owsFailDebug("currentViewController was unexpectedly nil")
|
owsFailDebug("currentViewController was unexpectedly nil")
|
||||||
return
|
return
|
||||||
|
@ -553,7 +554,8 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
||||||
),
|
),
|
||||||
interactionId: nil, // Show no interaction for the current user
|
interactionId: nil, // Show no interaction for the current user
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: threadVariant
|
threadVariant: threadVariant,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -418,7 +418,7 @@ extension SendMediaNavigationController: AttachmentApprovalViewControllerDelegat
|
||||||
attachmentDraftCollection.remove(attachment: attachment)
|
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)
|
sendMediaNavDelegate?.sendMediaNav(self, didApproveAttachments: attachments, forThreadId: threadId, messageText: messageText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -553,7 +553,8 @@ class NotificationActionHandler {
|
||||||
func reply(
|
func reply(
|
||||||
userInfo: [AnyHashable: Any],
|
userInfo: [AnyHashable: Any],
|
||||||
replyText: String,
|
replyText: String,
|
||||||
applicationState: UIApplication.State
|
applicationState: UIApplication.State,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
guard let threadId = userInfo[AppNotificationUserInfoKey.threadId] as? String else {
|
guard let threadId = userInfo[AppNotificationUserInfoKey.threadId] as? String else {
|
||||||
return Fail<Void, Error>(error: NotificationError.failDebug("threadId was unexpectedly nil"))
|
return Fail<Void, Error>(error: NotificationError.failDebug("threadId was unexpectedly nil"))
|
||||||
|
@ -599,10 +600,11 @@ class NotificationActionHandler {
|
||||||
db,
|
db,
|
||||||
interaction: interaction,
|
interaction: interaction,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: thread.variant
|
threadVariant: thread.variant,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||||
.handleEvents(
|
.handleEvents(
|
||||||
receiveCompletion: { result in
|
receiveCompletion: { result in
|
||||||
switch result {
|
switch result {
|
||||||
|
|
|
@ -20,7 +20,7 @@ public enum SyncPushTokensJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, 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
|
// 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 {
|
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
||||||
|
|
|
@ -26,7 +26,10 @@ enum Onboarding {
|
||||||
return existingPublisher
|
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
|
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
|
||||||
guard SessionUtil.userConfigsEnabled else {
|
guard SessionUtil.userConfigsEnabled else {
|
||||||
return Just(nil)
|
return Just(nil)
|
||||||
|
@ -99,7 +102,8 @@ enum Onboarding {
|
||||||
)
|
)
|
||||||
}(),
|
}(),
|
||||||
sentTimestamp: TimeInterval((message.sentTimestamp ?? 0) / 1000),
|
sentTimestamp: TimeInterval((message.sentTimestamp ?? 0) / 1000),
|
||||||
calledFromConfigHandling: false
|
calledFromConfigHandling: false,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return ()
|
return ()
|
||||||
|
|
|
@ -244,7 +244,7 @@ final class NukeDataModal: Modal {
|
||||||
UserDefaults.removeAll()
|
UserDefaults.removeAll()
|
||||||
|
|
||||||
// Remove the cached key so it gets re-cached on next access
|
// Remove the cached key so it gets re-cached on next access
|
||||||
dependencies.mutableGeneralCache.mutate {
|
dependencies.caches.mutate(cache: .general) {
|
||||||
$0.encodedPublicKey = nil
|
$0.encodedPublicKey = nil
|
||||||
$0.recentReactionTimestamps = []
|
$0.recentReactionTimestamps = []
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,7 @@ public final class BackgroundPoller {
|
||||||
|
|
||||||
public static func poll(
|
public static func poll(
|
||||||
completionHandler: @escaping (UIBackgroundFetchResult) -> Void,
|
completionHandler: @escaping (UIBackgroundFetchResult) -> Void,
|
||||||
dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies(
|
using dependencies: Dependencies = Dependencies()
|
||||||
subscribeQueue: .global(qos: .background),
|
|
||||||
receiveQueue: .main
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
Publishers
|
Publishers
|
||||||
.MergeMany(
|
.MergeMany(
|
||||||
|
@ -55,8 +52,8 @@ public final class BackgroundPoller {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.subscribe(on: dependencies.subscribeQueue)
|
.subscribe(on: DispatchQueue.global(qos: .background), using: dependencies)
|
||||||
.receive(on: dependencies.receiveQueue)
|
.receive(on: DispatchQueue.main, using: dependencies)
|
||||||
.collect()
|
.collect()
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
receiveCompletion: { result in
|
receiveCompletion: { result in
|
||||||
|
@ -74,7 +71,7 @@ public final class BackgroundPoller {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func pollForMessages(
|
private static func pollForMessages(
|
||||||
using dependencies: OpenGroupManager.OGMDependencies
|
using dependencies: Dependencies
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
let userPublicKey: String = getUserHexEncodedPublicKey()
|
let userPublicKey: String = getUserHexEncodedPublicKey()
|
||||||
|
|
||||||
|
@ -94,7 +91,7 @@ public final class BackgroundPoller {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func pollForClosedGroupMessages(
|
private static func pollForClosedGroupMessages(
|
||||||
using dependencies: OpenGroupManager.OGMDependencies
|
using dependencies: Dependencies
|
||||||
) -> [AnyPublisher<Void, Error>] {
|
) -> [AnyPublisher<Void, Error>] {
|
||||||
// Fetch all closed groups (excluding any don't contain the current user as a
|
// 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)
|
// GroupMemeber as the user is no longer a member of those)
|
||||||
|
|
|
@ -124,13 +124,14 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
message: CallMessage,
|
message: CallMessage,
|
||||||
interactionId: Int64?,
|
interactionId: Int64?,
|
||||||
in thread: SessionThread
|
in thread: SessionThread,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> AnyPublisher<Void, Error> {
|
) throws -> AnyPublisher<Void, Error> {
|
||||||
SNLog("[Calls] Sending pre-offer message.")
|
SNLog("[Calls] Sending pre-offer message.")
|
||||||
|
|
||||||
return MessageSender
|
return MessageSender
|
||||||
.sendImmediate(
|
.sendImmediate(
|
||||||
preparedSendData: try MessageSender
|
data: try MessageSender
|
||||||
.preparedSendData(
|
.preparedSendData(
|
||||||
db,
|
db,
|
||||||
message: message,
|
message: message,
|
||||||
|
@ -138,8 +139,10 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
||||||
namespace: try Message.Destination
|
namespace: try Message.Destination
|
||||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||||
.defaultNamespace,
|
.defaultNamespace,
|
||||||
interactionId: interactionId
|
interactionId: interactionId,
|
||||||
)
|
using: dependencies
|
||||||
|
),
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
.handleEvents(receiveOutput: { _ in SNLog("[Calls] Pre-offer message has been sent.") })
|
.handleEvents(receiveOutput: { _ in SNLog("[Calls] Pre-offer message has been sent.") })
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
@ -147,7 +150,8 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
||||||
|
|
||||||
public func sendOffer(
|
public func sendOffer(
|
||||||
to thread: SessionThread,
|
to thread: SessionThread,
|
||||||
isRestartingICEConnection: Bool = false
|
isRestartingICEConnection: Bool = false,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
SNLog("[Calls] Sending offer message.")
|
SNLog("[Calls] Sending offer message.")
|
||||||
let uuid: String = self.uuid
|
let uuid: String = self.uuid
|
||||||
|
@ -172,7 +176,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage.shared
|
dependencies.storage
|
||||||
.writePublisher { db in
|
.writePublisher { db in
|
||||||
try MessageSender
|
try MessageSender
|
||||||
.preparedSendData(
|
.preparedSendData(
|
||||||
|
@ -188,10 +192,11 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
||||||
namespace: try Message.Destination
|
namespace: try Message.Destination
|
||||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||||
.defaultNamespace,
|
.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))
|
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
receiveCompletion: { result in
|
receiveCompletion: { result in
|
||||||
|
@ -207,12 +212,15 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
||||||
.eraseToAnyPublisher()
|
.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.")
|
SNLog("[Calls] Sending answer message.")
|
||||||
let uuid: String = self.uuid
|
let uuid: String = self.uuid
|
||||||
let mediaConstraints: RTCMediaConstraints = mediaConstraints(false)
|
let mediaConstraints: RTCMediaConstraints = mediaConstraints(false)
|
||||||
|
|
||||||
return Storage.shared
|
return dependencies.storage
|
||||||
.readPublisher { db -> SessionThread in
|
.readPublisher { db -> SessionThread in
|
||||||
guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId) else {
|
guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId) else {
|
||||||
throw WebRTCSessionError.noThread
|
throw WebRTCSessionError.noThread
|
||||||
|
@ -239,7 +247,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage.shared
|
dependencies.storage
|
||||||
.writePublisher { db in
|
.writePublisher { db in
|
||||||
try MessageSender
|
try MessageSender
|
||||||
.preparedSendData(
|
.preparedSendData(
|
||||||
|
@ -254,10 +262,11 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
||||||
namespace: try Message.Destination
|
namespace: try Message.Destination
|
||||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||||
.defaultNamespace,
|
.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))
|
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
receiveCompletion: { result in
|
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 candidates: [RTCIceCandidate] = self.queuedICECandidates
|
||||||
let uuid: String = self.uuid
|
let uuid: String = self.uuid
|
||||||
let contactSessionId: String = self.contactSessionId
|
let contactSessionId: String = self.contactSessionId
|
||||||
|
@ -291,7 +300,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
||||||
// Empty the queue
|
// Empty the queue
|
||||||
self.queuedICECandidates.removeAll()
|
self.queuedICECandidates.removeAll()
|
||||||
|
|
||||||
Storage.shared
|
dependencies.storage
|
||||||
.writePublisher { db in
|
.writePublisher { db in
|
||||||
guard let thread: SessionThread = try SessionThread.fetchOne(db, id: contactSessionId) else {
|
guard let thread: SessionThread = try SessionThread.fetchOne(db, id: contactSessionId) else {
|
||||||
throw WebRTCSessionError.noThread
|
throw WebRTCSessionError.noThread
|
||||||
|
@ -315,15 +324,20 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
||||||
namespace: try Message.Destination
|
namespace: try Message.Destination
|
||||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||||
.defaultNamespace,
|
.defaultNamespace,
|
||||||
interactionId: nil
|
interactionId: nil,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||||
.sinkUntilComplete()
|
.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 }
|
guard let thread: SessionThread = try SessionThread.fetchOne(db, id: sessionId) else { return }
|
||||||
|
|
||||||
SNLog("[Calls] Sending end call message.")
|
SNLog("[Calls] Sending end call message.")
|
||||||
|
@ -340,11 +354,12 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
||||||
namespace: try Message.Destination
|
namespace: try Message.Destination
|
||||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||||
.defaultNamespace,
|
.defaultNamespace,
|
||||||
interactionId: nil
|
interactionId: nil,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
MessageSender
|
MessageSender
|
||||||
.sendImmediate(preparedSendData: preparedSendData)
|
.sendImmediate(data: preparedSendData, using: dependencies)
|
||||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||||
.sinkUntilComplete()
|
.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
|
// This can occur if an AttachmnetUploadJob was explicitly created for a message
|
||||||
// dependant on the attachment being uploaded (in this case the attachment has
|
// dependant on the attachment being uploaded (in this case the attachment has
|
||||||
// already been uploaded so just succeed)
|
// already been uploaded so just succeed)
|
||||||
|
|
|
@ -76,7 +76,7 @@ public extension BlindedIdLookup {
|
||||||
openGroupServer: String,
|
openGroupServer: String,
|
||||||
openGroupPublicKey: String,
|
openGroupPublicKey: String,
|
||||||
isCheckingForOutbox: Bool,
|
isCheckingForOutbox: Bool,
|
||||||
dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> BlindedIdLookup {
|
) throws -> BlindedIdLookup {
|
||||||
var lookup: BlindedIdLookup = (try? BlindedIdLookup
|
var lookup: BlindedIdLookup = (try? BlindedIdLookup
|
||||||
.fetchOne(db, id: blindedId))
|
.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 we we given a sessionId then validate it is correct and if so save it
|
||||||
if
|
if
|
||||||
let sessionId: String = sessionId,
|
let sessionId: String = sessionId,
|
||||||
dependencies.sodium.sessionId(
|
dependencies.crypto.verify(
|
||||||
sessionId,
|
.sessionId(
|
||||||
matchesBlindedId: blindedId,
|
sessionId,
|
||||||
serverPublicKey: openGroupPublicKey,
|
matchesBlindedId: blindedId,
|
||||||
genericHash: dependencies.genericHash
|
serverPublicKey: openGroupPublicKey,
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
lookup = try lookup
|
lookup = try lookup
|
||||||
|
@ -115,9 +117,16 @@ public extension BlindedIdLookup {
|
||||||
.fetchCursor(db)
|
.fetchCursor(db)
|
||||||
|
|
||||||
while let contact: Contact = try contactsThatApprovedMeCursor.next() {
|
while let contact: Contact = try contactsThatApprovedMeCursor.next() {
|
||||||
guard dependencies.sodium.sessionId(contact.id, matchesBlindedId: blindedId, serverPublicKey: openGroupPublicKey, genericHash: dependencies.genericHash) else {
|
guard
|
||||||
continue
|
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
|
// We found a match so update the lookup and leave the loop
|
||||||
lookup = try lookup
|
lookup = try lookup
|
||||||
|
@ -151,11 +160,13 @@ public extension BlindedIdLookup {
|
||||||
while let otherLookup: BlindedIdLookup = try blindedIdLookupCursor.next() {
|
while let otherLookup: BlindedIdLookup = try blindedIdLookupCursor.next() {
|
||||||
guard
|
guard
|
||||||
let sessionId: String = otherLookup.sessionId,
|
let sessionId: String = otherLookup.sessionId,
|
||||||
dependencies.sodium.sessionId(
|
dependencies.crypto.verify(
|
||||||
sessionId,
|
.sessionId(
|
||||||
matchesBlindedId: blindedId,
|
sessionId,
|
||||||
serverPublicKey: openGroupPublicKey,
|
matchesBlindedId: blindedId,
|
||||||
genericHash: dependencies.genericHash
|
serverPublicKey: openGroupPublicKey,
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else { continue }
|
else { continue }
|
||||||
|
|
||||||
|
|
|
@ -55,12 +55,12 @@ public struct Contact: Codable, Identifiable, Equatable, FetchableRecord, Persis
|
||||||
isBlocked: Bool = false,
|
isBlocked: Bool = false,
|
||||||
didApproveMe: Bool = false,
|
didApproveMe: Bool = false,
|
||||||
hasBeenBlocked: Bool = false,
|
hasBeenBlocked: Bool = false,
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.isTrusted = (
|
self.isTrusted = (
|
||||||
isTrusted ||
|
isTrusted ||
|
||||||
id == getUserHexEncodedPublicKey(dependencies: dependencies) // Always trust ourselves
|
id == getUserHexEncodedPublicKey(using: dependencies) // Always trust ourselves
|
||||||
)
|
)
|
||||||
self.isApproved = isApproved
|
self.isApproved = isApproved
|
||||||
self.isBlocked = isBlocked
|
self.isBlocked = isBlocked
|
||||||
|
|
|
@ -797,22 +797,19 @@ public extension Interaction {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
threadId: String,
|
threadId: String,
|
||||||
body: String?,
|
body: String?,
|
||||||
quoteAuthorId: String? = nil
|
quoteAuthorId: String? = nil,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
var publicKeysToCheck: [String] = [
|
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 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) {
|
if let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: threadId) {
|
||||||
let sodium: Sodium = Sodium()
|
|
||||||
|
|
||||||
if
|
if
|
||||||
let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
||||||
let blindedKeyPair: KeyPair = sodium.blindedKeyPair(
|
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
|
||||||
serverPublicKey: openGroup.publicKey,
|
.blindedKeyPair(serverPublicKey: openGroup.publicKey, edKeyPair: userEd25519KeyPair, using: dependencies)
|
||||||
edKeyPair: userEd25519KeyPair,
|
|
||||||
genericHash: sodium.genericHash
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
publicKeysToCheck.append(SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString)
|
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,
|
/// **Note:** This method intentionally does **not** save the newly created Profile,
|
||||||
/// it will need to be explicitly saved after calling
|
/// 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)
|
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||||
|
|
||||||
guard let db: Database = db else {
|
guard let db: Database = db else {
|
||||||
|
|
|
@ -525,12 +525,19 @@ public extension SessionThread {
|
||||||
_ db: Database? = nil,
|
_ db: Database? = nil,
|
||||||
threadId: String,
|
threadId: String,
|
||||||
threadVariant: Variant,
|
threadVariant: Variant,
|
||||||
blindingPrefix: SessionId.Prefix
|
blindingPrefix: SessionId.Prefix,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> String? {
|
) -> String? {
|
||||||
guard threadVariant == .community else { return nil }
|
guard threadVariant == .community else { return nil }
|
||||||
guard let db: Database = db else {
|
guard let db: Database = db else {
|
||||||
return Storage.shared.read { db in
|
return dependencies.storage.read { db in
|
||||||
getUserHexEncodedBlindedKey(db, threadId: threadId, threadVariant: threadVariant, blindingPrefix: blindingPrefix)
|
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 }
|
guard capabilities.isEmpty || capabilities.contains(.blind) else { return nil }
|
||||||
|
|
||||||
let sodium: Sodium = Sodium()
|
let blindedKeyPair: KeyPair? = dependencies.crypto.generate(
|
||||||
|
.blindedKeyPair(serverPublicKey: openGroupInfo.publicKey, edKeyPair: userEdKeyPair, using: dependencies)
|
||||||
let blindedKeyPair: KeyPair? = sodium.blindedKeyPair(
|
|
||||||
serverPublicKey: openGroupInfo.publicKey,
|
|
||||||
edKeyPair: userEdKeyPair,
|
|
||||||
genericHash: sodium.getGenericHash()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return blindedKeyPair.map { keyPair -> String in
|
return blindedKeyPair.map { keyPair -> String in
|
||||||
|
|
|
@ -17,7 +17,7 @@ public enum AttachmentDownloadJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let threadId: String = job.threadId,
|
let threadId: String = job.threadId,
|
||||||
|
|
|
@ -17,7 +17,7 @@ public enum AttachmentUploadJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let threadId: String = job.threadId,
|
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
|
// 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
|
// will attempt to update the state of the job immediately
|
||||||
attachment
|
attachment
|
||||||
.upload(to: (openGroup.map { .openGroup($0) } ?? .fileServer))
|
.upload(to: (openGroup.map { .openGroup($0) } ?? .fileServer), using: dependencies)
|
||||||
.subscribe(on: queue)
|
.subscribe(on: queue)
|
||||||
.receive(on: queue)
|
.receive(on: queue)
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
|
@ -95,7 +95,8 @@ public enum AttachmentUploadJob: JobExecutor {
|
||||||
message: details.message,
|
message: details.message,
|
||||||
with: .other(error),
|
with: .other(error),
|
||||||
interactionId: interactionId,
|
interactionId: interactionId,
|
||||||
isSyncMessage: details.isSyncMessage
|
isSyncMessage: details.isSyncMessage,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ public enum ConfigMessageReceiveJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, 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
|
/// 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
|
/// 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) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
SessionUtil.userConfigsEnabled,
|
SessionUtil.userConfigsEnabled,
|
||||||
|
@ -43,7 +43,7 @@ public enum ConfigurationSyncJob: JobExecutor {
|
||||||
// it again immediately which is pointless)
|
// it again immediately which is pointless)
|
||||||
let updatedJob: Job? = dependencies.storage.write { db in
|
let updatedJob: Job? = dependencies.storage.write { db in
|
||||||
try job
|
try job
|
||||||
.with(nextRunTimestamp: Date().timeIntervalSince1970 + maxRunFrequency)
|
.with(nextRunTimestamp: dependencies.dateNow.timeIntervalSince1970 + maxRunFrequency)
|
||||||
.saved(db)
|
.saved(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ public enum ConfigurationSyncJob: JobExecutor {
|
||||||
.map { $0.obsoleteHashes }
|
.map { $0.obsoleteHashes }
|
||||||
.reduce([], +)
|
.reduce([], +)
|
||||||
.asSet()
|
.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")")
|
SNLog("[ConfigurationSyncJob] For \(publicKey) started with \(pendingConfigChanges.count) change\(pendingConfigChanges.count == 1 ? "" : "s")")
|
||||||
|
|
||||||
dependencies.storage
|
dependencies.storage
|
||||||
|
@ -105,7 +105,8 @@ public enum ConfigurationSyncJob: JobExecutor {
|
||||||
|
|
||||||
return (snodeMessage, namespace)
|
return (snodeMessage, namespace)
|
||||||
},
|
},
|
||||||
allObsoleteHashes: Array(allObsoleteHashes)
|
allObsoleteHashes: Array(allObsoleteHashes),
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.subscribe(on: queue)
|
.subscribe(on: queue)
|
||||||
|
@ -223,7 +224,7 @@ public extension ConfigurationSyncJob {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
canStartJob: true,
|
canStartJob: true,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -231,16 +232,16 @@ public extension ConfigurationSyncJob {
|
||||||
// Upsert a config sync job if needed
|
// Upsert a config sync job if needed
|
||||||
dependencies.jobRunner.upsert(
|
dependencies.jobRunner.upsert(
|
||||||
db,
|
db,
|
||||||
job: ConfigurationSyncJob.createIfNeeded(db, publicKey: publicKey, dependencies: dependencies),
|
job: ConfigurationSyncJob.createIfNeeded(db, publicKey: publicKey, using: dependencies),
|
||||||
canStartJob: true,
|
canStartJob: true,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult static func createIfNeeded(
|
@discardableResult static func createIfNeeded(
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
publicKey: String,
|
publicKey: String,
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> Job? {
|
) -> Job? {
|
||||||
/// The ConfigurationSyncJob will automatically reschedule itself to run again after 3 seconds so if there is an existing
|
/// 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
|
/// 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
|
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
|
||||||
guard SessionUtil.userConfigsEnabled else {
|
guard SessionUtil.userConfigsEnabled else {
|
||||||
return Storage.shared
|
return Storage.shared
|
||||||
|
@ -276,17 +277,18 @@ public extension ConfigurationSyncJob {
|
||||||
// fresh install due to the migrations getting run)
|
// fresh install due to the migrations getting run)
|
||||||
guard Identity.userCompletedRequiredOnboarding(db) else { throw StorageError.generic }
|
guard Identity.userCompletedRequiredOnboarding(db) else { throw StorageError.generic }
|
||||||
|
|
||||||
let publicKey: String = getUserHexEncodedPublicKey(db)
|
let publicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||||
|
|
||||||
return try MessageSender.preparedSendData(
|
return try MessageSender.preparedSendData(
|
||||||
db,
|
db,
|
||||||
message: try ConfigurationMessage.getCurrent(db),
|
message: try ConfigurationMessage.getCurrent(db),
|
||||||
to: Message.Destination.contact(publicKey: publicKey),
|
to: Message.Destination.contact(publicKey: publicKey),
|
||||||
namespace: .default,
|
namespace: .default,
|
||||||
interactionId: nil
|
interactionId: nil,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,7 +300,8 @@ public extension ConfigurationSyncJob {
|
||||||
queue: .global(qos: .userInitiated),
|
queue: .global(qos: .userInitiated),
|
||||||
success: { _, _, _ in resolver(Result.success(())) },
|
success: { _, _, _ in resolver(Result.success(())) },
|
||||||
failure: { _, error, _, _ in resolver(Result.failure(error ?? HTTPError.generic)) },
|
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) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
// The 'backgroundTask' gets captured and cleared within the 'completion' block
|
// The 'backgroundTask' gets captured and cleared within the 'completion' block
|
||||||
let timestampNowMs: TimeInterval = TimeInterval(SnodeAPI.currentOffsetTimestampMs())
|
let timestampNowMs: TimeInterval = TimeInterval(SnodeAPI.currentOffsetTimestampMs())
|
||||||
|
|
|
@ -16,7 +16,7 @@ public enum FailedAttachmentDownloadsJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
var changeCount: Int = -1
|
var changeCount: Int = -1
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ public enum FailedMessageSendsJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
var changeCount: Int = -1
|
var changeCount: Int = -1
|
||||||
var attachmentChangeCount: Int = -1
|
var attachmentChangeCount: Int = -1
|
||||||
|
|
|
@ -23,7 +23,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, 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)
|
/// 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) }?
|
.map { try? JSONDecoder().decode(Details.self, from: $0) }?
|
||||||
.typesToCollect)
|
.typesToCollect)
|
||||||
.defaulting(to: Types.allCases)
|
.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
|
/// 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
|
/// 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
|
/// want to prevent it running to frequently (the app becomes active if a system alert, the notification center or the control panel
|
||||||
/// are shown)
|
/// are shown)
|
||||||
let lastGarbageCollection: Date = UserDefaults.standard[.lastGarbageCollection]
|
let lastGarbageCollection: Date = dependencies.standardUserDefaults[.lastGarbageCollection]
|
||||||
.defaulting(to: Date.distantPast)
|
.defaulting(to: Date.distantPast)
|
||||||
let finalTypesToCollect: Set<Types> = {
|
let finalTypesToCollect: Set<Types> = {
|
||||||
guard
|
guard
|
||||||
job.behaviour != .recurringOnActive ||
|
job.behaviour != .recurringOnActive ||
|
||||||
Date().timeIntervalSince(lastGarbageCollection) > (23 * 60 * 60)
|
dependencies.dateNow.timeIntervalSince(lastGarbageCollection) > (23 * 60 * 60)
|
||||||
else {
|
else {
|
||||||
// Note: This should only contain the `Types` which are unlikely to ever cause
|
// Note: This should only contain the `Types` which are unlikely to ever cause
|
||||||
// a startup delay (ie. avoid mass deletions and file management)
|
// 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
|
// If we did a full collection then update the 'lastGarbageCollection' date to
|
||||||
// prevent a full collection from running again in the next 23 hours
|
// prevent a full collection from running again in the next 23 hours
|
||||||
if job.behaviour == .recurringOnActive && Date().timeIntervalSince(lastGarbageCollection) > (23 * 60 * 60) {
|
if job.behaviour == .recurringOnActive && dependencies.dateNow.timeIntervalSince(lastGarbageCollection) > (23 * 60 * 60) {
|
||||||
UserDefaults.standard[.lastGarbageCollection] = Date()
|
dependencies.standardUserDefaults[.lastGarbageCollection] = dependencies.dateNow
|
||||||
}
|
}
|
||||||
|
|
||||||
success(job, false, dependencies)
|
success(job, false, dependencies)
|
||||||
|
|
|
@ -18,7 +18,7 @@ public enum GroupLeavingJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let detailsData: Data = job.details,
|
let detailsData: Data = job.details,
|
||||||
|
@ -51,10 +51,11 @@ public enum GroupLeavingJob: JobExecutor {
|
||||||
to: destination,
|
to: destination,
|
||||||
namespace: destination.defaultNamespace,
|
namespace: destination.defaultNamespace,
|
||||||
interactionId: job.interactionId,
|
interactionId: job.interactionId,
|
||||||
isSyncMessage: false
|
isSyncMessage: false,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||||
.subscribe(on: queue)
|
.subscribe(on: queue)
|
||||||
.receive(on: queue)
|
.receive(on: queue)
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
|
|
|
@ -15,7 +15,7 @@ public enum MessageReceiveJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let threadId: String = job.threadId,
|
let threadId: String = job.threadId,
|
||||||
|
|
|
@ -18,7 +18,7 @@ public enum MessageSendJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let detailsData: Data = job.details,
|
let detailsData: Data = job.details,
|
||||||
|
@ -90,6 +90,10 @@ public enum MessageSendJob: JobExecutor {
|
||||||
switch attachment.state {
|
switch attachment.state {
|
||||||
case .uploading, .pendingDownload, .downloading, .failedUpload, .downloaded:
|
case .uploading, .pendingDownload, .downloading, .failedUpload, .downloaded:
|
||||||
return true
|
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
|
default: return false
|
||||||
}
|
}
|
||||||
|
@ -122,7 +126,7 @@ public enum MessageSendJob: JobExecutor {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.compactMap { attachmentId -> (jobId: Int64, job: Job)? in
|
.compactMap { attachmentId -> (jobId: Int64, job: Job)? in
|
||||||
JobRunner
|
dependencies.jobRunner
|
||||||
.insert(
|
.insert(
|
||||||
db,
|
db,
|
||||||
job: Job(
|
job: Job(
|
||||||
|
@ -171,13 +175,14 @@ public enum MessageSendJob: JobExecutor {
|
||||||
to: details.destination,
|
to: details.destination,
|
||||||
namespace: details.destination.defaultNamespace,
|
namespace: details.destination.defaultNamespace,
|
||||||
interactionId: job.interactionId,
|
interactionId: job.interactionId,
|
||||||
isSyncMessage: details.isSyncMessage
|
isSyncMessage: details.isSyncMessage,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.map { sendData in sendData.with(fileIds: messageFileIds) }
|
.map { sendData in sendData.with(fileIds: messageFileIds) }
|
||||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||||
.subscribe(on: queue)
|
.subscribe(on: queue, using: dependencies)
|
||||||
.receive(on: queue)
|
.receive(on: queue, using: dependencies)
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
receiveCompletion: { result in
|
receiveCompletion: { result in
|
||||||
switch result {
|
switch result {
|
||||||
|
|
|
@ -16,7 +16,7 @@ public enum NotifyPushServerJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let detailsData: Data = job.details,
|
let detailsData: Data = job.details,
|
||||||
|
|
|
@ -15,7 +15,7 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
// Don't run when inactive or not in main app
|
// Don't run when inactive or not in main app
|
||||||
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
||||||
|
|
|
@ -17,7 +17,7 @@ public enum SendReadReceiptsJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
guard
|
guard
|
||||||
let threadId: String = job.threadId,
|
let threadId: String = job.threadId,
|
||||||
|
@ -47,7 +47,7 @@ public enum SendReadReceiptsJob: JobExecutor {
|
||||||
isSyncMessage: false
|
isSyncMessage: false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||||
.subscribe(on: queue)
|
.subscribe(on: queue)
|
||||||
.receive(on: queue)
|
.receive(on: queue)
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
|
@ -59,7 +59,7 @@ public enum SendReadReceiptsJob: JobExecutor {
|
||||||
// another one for the same thread but with a 'nextRunTimestamp' set to the
|
// another one for the same thread but with a 'nextRunTimestamp' set to the
|
||||||
// 'maxRunFrequency' value to throttle the read receipt requests
|
// 'maxRunFrequency' value to throttle the read receipt requests
|
||||||
var shouldFinishCurrentJob: Bool = false
|
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
|
let updatedJob: Job? = Storage.shared.write { db in
|
||||||
// If another 'sendReadReceipts' job was scheduled then update that one
|
// If another 'sendReadReceipts' job was scheduled then update that one
|
||||||
|
|
|
@ -15,7 +15,7 @@ public enum UpdateProfilePictureJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, Dependencies) -> (),
|
deferred: @escaping (Job, Dependencies) -> (),
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
// Don't run when inactive or not in main app
|
// Don't run when inactive or not in main app
|
||||||
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
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
|
// Only re-upload the profile picture if enough time has passed since the last upload
|
||||||
guard
|
guard
|
||||||
let lastProfilePictureUpload: Date = UserDefaults.standard[.lastProfilePictureUpload],
|
let lastProfilePictureUpload: Date = dependencies.standardUserDefaults[.lastProfilePictureUpload],
|
||||||
Date().timeIntervalSince(lastProfilePictureUpload) > (14 * 24 * 60 * 60)
|
dependencies.dateNow.timeIntervalSince(lastProfilePictureUpload) > (14 * 24 * 60 * 60)
|
||||||
else {
|
else {
|
||||||
// Reset the `nextRunTimestamp` value just in case the last run failed so we don't get stuck
|
// Reset the `nextRunTimestamp` value just in case the last run failed so we don't get stuck
|
||||||
// in a loop endlessly deferring the job
|
// in a loop endlessly deferring the job
|
||||||
|
@ -42,7 +42,7 @@ public enum UpdateProfilePictureJob: JobExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: The user defaults flag is updated in ProfileManager
|
// 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
|
let profilePictureData: Data? = profile.profilePictureFileName
|
||||||
.map { ProfileManager.loadProfileData(with: $0) }
|
.map { ProfileManager.loadProfileData(with: $0) }
|
||||||
|
|
||||||
|
|
|
@ -230,7 +230,8 @@ public extension Message {
|
||||||
|
|
||||||
static func processRawReceivedMessage(
|
static func processRawReceivedMessage(
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
rawMessage: SnodeReceivedMessage
|
rawMessage: SnodeReceivedMessage,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> ProcessedMessage? {
|
) throws -> ProcessedMessage? {
|
||||||
guard let envelope = SNProtoEnvelope.from(rawMessage) else {
|
guard let envelope = SNProtoEnvelope.from(rawMessage) else {
|
||||||
throw MessageReceiverError.invalidMessage
|
throw MessageReceiverError.invalidMessage
|
||||||
|
@ -242,7 +243,8 @@ public extension Message {
|
||||||
envelope: envelope,
|
envelope: envelope,
|
||||||
serverExpirationTimestamp: (TimeInterval(rawMessage.info.expirationDateMs) / 1000),
|
serverExpirationTimestamp: (TimeInterval(rawMessage.info.expirationDateMs) / 1000),
|
||||||
serverHash: rawMessage.info.hash,
|
serverHash: rawMessage.info.hash,
|
||||||
handleClosedGroupKeyUpdateMessages: true
|
handleClosedGroupKeyUpdateMessages: true,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure we actually want to de-dupe messages for this namespace, otherwise just
|
// Ensure we actually want to de-dupe messages for this namespace, otherwise just
|
||||||
|
@ -289,7 +291,8 @@ public extension Message {
|
||||||
static func processRawReceivedMessage(
|
static func processRawReceivedMessage(
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
serializedData: Data,
|
serializedData: Data,
|
||||||
serverHash: String?
|
serverHash: String?,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> ProcessedMessage? {
|
) throws -> ProcessedMessage? {
|
||||||
guard let envelope = try? SNProtoEnvelope.parseData(serializedData) else {
|
guard let envelope = try? SNProtoEnvelope.parseData(serializedData) else {
|
||||||
throw MessageReceiverError.invalidMessage
|
throw MessageReceiverError.invalidMessage
|
||||||
|
@ -303,7 +306,8 @@ public extension Message {
|
||||||
ControlMessageProcessRecord.defaultExpirationSeconds
|
ControlMessageProcessRecord.defaultExpirationSeconds
|
||||||
),
|
),
|
||||||
serverHash: serverHash,
|
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)
|
/// closed group key update messages (the `NotificationServiceExtension` does this itself)
|
||||||
static func processRawReceivedMessageAsNotification(
|
static func processRawReceivedMessageAsNotification(
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
envelope: SNProtoEnvelope
|
envelope: SNProtoEnvelope,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> ProcessedMessage? {
|
) throws -> ProcessedMessage? {
|
||||||
let processedMessage: ProcessedMessage? = try processRawReceivedMessage(
|
let processedMessage: ProcessedMessage? = try processRawReceivedMessage(
|
||||||
db,
|
db,
|
||||||
|
@ -322,7 +327,8 @@ public extension Message {
|
||||||
ControlMessageProcessRecord.defaultExpirationSeconds
|
ControlMessageProcessRecord.defaultExpirationSeconds
|
||||||
),
|
),
|
||||||
serverHash: nil,
|
serverHash: nil,
|
||||||
handleClosedGroupKeyUpdateMessages: false
|
handleClosedGroupKeyUpdateMessages: false,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
return processedMessage
|
return processedMessage
|
||||||
|
@ -334,7 +340,7 @@ public extension Message {
|
||||||
openGroupServerPublicKey: String,
|
openGroupServerPublicKey: String,
|
||||||
message: OpenGroupAPI.Message,
|
message: OpenGroupAPI.Message,
|
||||||
data: Data,
|
data: Data,
|
||||||
dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> ProcessedMessage? {
|
) throws -> ProcessedMessage? {
|
||||||
// Need a sender in order to process the message
|
// Need a sender in order to process the message
|
||||||
guard let sender: String = message.sender, let timestamp = message.posted else { return nil }
|
guard let sender: String = message.sender, let timestamp = message.posted else { return nil }
|
||||||
|
@ -357,7 +363,7 @@ public extension Message {
|
||||||
openGroupMessageServerId: message.id,
|
openGroupMessageServerId: message.id,
|
||||||
openGroupServerPublicKey: openGroupServerPublicKey,
|
openGroupServerPublicKey: openGroupServerPublicKey,
|
||||||
handleClosedGroupKeyUpdateMessages: false,
|
handleClosedGroupKeyUpdateMessages: false,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,7 +374,7 @@ public extension Message {
|
||||||
data: Data,
|
data: Data,
|
||||||
isOutgoing: Bool? = nil,
|
isOutgoing: Bool? = nil,
|
||||||
otherBlindedPublicKey: String? = nil,
|
otherBlindedPublicKey: String? = nil,
|
||||||
dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> ProcessedMessage? {
|
) throws -> ProcessedMessage? {
|
||||||
// Note: The `posted` value is in seconds but all messages in the database use milliseconds for timestamps
|
// 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)))
|
let envelopeBuilder = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: UInt64(floor(message.posted * 1000)))
|
||||||
|
@ -390,7 +396,7 @@ public extension Message {
|
||||||
isOutgoing: isOutgoing,
|
isOutgoing: isOutgoing,
|
||||||
otherBlindedPublicKey: otherBlindedPublicKey,
|
otherBlindedPublicKey: otherBlindedPublicKey,
|
||||||
handleClosedGroupKeyUpdateMessages: false,
|
handleClosedGroupKeyUpdateMessages: false,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,24 +405,26 @@ public extension Message {
|
||||||
openGroupId: String,
|
openGroupId: String,
|
||||||
message: OpenGroupAPI.Message,
|
message: OpenGroupAPI.Message,
|
||||||
associatedPendingChanges: [OpenGroupAPI.PendingChange],
|
associatedPendingChanges: [OpenGroupAPI.PendingChange],
|
||||||
dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> [Reaction] {
|
) -> [Reaction] {
|
||||||
var results: [Reaction] = []
|
var results: [Reaction] = []
|
||||||
guard let reactions = message.reactions else { return results }
|
guard let reactions = message.reactions else { return results }
|
||||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||||
let blinded15UserPublicKey: String? = SessionThread
|
let blinded15UserPublicKey: String? = SessionThread
|
||||||
.getUserHexEncodedBlindedKey(
|
.getUserHexEncodedBlindedKey(
|
||||||
db,
|
db,
|
||||||
threadId: openGroupId,
|
threadId: openGroupId,
|
||||||
threadVariant: .community,
|
threadVariant: .community,
|
||||||
blindingPrefix: .blinded15
|
blindingPrefix: .blinded15,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
let blinded25UserPublicKey: String? = SessionThread
|
let blinded25UserPublicKey: String? = SessionThread
|
||||||
.getUserHexEncodedBlindedKey(
|
.getUserHexEncodedBlindedKey(
|
||||||
db,
|
db,
|
||||||
threadId: openGroupId,
|
threadId: openGroupId,
|
||||||
threadVariant: .community,
|
threadVariant: .community,
|
||||||
blindingPrefix: .blinded25
|
blindingPrefix: .blinded25,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
for (encodedEmoji, rawReaction) in reactions {
|
for (encodedEmoji, rawReaction) in reactions {
|
||||||
|
@ -536,7 +544,7 @@ public extension Message {
|
||||||
isOutgoing: Bool? = nil,
|
isOutgoing: Bool? = nil,
|
||||||
otherBlindedPublicKey: String? = nil,
|
otherBlindedPublicKey: String? = nil,
|
||||||
handleClosedGroupKeyUpdateMessages: Bool,
|
handleClosedGroupKeyUpdateMessages: Bool,
|
||||||
dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) throws -> ProcessedMessage? {
|
) throws -> ProcessedMessage? {
|
||||||
let (message, proto, threadId, threadVariant) = try MessageReceiver.parse(
|
let (message, proto, threadId, threadVariant) = try MessageReceiver.parse(
|
||||||
db,
|
db,
|
||||||
|
@ -547,7 +555,7 @@ public extension Message {
|
||||||
openGroupServerPublicKey: openGroupServerPublicKey,
|
openGroupServerPublicKey: openGroupServerPublicKey,
|
||||||
isOutgoing: isOutgoing,
|
isOutgoing: isOutgoing,
|
||||||
otherBlindedPublicKey: otherBlindedPublicKey,
|
otherBlindedPublicKey: otherBlindedPublicKey,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
message.serverHash = serverHash
|
message.serverHash = serverHash
|
||||||
|
|
||||||
|
@ -568,7 +576,8 @@ public extension Message {
|
||||||
db,
|
db,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: threadVariant,
|
threadVariant: threadVariant,
|
||||||
message: closedGroupControlMessage
|
message: closedGroupControlMessage,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
return nil
|
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: [:])
|
info = HTTP.ResponseInfo(code: 0, headers: [:])
|
||||||
data = [:]
|
data = [:]
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ extension OpenGroupAPI.Message {
|
||||||
guard let sender: String = maybeSender, let data = Data(base64Encoded: base64EncodedData), let signature = Data(base64Encoded: base64EncodedSignature) else {
|
guard let sender: String = maybeSender, let data = Data(base64Encoded: base64EncodedData), let signature = Data(base64Encoded: base64EncodedSignature) else {
|
||||||
throw HTTPError.parsingFailed
|
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
|
throw HTTPError.parsingFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,13 +78,21 @@ extension OpenGroupAPI.Message {
|
||||||
|
|
||||||
switch SessionId.Prefix(from: sender) {
|
switch SessionId.Prefix(from: sender) {
|
||||||
case .blinded15, .blinded25:
|
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.")
|
SNLog("Ignoring message with invalid signature.")
|
||||||
throw HTTPError.parsingFailed
|
throw HTTPError.parsingFailed
|
||||||
}
|
}
|
||||||
|
|
||||||
case .standard, .unblinded:
|
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.")
|
SNLog("Ignoring message with invalid signature.")
|
||||||
throw HTTPError.parsingFailed
|
throw HTTPError.parsingFailed
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ public enum OpenGroupAPI {
|
||||||
server: String,
|
server: String,
|
||||||
hasPerformedInitialPoll: Bool,
|
hasPerformedInitialPoll: Bool,
|
||||||
timeSinceLastPoll: TimeInterval,
|
timeSinceLastPoll: TimeInterval,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<BatchResponse> {
|
) throws -> PreparedSendData<BatchResponse> {
|
||||||
let lastInboxMessageId: Int64 = (try? OpenGroup
|
let lastInboxMessageId: Int64 = (try? OpenGroup
|
||||||
.select(.inboxLatestMessageId)
|
.select(.inboxLatestMessageId)
|
||||||
|
@ -143,7 +143,7 @@ public enum OpenGroupAPI {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
server: String,
|
server: String,
|
||||||
requests: [ErasedPreparedSendData],
|
requests: [ErasedPreparedSendData],
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<BatchResponse> {
|
) throws -> PreparedSendData<BatchResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -173,7 +173,7 @@ public enum OpenGroupAPI {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
server: String,
|
server: String,
|
||||||
requests: [ErasedPreparedSendData],
|
requests: [ErasedPreparedSendData],
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<BatchResponse> {
|
) throws -> PreparedSendData<BatchResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -202,7 +202,7 @@ public enum OpenGroupAPI {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
server: String,
|
server: String,
|
||||||
forceBlinded: Bool = false,
|
forceBlinded: Bool = false,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<Capabilities> {
|
) throws -> PreparedSendData<Capabilities> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -225,7 +225,7 @@ public enum OpenGroupAPI {
|
||||||
public static func preparedRooms(
|
public static func preparedRooms(
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
server: String,
|
server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<[Room]> {
|
) throws -> PreparedSendData<[Room]> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -244,7 +244,7 @@ public enum OpenGroupAPI {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
for roomToken: String,
|
for roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<Room> {
|
) throws -> PreparedSendData<Room> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -267,7 +267,7 @@ public enum OpenGroupAPI {
|
||||||
lastUpdated: Int64,
|
lastUpdated: Int64,
|
||||||
for roomToken: String,
|
for roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<RoomPollInfo> {
|
) throws -> PreparedSendData<RoomPollInfo> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -292,7 +292,7 @@ public enum OpenGroupAPI {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
for roomToken: String,
|
for roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<CapabilitiesAndRoomResponse> {
|
) throws -> PreparedSendData<CapabilitiesAndRoomResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.preparedSequence(
|
.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
|
/// 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
|
/// methods for the documented behaviour of each method
|
||||||
public static func preparedCapabilitiesAndRooms(
|
public static func preparedCapabilitiesAndRooms(
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<(capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room]))> {
|
) throws -> PreparedSendData<CapabilitiesAndRoomsResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.preparedSequence(
|
.preparedSequence(
|
||||||
db,
|
db,
|
||||||
|
@ -351,7 +356,7 @@ public enum OpenGroupAPI {
|
||||||
],
|
],
|
||||||
using: dependencies
|
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 maybeCapabilities: HTTP.BatchSubResponse<Capabilities>? = (response[.capabilities] as? HTTP.BatchSubResponse<Capabilities>)
|
||||||
let maybeRooms: HTTP.BatchSubResponse<[Room]>? = response.data
|
let maybeRooms: HTTP.BatchSubResponse<[Room]>? = response.data
|
||||||
.first(where: { key, _ in
|
.first(where: { key, _ in
|
||||||
|
@ -387,7 +392,7 @@ public enum OpenGroupAPI {
|
||||||
whisperTo: String?,
|
whisperTo: String?,
|
||||||
whisperMods: Bool,
|
whisperMods: Bool,
|
||||||
fileIds: [String]?,
|
fileIds: [String]?,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<Message> {
|
) throws -> PreparedSendData<Message> {
|
||||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else {
|
guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else {
|
||||||
throw OpenGroupAPIError.signingFailed
|
throw OpenGroupAPIError.signingFailed
|
||||||
|
@ -419,7 +424,7 @@ public enum OpenGroupAPI {
|
||||||
id: Int64,
|
id: Int64,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<Message> {
|
) throws -> PreparedSendData<Message> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -443,7 +448,7 @@ public enum OpenGroupAPI {
|
||||||
fileIds: [Int64]?,
|
fileIds: [Int64]?,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<NoResponse> {
|
) throws -> PreparedSendData<NoResponse> {
|
||||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else {
|
guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else {
|
||||||
throw OpenGroupAPIError.signingFailed
|
throw OpenGroupAPIError.signingFailed
|
||||||
|
@ -473,7 +478,7 @@ public enum OpenGroupAPI {
|
||||||
id: Int64,
|
id: Int64,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<NoResponse> {
|
) throws -> PreparedSendData<NoResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -497,7 +502,7 @@ public enum OpenGroupAPI {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<[Failable<Message>]> {
|
) throws -> PreparedSendData<[Failable<Message>]> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -526,7 +531,7 @@ public enum OpenGroupAPI {
|
||||||
messageId: Int64,
|
messageId: Int64,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<[Failable<Message>]> {
|
) throws -> PreparedSendData<[Failable<Message>]> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -555,7 +560,7 @@ public enum OpenGroupAPI {
|
||||||
seqNo: Int64,
|
seqNo: Int64,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<[Failable<Message>]> {
|
) throws -> PreparedSendData<[Failable<Message>]> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -591,7 +596,7 @@ public enum OpenGroupAPI {
|
||||||
sessionId: String,
|
sessionId: String,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<NoResponse> {
|
) throws -> PreparedSendData<NoResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -615,7 +620,7 @@ public enum OpenGroupAPI {
|
||||||
id: Int64,
|
id: Int64,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<NoResponse> {
|
) throws -> PreparedSendData<NoResponse> {
|
||||||
/// URL(String:) won't convert raw emojis, so need to do a little encoding here.
|
/// 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
|
/// The raw emoji will come back when calling url.path
|
||||||
|
@ -646,7 +651,7 @@ public enum OpenGroupAPI {
|
||||||
id: Int64,
|
id: Int64,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<ReactionAddResponse> {
|
) throws -> PreparedSendData<ReactionAddResponse> {
|
||||||
/// URL(String:) won't convert raw emojis, so need to do a little encoding here.
|
/// 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
|
/// The raw emoji will come back when calling url.path
|
||||||
|
@ -675,7 +680,7 @@ public enum OpenGroupAPI {
|
||||||
id: Int64,
|
id: Int64,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<ReactionRemoveResponse> {
|
) throws -> PreparedSendData<ReactionRemoveResponse> {
|
||||||
/// URL(String:) won't convert raw emojis, so need to do a little encoding here.
|
/// 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
|
/// The raw emoji will come back when calling url.path
|
||||||
|
@ -705,7 +710,7 @@ public enum OpenGroupAPI {
|
||||||
id: Int64,
|
id: Int64,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<ReactionRemoveAllResponse> {
|
) throws -> PreparedSendData<ReactionRemoveAllResponse> {
|
||||||
/// URL(String:) won't convert raw emojis, so need to do a little encoding here.
|
/// 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
|
/// The raw emoji will come back when calling url.path
|
||||||
|
@ -743,7 +748,7 @@ public enum OpenGroupAPI {
|
||||||
id: Int64,
|
id: Int64,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<NoResponse> {
|
) throws -> PreparedSendData<NoResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -766,7 +771,7 @@ public enum OpenGroupAPI {
|
||||||
id: Int64,
|
id: Int64,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<NoResponse> {
|
) throws -> PreparedSendData<NoResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -788,7 +793,7 @@ public enum OpenGroupAPI {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<NoResponse> {
|
) throws -> PreparedSendData<NoResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -817,7 +822,7 @@ public enum OpenGroupAPI {
|
||||||
fileName: String? = nil,
|
fileName: String? = nil,
|
||||||
to roomToken: String,
|
to roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<FileUploadResponse> {
|
) throws -> PreparedSendData<FileUploadResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -849,7 +854,7 @@ public enum OpenGroupAPI {
|
||||||
fileId: String,
|
fileId: String,
|
||||||
from roomToken: String,
|
from roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<Data> {
|
) throws -> PreparedSendData<Data> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -872,7 +877,7 @@ public enum OpenGroupAPI {
|
||||||
public static func preparedInbox(
|
public static func preparedInbox(
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<[DirectMessage]?> {
|
) throws -> PreparedSendData<[DirectMessage]?> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -893,7 +898,7 @@ public enum OpenGroupAPI {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
id: Int64,
|
id: Int64,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<[DirectMessage]?> {
|
) throws -> PreparedSendData<[DirectMessage]?> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -915,7 +920,7 @@ public enum OpenGroupAPI {
|
||||||
ciphertext: Data,
|
ciphertext: Data,
|
||||||
toInboxFor blindedSessionId: String,
|
toInboxFor blindedSessionId: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) throws -> PreparedSendData<SendDirectMessageResponse> {
|
) throws -> PreparedSendData<SendDirectMessageResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -939,7 +944,7 @@ public enum OpenGroupAPI {
|
||||||
public static func preparedOutbox(
|
public static func preparedOutbox(
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<[DirectMessage]?> {
|
) throws -> PreparedSendData<[DirectMessage]?> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -960,7 +965,7 @@ public enum OpenGroupAPI {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
id: Int64,
|
id: Int64,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<[DirectMessage]?> {
|
) throws -> PreparedSendData<[DirectMessage]?> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -1013,7 +1018,7 @@ public enum OpenGroupAPI {
|
||||||
for timeout: TimeInterval? = nil,
|
for timeout: TimeInterval? = nil,
|
||||||
from roomTokens: [String]? = nil,
|
from roomTokens: [String]? = nil,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<NoResponse> {
|
) throws -> PreparedSendData<NoResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -1062,7 +1067,7 @@ public enum OpenGroupAPI {
|
||||||
sessionId: String,
|
sessionId: String,
|
||||||
from roomTokens: [String]?,
|
from roomTokens: [String]?,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<NoResponse> {
|
) throws -> PreparedSendData<NoResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.prepareSendData(
|
.prepareSendData(
|
||||||
|
@ -1140,7 +1145,7 @@ public enum OpenGroupAPI {
|
||||||
visible: Bool,
|
visible: Bool,
|
||||||
for roomTokens: [String]?,
|
for roomTokens: [String]?,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<NoResponse> {
|
) throws -> PreparedSendData<NoResponse> {
|
||||||
guard (moderator != nil && admin == nil) || (moderator == nil && admin != nil) else {
|
guard (moderator != nil && admin == nil) || (moderator == nil && admin != nil) else {
|
||||||
throw HTTPError.generic
|
throw HTTPError.generic
|
||||||
|
@ -1173,7 +1178,7 @@ public enum OpenGroupAPI {
|
||||||
sessionId: String,
|
sessionId: String,
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<BatchResponse> {
|
) throws -> PreparedSendData<BatchResponse> {
|
||||||
return try OpenGroupAPI
|
return try OpenGroupAPI
|
||||||
.preparedSequence(
|
.preparedSequence(
|
||||||
|
@ -1208,7 +1213,7 @@ public enum OpenGroupAPI {
|
||||||
for serverName: String,
|
for serverName: String,
|
||||||
fallbackSigningType signingType: SessionId.Prefix,
|
fallbackSigningType signingType: SessionId.Prefix,
|
||||||
forceBlinded: Bool = false,
|
forceBlinded: Bool = false,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) -> (publicKey: String, signature: Bytes)? {
|
) -> (publicKey: String, signature: Bytes)? {
|
||||||
guard
|
guard
|
||||||
let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
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 we have no capabilities or if the server supports blinded keys then sign using the blinded key
|
||||||
if forceBlinded || capabilities.isEmpty || capabilities.contains(.blind) {
|
if forceBlinded || capabilities.isEmpty || capabilities.contains(.blind) {
|
||||||
guard let blindedKeyPair: KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else {
|
guard
|
||||||
return nil
|
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
|
||||||
}
|
.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, using: dependencies)
|
||||||
|
),
|
||||||
guard let signatureResult: Bytes = dependencies.sodium.sogsSignature(message: messageBytes, secretKey: userEdKeyPair.secretKey, blindedSecretKey: blindedKeyPair.secretKey, blindedPublicKey: blindedKeyPair.publicKey) else {
|
let signatureResult: Bytes = try? dependencies.crypto.perform(
|
||||||
return nil
|
.sogsSignature(message: messageBytes, secretKey: userEdKeyPair.secretKey, blindedSecretKey: blindedKeyPair.secretKey, blindedPublicKey: blindedKeyPair.publicKey)
|
||||||
}
|
)
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
publicKey: SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString,
|
publicKey: SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString,
|
||||||
|
@ -1245,9 +1251,11 @@ public enum OpenGroupAPI {
|
||||||
// Otherwise sign using the fallback type
|
// Otherwise sign using the fallback type
|
||||||
switch signingType {
|
switch signingType {
|
||||||
case .unblinded:
|
case .unblinded:
|
||||||
guard let signatureResult: Bytes = dependencies.sign.signature(message: messageBytes, secretKey: userEdKeyPair.secretKey) else {
|
guard
|
||||||
return nil
|
let signatureResult: Bytes = try? dependencies.crypto.perform(
|
||||||
}
|
.signature(message: messageBytes, secretKey: userEdKeyPair.secretKey)
|
||||||
|
)
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
publicKey: SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString,
|
publicKey: SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString,
|
||||||
|
@ -1256,10 +1264,12 @@ public enum OpenGroupAPI {
|
||||||
|
|
||||||
// Default to using the 'standard' key
|
// Default to using the 'standard' key
|
||||||
default:
|
default:
|
||||||
guard let userKeyPair: KeyPair = Identity.fetchUserKeyPair(db) else { return nil }
|
guard
|
||||||
guard let signatureResult: Bytes = try? dependencies.ed25519.sign(data: messageBytes, keyPair: userKeyPair) else {
|
let userKeyPair: KeyPair = Identity.fetchUserKeyPair(db),
|
||||||
return nil
|
let signatureResult: Bytes = try? dependencies.crypto.perform(
|
||||||
}
|
.signEd25519(data: messageBytes, keyPair: userKeyPair)
|
||||||
|
)
|
||||||
|
else { return nil }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
publicKey: SessionId(.standard, publicKey: userKeyPair.publicKey).hexString,
|
publicKey: SessionId(.standard, publicKey: userKeyPair.publicKey).hexString,
|
||||||
|
@ -1275,7 +1285,7 @@ public enum OpenGroupAPI {
|
||||||
for serverName: String,
|
for serverName: String,
|
||||||
with serverPublicKey: String,
|
with serverPublicKey: String,
|
||||||
forceBlinded: Bool = false,
|
forceBlinded: Bool = false,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> URLRequest? {
|
) -> URLRequest? {
|
||||||
guard let url: URL = request.url else { return nil }
|
guard let url: URL = request.url else { return nil }
|
||||||
|
|
||||||
|
@ -1283,12 +1293,12 @@ public enum OpenGroupAPI {
|
||||||
let path: String = url.path
|
let path: String = url.path
|
||||||
.appending(url.query.map { value in "?\(value)" })
|
.appending(url.query.map { value in "?\(value)" })
|
||||||
let method: String = (request.httpMethod ?? "GET")
|
let method: String = (request.httpMethod ?? "GET")
|
||||||
let timestamp: Int = Int(floor(dependencies.date.timeIntervalSince1970))
|
let timestamp: Int = Int(floor(dependencies.dateNow.timeIntervalSince1970))
|
||||||
let nonce: Data = Data(dependencies.nonceGenerator16.nonce())
|
|
||||||
let serverPublicKeyData: Data = Data(hex: serverPublicKey)
|
let serverPublicKeyData: Data = Data(hex: serverPublicKey)
|
||||||
|
|
||||||
guard
|
guard
|
||||||
!serverPublicKeyData.isEmpty,
|
!serverPublicKeyData.isEmpty,
|
||||||
|
let nonce: Data = (try? dependencies.crypto.perform(.generateNonce16())).map({ Data($0) }),
|
||||||
let timestampBytes: Bytes = "\(timestamp)".data(using: .ascii)?.bytes
|
let timestampBytes: Bytes = "\(timestamp)".data(using: .ascii)?.bytes
|
||||||
else { return nil }
|
else { return nil }
|
||||||
|
|
||||||
|
@ -1296,7 +1306,7 @@ public enum OpenGroupAPI {
|
||||||
let bodyHash: Bytes? = {
|
let bodyHash: Bytes? = {
|
||||||
guard let body: Data = request.httpBody else { return nil }
|
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
|
/// Generate the signature message
|
||||||
|
@ -1341,7 +1351,7 @@ public enum OpenGroupAPI {
|
||||||
responseType: R.Type,
|
responseType: R.Type,
|
||||||
forceBlinded: Bool = false,
|
forceBlinded: Bool = false,
|
||||||
timeout: TimeInterval = HTTP.defaultTimeout,
|
timeout: TimeInterval = HTTP.defaultTimeout,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData<R> {
|
) throws -> PreparedSendData<R> {
|
||||||
let urlRequest: URLRequest = try request.generateUrlRequest()
|
let urlRequest: URLRequest = try request.generateUrlRequest()
|
||||||
let maybePublicKey: String? = try? OpenGroup
|
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
|
/// This method takes in the `PreparedSendData<R>` and actually sends it to the API
|
||||||
public static func send<R>(
|
public static func send<R>(
|
||||||
data: PreparedSendData<R>?,
|
data: PreparedSendData<R>?,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<(ResponseInfoType, R), Error> {
|
) -> AnyPublisher<(ResponseInfoType, R), Error> {
|
||||||
guard let validData: PreparedSendData<R> = data else {
|
guard let validData: PreparedSendData<R> = data else {
|
||||||
return Fail(error: OpenGroupAPIError.invalidPreparedData)
|
return Fail(error: OpenGroupAPIError.invalidPreparedData)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
return dependencies.onionApi
|
return dependencies.network
|
||||||
.sendOnionRequest(
|
.send(
|
||||||
validData.request,
|
.onionRequest(
|
||||||
to: validData.server,
|
validData.request,
|
||||||
with: validData.publicKey,
|
to: validData.server,
|
||||||
timeout: validData.timeout
|
with: validData.publicKey,
|
||||||
|
timeout: validData.timeout
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.decoded(with: validData, using: dependencies)
|
.decoded(with: validData, using: dependencies)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
|
@ -12,48 +12,17 @@ import SessionSnodeKit
|
||||||
public final class OpenGroupManager {
|
public final class OpenGroupManager {
|
||||||
public typealias DefaultRoomInfo = (room: OpenGroupAPI.Room, existingImageData: Data?)
|
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
|
// MARK: - Variables
|
||||||
|
|
||||||
public static let shared: OpenGroupManager = OpenGroupManager()
|
public static let shared: OpenGroupManager = OpenGroupManager()
|
||||||
|
|
||||||
// MARK: - Polling
|
// 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
|
// Run on the 'workQueue' to ensure any 'Atomic' access doesn't block the main thread
|
||||||
// on startup
|
// on startup
|
||||||
OpenGroupAPI.workQueue.async {
|
OpenGroupAPI.workQueue.async(using: dependencies) {
|
||||||
guard !dependencies.cache.isPolling else { return }
|
guard !dependencies.caches[.openGroupManager].isPolling else { return }
|
||||||
|
|
||||||
let servers: Set<String> = dependencies.storage
|
let servers: Set<String> = dependencies.storage
|
||||||
.read { db in
|
.read { db in
|
||||||
|
@ -70,7 +39,7 @@ public final class OpenGroupManager {
|
||||||
.defaulting(to: [])
|
.defaulting(to: [])
|
||||||
|
|
||||||
// Update the cache state and re-create all of the pollers
|
// 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.isPolling = true
|
||||||
cache.pollers = servers
|
cache.pollers = servers
|
||||||
.reduce(into: [:]) { result, server in
|
.reduce(into: [:]) { result, server in
|
||||||
|
@ -80,13 +49,14 @@ public final class OpenGroupManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that the pollers have been created actually start them
|
// 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()) {
|
public func stopPolling(using dependencies: Dependencies = Dependencies()) {
|
||||||
dependencies.mutableCache.mutate {
|
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||||
$0.pollers.forEach { (_, openGroupPoller) in openGroupPoller.stop() }
|
$0.pollers.forEach { _, openGroupPoller in openGroupPoller.stop() }
|
||||||
$0.pollers.removeAll()
|
$0.pollers.removeAll()
|
||||||
$0.isPolling = false
|
$0.isPolling = false
|
||||||
}
|
}
|
||||||
|
@ -132,7 +102,13 @@ public final class OpenGroupManager {
|
||||||
return options.contains(serverHost)
|
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 }
|
guard let serverUrl: URL = URL(string: server.lowercased()) else { return false }
|
||||||
|
|
||||||
let serverPort: String = OpenGroupManager.port(for: server, serverUrl: serverUrl)
|
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
|
// 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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,10 +163,10 @@ public final class OpenGroupManager {
|
||||||
server: String,
|
server: String,
|
||||||
publicKey: String,
|
publicKey: String,
|
||||||
calledFromConfigHandling: Bool,
|
calledFromConfigHandling: Bool,
|
||||||
dependencies: OGMDependencies = OGMDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
// If we are currently polling for this server and already have a TSGroupThread for this room the do nothing
|
// 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)")
|
SNLog("Ignoring join open group attempt (already joined), user initiated: \(!calledFromConfigHandling)")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -256,7 +232,7 @@ public final class OpenGroupManager {
|
||||||
server: String,
|
server: String,
|
||||||
publicKey: String,
|
publicKey: String,
|
||||||
calledFromConfigHandling: Bool,
|
calledFromConfigHandling: Bool,
|
||||||
dependencies: OGMDependencies = OGMDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
guard successfullyAddedGroup else {
|
guard successfullyAddedGroup else {
|
||||||
return Just(())
|
return Just(())
|
||||||
|
@ -311,7 +287,7 @@ public final class OpenGroupManager {
|
||||||
publicKey: publicKey,
|
publicKey: publicKey,
|
||||||
for: roomToken,
|
for: roomToken,
|
||||||
on: targetServer,
|
on: targetServer,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
) {
|
) {
|
||||||
resolver(Result.success(()))
|
resolver(Result.success(()))
|
||||||
}
|
}
|
||||||
|
@ -322,7 +298,7 @@ public final class OpenGroupManager {
|
||||||
receiveCompletion: { result in
|
receiveCompletion: { result in
|
||||||
switch result {
|
switch result {
|
||||||
case .finished: break
|
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,
|
_ db: Database,
|
||||||
openGroupId: String,
|
openGroupId: String,
|
||||||
calledFromConfigHandling: Bool,
|
calledFromConfigHandling: Bool,
|
||||||
using dependencies: OGMDependencies = OGMDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
let server: String? = try? OpenGroup
|
let server: String? = try? OpenGroup
|
||||||
.select(.server)
|
.select(.server)
|
||||||
|
@ -358,9 +334,9 @@ public final class OpenGroupManager {
|
||||||
.defaulting(to: 1)
|
.defaulting(to: 1)
|
||||||
|
|
||||||
if numActiveRooms == 1, let server: String = server?.lowercased() {
|
if numActiveRooms == 1, let server: String = server?.lowercased() {
|
||||||
let poller = dependencies.cache.pollers[server]
|
let poller = dependencies.caches[.openGroupManager].pollers[server]
|
||||||
poller?.stop()
|
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)
|
// Remove all the data (everything should cascade delete)
|
||||||
|
@ -435,7 +411,7 @@ public final class OpenGroupManager {
|
||||||
for roomToken: String,
|
for roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
waitForImageToComplete: Bool = false,
|
waitForImageToComplete: Bool = false,
|
||||||
dependencies: OGMDependencies = OGMDependencies(),
|
using dependencies: Dependencies,
|
||||||
completion: (() -> ())? = nil
|
completion: (() -> ())? = nil
|
||||||
) throws {
|
) throws {
|
||||||
// Create the open group model and get or create the thread
|
// 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
|
// Dispatch async to the workQueue to prevent holding up the DBWrite thread from the
|
||||||
// above transaction
|
// above transaction
|
||||||
OpenGroupAPI.workQueue.async {
|
OpenGroupAPI.workQueue.async(using: dependencies) {
|
||||||
// Start the poller if needed
|
// Start the poller if needed
|
||||||
if dependencies.cache.pollers[server.lowercased()] == nil {
|
if dependencies.caches[.openGroupManager].pollers[server.lowercased()] == nil {
|
||||||
dependencies.mutableCache.mutate {
|
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||||
$0.pollers[server.lowercased()]?.stop()
|
$0.pollers[server.lowercased()]?.stop()
|
||||||
$0.pollers[server.lowercased()] = OpenGroupAPI.Poller(for: server.lowercased())
|
$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)
|
/// 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
|
// Note: We need to subscribe and receive on different threads to ensure the
|
||||||
// logic in 'receiveValue' doesn't result in a reentrancy database issue
|
// logic in 'receiveValue' doesn't result in a reentrancy database issue
|
||||||
.subscribe(on: OpenGroupAPI.workQueue)
|
.subscribe(on: OpenGroupAPI.workQueue, using: dependencies)
|
||||||
.receive(on: DispatchQueue.global(qos: .default))
|
.receive(on: DispatchQueue.global(qos: .default), using: dependencies)
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
receiveCompletion: { _ in
|
receiveCompletion: { _ in
|
||||||
if waitForImageToComplete {
|
if waitForImageToComplete {
|
||||||
|
@ -562,7 +539,7 @@ public final class OpenGroupManager {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
receiveValue: { data in
|
receiveValue: { data in
|
||||||
dependencies.storage.write { db in
|
dependencies.storage.write(using: dependencies) { db in
|
||||||
_ = try OpenGroup
|
_ = try OpenGroup
|
||||||
.filter(id: threadId)
|
.filter(id: threadId)
|
||||||
.updateAll(db, OpenGroup.Columns.imageData.set(to: data))
|
.updateAll(db, OpenGroup.Columns.imageData.set(to: data))
|
||||||
|
@ -588,7 +565,7 @@ public final class OpenGroupManager {
|
||||||
messages: [OpenGroupAPI.Message],
|
messages: [OpenGroupAPI.Message],
|
||||||
for roomToken: String,
|
for roomToken: String,
|
||||||
on server: 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 {
|
guard let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: OpenGroup.idFor(roomToken: roomToken, server: server)) else {
|
||||||
SNLog("Couldn't handle open group messages.")
|
SNLog("Couldn't handle open group messages.")
|
||||||
|
@ -623,7 +600,7 @@ public final class OpenGroupManager {
|
||||||
openGroupServerPublicKey: openGroup.publicKey,
|
openGroupServerPublicKey: openGroup.publicKey,
|
||||||
message: message,
|
message: message,
|
||||||
data: data,
|
data: data,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
if let messageInfo: MessageReceiveJob.Details.MessageInfo = processedMessage?.messageInfo {
|
if let messageInfo: MessageReceiveJob.Details.MessageInfo = processedMessage?.messageInfo {
|
||||||
|
@ -634,7 +611,7 @@ public final class OpenGroupManager {
|
||||||
message: messageInfo.message,
|
message: messageInfo.message,
|
||||||
serverExpirationTimestamp: messageInfo.serverExpirationTimestamp,
|
serverExpirationTimestamp: messageInfo.serverExpirationTimestamp,
|
||||||
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
|
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
largestValidSeqNo = max(largestValidSeqNo, message.seqNo)
|
largestValidSeqNo = max(largestValidSeqNo, message.seqNo)
|
||||||
}
|
}
|
||||||
|
@ -661,7 +638,7 @@ public final class OpenGroupManager {
|
||||||
db,
|
db,
|
||||||
openGroupId: openGroup.id,
|
openGroupId: openGroup.id,
|
||||||
message: message,
|
message: message,
|
||||||
associatedPendingChanges: dependencies.cache.pendingChanges
|
associatedPendingChanges: dependencies.caches[.openGroupManager].pendingChanges
|
||||||
.filter {
|
.filter {
|
||||||
guard $0.server == server && $0.room == roomToken && $0.changeType == .reaction else {
|
guard $0.server == server && $0.room == roomToken && $0.changeType == .reaction else {
|
||||||
return false
|
return false
|
||||||
|
@ -672,7 +649,7 @@ public final class OpenGroupManager {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
try MessageReceiver.handleOpenGroupReactions(
|
try MessageReceiver.handleOpenGroupReactions(
|
||||||
|
@ -708,7 +685,7 @@ public final class OpenGroupManager {
|
||||||
.updateAll(db, OpenGroup.Columns.sequenceNumber.set(to: largestValidSeqNo))
|
.updateAll(db, OpenGroup.Columns.sequenceNumber.set(to: largestValidSeqNo))
|
||||||
|
|
||||||
// Update pendingChange cache based on the `largestValidSeqNo` value
|
// Update pendingChange cache based on the `largestValidSeqNo` value
|
||||||
dependencies.mutableCache.mutate {
|
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||||
$0.pendingChanges = $0.pendingChanges
|
$0.pendingChanges = $0.pendingChanges
|
||||||
.filter { $0.seqNo == nil || $0.seqNo! > largestValidSeqNo }
|
.filter { $0.seqNo == nil || $0.seqNo! > largestValidSeqNo }
|
||||||
}
|
}
|
||||||
|
@ -719,7 +696,7 @@ public final class OpenGroupManager {
|
||||||
messages: [OpenGroupAPI.DirectMessage],
|
messages: [OpenGroupAPI.DirectMessage],
|
||||||
fromOutbox: Bool,
|
fromOutbox: Bool,
|
||||||
on server: String,
|
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)
|
// Don't need to do anything if we have no messages (it's a valid case)
|
||||||
guard !messages.isEmpty else { return }
|
guard !messages.isEmpty else { return }
|
||||||
|
@ -762,7 +739,7 @@ public final class OpenGroupManager {
|
||||||
data: messageData,
|
data: messageData,
|
||||||
isOutgoing: fromOutbox,
|
isOutgoing: fromOutbox,
|
||||||
otherBlindedPublicKey: (fromOutbox ? message.recipient : message.sender),
|
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
|
// 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(),
|
openGroupServer: server.lowercased(),
|
||||||
openGroupPublicKey: openGroup.publicKey,
|
openGroupPublicKey: openGroup.publicKey,
|
||||||
isCheckingForOutbox: fromOutbox,
|
isCheckingForOutbox: fromOutbox,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
}()
|
}()
|
||||||
lookupCache[message.recipient] = lookup
|
lookupCache[message.recipient] = lookup
|
||||||
|
@ -817,7 +794,7 @@ public final class OpenGroupManager {
|
||||||
message: messageInfo.message,
|
message: messageInfo.message,
|
||||||
serverExpirationTimestamp: messageInfo.serverExpirationTimestamp,
|
serverExpirationTimestamp: messageInfo.serverExpirationTimestamp,
|
||||||
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
|
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -846,7 +823,7 @@ public final class OpenGroupManager {
|
||||||
in roomToken: String,
|
in roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
type: OpenGroupAPI.PendingChange.ReactAction,
|
type: OpenGroupAPI.PendingChange.ReactAction,
|
||||||
using dependencies: OGMDependencies = OGMDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> OpenGroupAPI.PendingChange {
|
) -> OpenGroupAPI.PendingChange {
|
||||||
let pendingChange = OpenGroupAPI.PendingChange(
|
let pendingChange = OpenGroupAPI.PendingChange(
|
||||||
server: server,
|
server: server,
|
||||||
|
@ -859,7 +836,7 @@ public final class OpenGroupManager {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
dependencies.mutableCache.mutate {
|
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||||
$0.pendingChanges.append(pendingChange)
|
$0.pendingChanges.append(pendingChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -869,9 +846,9 @@ public final class OpenGroupManager {
|
||||||
public static func updatePendingChange(
|
public static func updatePendingChange(
|
||||||
_ pendingChange: OpenGroupAPI.PendingChange,
|
_ pendingChange: OpenGroupAPI.PendingChange,
|
||||||
seqNo: Int64?,
|
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) {
|
if let index = $0.pendingChanges.firstIndex(of: pendingChange) {
|
||||||
$0.pendingChanges[index].seqNo = seqNo
|
$0.pendingChanges[index].seqNo = seqNo
|
||||||
}
|
}
|
||||||
|
@ -880,9 +857,9 @@ public final class OpenGroupManager {
|
||||||
|
|
||||||
public static func removePendingChange(
|
public static func removePendingChange(
|
||||||
_ pendingChange: OpenGroupAPI.PendingChange,
|
_ 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) {
|
if let index = $0.pendingChanges.firstIndex(of: pendingChange) {
|
||||||
$0.pendingChanges.remove(at: index)
|
$0.pendingChanges.remove(at: index)
|
||||||
}
|
}
|
||||||
|
@ -894,7 +871,7 @@ public final class OpenGroupManager {
|
||||||
_ db: Database? = nil,
|
_ db: Database? = nil,
|
||||||
capability: Capability.Variant,
|
capability: Capability.Variant,
|
||||||
on server: String?,
|
on server: String?,
|
||||||
using dependencies: OGMDependencies = OGMDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
guard let server: String = server else { return false }
|
guard let server: String = server else { return false }
|
||||||
guard let db: Database = db else {
|
guard let db: Database = db else {
|
||||||
|
@ -919,7 +896,7 @@ public final class OpenGroupManager {
|
||||||
_ publicKey: String,
|
_ publicKey: String,
|
||||||
for roomToken: String?,
|
for roomToken: String?,
|
||||||
on server: String?,
|
on server: String?,
|
||||||
using dependencies: OGMDependencies = OGMDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
guard let roomToken: String = roomToken, let server: String = server else { return false }
|
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
|
// Conveniently the logic for these different cases works in order so we can fallthrough each
|
||||||
// case with only minor efficiency losses
|
// case with only minor efficiency losses
|
||||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||||
|
|
||||||
switch sessionId.prefix {
|
switch sessionId.prefix {
|
||||||
case .standard:
|
case .standard:
|
||||||
|
@ -967,10 +944,12 @@ public final class OpenGroupManager {
|
||||||
.filter(id: groupId)
|
.filter(id: groupId)
|
||||||
.asRequest(of: String.self)
|
.asRequest(of: String.self)
|
||||||
.fetchOne(db),
|
.fetchOne(db),
|
||||||
let blindedKeyPair: KeyPair = dependencies.sodium.blindedKeyPair(
|
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
|
||||||
serverPublicKey: openGroupPublicKey,
|
.blindedKeyPair(
|
||||||
edKeyPair: userEdKeyPair,
|
serverPublicKey: openGroupPublicKey,
|
||||||
genericHash: dependencies.genericHash
|
edKeyPair: userEdKeyPair,
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else { return false }
|
else { return false }
|
||||||
guard
|
guard
|
||||||
|
@ -1003,19 +982,16 @@ public final class OpenGroupManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult public static func getDefaultRoomsIfNeeded(
|
@discardableResult public static func getDefaultRoomsIfNeeded(
|
||||||
using dependencies: OGMDependencies = OGMDependencies(
|
using dependencies: Dependencies = Dependencies()
|
||||||
subscribeQueue: OpenGroupAPI.workQueue,
|
|
||||||
receiveQueue: OpenGroupAPI.workQueue
|
|
||||||
)
|
|
||||||
) -> AnyPublisher<[DefaultRoomInfo], Error> {
|
) -> AnyPublisher<[DefaultRoomInfo], Error> {
|
||||||
// Note: If we already have a 'defaultRoomsPromise' then there is no need to get it again
|
// 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
|
return existingPublisher
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to retrieve the default rooms 8 times
|
// Try to retrieve the default rooms 8 times
|
||||||
let publisher: AnyPublisher<[DefaultRoomInfo], Error> = dependencies.storage
|
let publisher: AnyPublisher<[DefaultRoomInfo], Error> = dependencies.storage
|
||||||
.readPublisher { db in
|
.readPublisher { db -> OpenGroupAPI.PreparedSendData<OpenGroupAPI.CapabilitiesAndRoomsResponse> in
|
||||||
try OpenGroupAPI.preparedCapabilitiesAndRooms(
|
try OpenGroupAPI.preparedCapabilitiesAndRooms(
|
||||||
db,
|
db,
|
||||||
on: OpenGroupAPI.defaultServer,
|
on: OpenGroupAPI.defaultServer,
|
||||||
|
@ -1023,9 +999,9 @@ public final class OpenGroupManager {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.flatMap { OpenGroupAPI.send(data: $0, using: dependencies) }
|
.flatMap { OpenGroupAPI.send(data: $0, using: dependencies) }
|
||||||
.subscribe(on: dependencies.subscribeQueue)
|
.subscribe(on: OpenGroupAPI.workQueue, using: dependencies)
|
||||||
.receive(on: dependencies.receiveQueue)
|
.receive(on: OpenGroupAPI.workQueue, using: dependencies)
|
||||||
.retry(8)
|
.retry(8, using: dependencies)
|
||||||
.map { info, response -> [DefaultRoomInfo]? in
|
.map { info, response -> [DefaultRoomInfo]? in
|
||||||
dependencies.storage.write { db -> [DefaultRoomInfo] in
|
dependencies.storage.write { db -> [DefaultRoomInfo] in
|
||||||
// Store the capabilities first
|
// Store the capabilities first
|
||||||
|
@ -1090,7 +1066,7 @@ public final class OpenGroupManager {
|
||||||
switch result {
|
switch result {
|
||||||
case .finished: break
|
case .finished: break
|
||||||
case .failure:
|
case .failure:
|
||||||
dependencies.mutableCache.mutate { cache in
|
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||||
cache.defaultRoomsPublisher = nil
|
cache.defaultRoomsPublisher = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1099,7 +1075,7 @@ public final class OpenGroupManager {
|
||||||
.shareReplay(1)
|
.shareReplay(1)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
|
||||||
dependencies.mutableCache.mutate { cache in
|
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||||
cache.defaultRoomsPublisher = publisher
|
cache.defaultRoomsPublisher = publisher
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1114,9 +1090,7 @@ public final class OpenGroupManager {
|
||||||
for roomToken: String,
|
for roomToken: String,
|
||||||
on server: String,
|
on server: String,
|
||||||
existingData: Data?,
|
existingData: Data?,
|
||||||
using dependencies: OGMDependencies = OGMDependencies(
|
using dependencies: Dependencies = Dependencies()
|
||||||
subscribeQueue: .global(qos: .background)
|
|
||||||
)
|
|
||||||
) -> AnyPublisher<Data, Error> {
|
) -> AnyPublisher<Data, Error> {
|
||||||
// Normally the image for a given group is stored with the group thread, so it's only
|
// 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
|
// 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.
|
// there is one.
|
||||||
let threadId: String = OpenGroup.idFor(roomToken: roomToken, server: server)
|
let threadId: String = OpenGroup.idFor(roomToken: roomToken, server: server)
|
||||||
let lastOpenGroupImageUpdate: Date? = dependencies.standardUserDefaults[.lastOpenGroupImageUpdate]
|
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 timeSinceLastUpdate: TimeInterval = (lastOpenGroupImageUpdate.map { now.timeIntervalSince($0) } ?? .greatestFiniteMagnitude)
|
||||||
let updateInterval: TimeInterval = (7 * 24 * 60 * 60)
|
let updateInterval: TimeInterval = (7 * 24 * 60 * 60)
|
||||||
let canUseExistingImage: Bool = (
|
let canUseExistingImage: Bool = (
|
||||||
|
@ -1143,14 +1117,14 @@ public final class OpenGroupManager {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
if let publisher: AnyPublisher<Data, Error> = dependencies.cache.groupImagePublishers[threadId] {
|
if let publisher: AnyPublisher<Data, Error> = dependencies.caches[.openGroupManager].groupImagePublishers[threadId] {
|
||||||
return publisher
|
return publisher
|
||||||
}
|
}
|
||||||
|
|
||||||
// Defer the actual download and run it on a separate thread to avoid blocking the calling thread
|
// Defer the actual download and run it on a separate thread to avoid blocking the calling thread
|
||||||
let publisher: AnyPublisher<Data, Error> = Deferred {
|
let publisher: AnyPublisher<Data, Error> = Deferred {
|
||||||
Future { resolver in
|
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
|
// Hold on to the publisher until it has completed at least once
|
||||||
dependencies.storage
|
dependencies.storage
|
||||||
.readPublisher { db -> (Data?, OpenGroupAPI.PreparedSendData<Data>?) in
|
.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
|
// Automatically subscribe for the roomImage download (want to download regardless of
|
||||||
// whether the upstream subscribes)
|
// whether the upstream subscribes)
|
||||||
publisher
|
publisher
|
||||||
.subscribe(on: dependencies.subscribeQueue)
|
.subscribe(on: DispatchQueue.global(qos: .background), using: dependencies)
|
||||||
.sinkUntilComplete()
|
.sinkUntilComplete()
|
||||||
|
|
||||||
dependencies.mutableCache.mutate { cache in
|
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||||
cache.groupImagePublishers[threadId] = publisher
|
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
|
// 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 defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { get set }
|
||||||
var groupImagePublishers: [String: AnyPublisher<Data, 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
|
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 method: HTTPMethod
|
||||||
private let path: String
|
private let path: String
|
||||||
public let endpoint: Endpoint
|
public let endpoint: Endpoint
|
||||||
fileprivate let batchEndpoints: [Endpoint]
|
internal let batchEndpoints: [Endpoint]
|
||||||
public let batchResponseTypes: [Decodable.Type]
|
public let batchResponseTypes: [Decodable.Type]
|
||||||
|
|
||||||
/// The `jsonBodyEncoder` is used to simplify the encoding for `BatchRequest`
|
/// 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 {
|
public extension Publisher where Output == (ResponseInfoType, Data?), Failure == Error {
|
||||||
func decoded<R>(
|
func decoded<R>(
|
||||||
with preparedData: OpenGroupAPI.PreparedSendData<R>,
|
with preparedData: OpenGroupAPI.PreparedSendData<R>,
|
||||||
using dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies
|
||||||
) -> AnyPublisher<(ResponseInfoType, R), Error> {
|
) -> AnyPublisher<(ResponseInfoType, R), Error> {
|
||||||
self
|
self
|
||||||
.tryMap { responseInfo, maybeData -> (ResponseInfoType, R) in
|
.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 encryptionFailed
|
||||||
case noUsername
|
case noUsername
|
||||||
case attachmentsNotUploaded
|
case attachmentsNotUploaded
|
||||||
|
case blindingFailed
|
||||||
|
|
||||||
// Closed groups
|
// Closed groups
|
||||||
case noThread
|
case noThread
|
||||||
|
@ -21,7 +22,10 @@ public enum MessageSenderError: LocalizedError, Equatable {
|
||||||
|
|
||||||
internal var isRetryable: Bool {
|
internal var isRetryable: Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .invalidMessage, .protoConversionFailed, .invalidClosedGroupUpdate, .signingFailed, .encryptionFailed: return false
|
case .invalidMessage, .protoConversionFailed, .invalidClosedGroupUpdate,
|
||||||
|
.signingFailed, .encryptionFailed, .blindingFailed:
|
||||||
|
return false
|
||||||
|
|
||||||
default: return true
|
default: return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +40,7 @@ public enum MessageSenderError: LocalizedError, Equatable {
|
||||||
case .encryptionFailed: return "Couldn't encrypt message."
|
case .encryptionFailed: return "Couldn't encrypt message."
|
||||||
case .noUsername: return "Missing username."
|
case .noUsername: return "Missing username."
|
||||||
case .attachmentsNotUploaded: return "Attachments for this message have not been uploaded."
|
case .attachmentsNotUploaded: return "Attachments for this message have not been uploaded."
|
||||||
|
case .blindingFailed: return "Couldn't blind the sender"
|
||||||
|
|
||||||
// Closed groups
|
// Closed groups
|
||||||
case .noThread: return "Couldn't find a thread associated with the given group public key."
|
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 (.noThread, .noThread): return true
|
||||||
case (.noKeyPair, .noKeyPair): return true
|
case (.noKeyPair, .noKeyPair): return true
|
||||||
case (.invalidClosedGroupUpdate, .invalidClosedGroupUpdate): return true
|
case (.invalidClosedGroupUpdate, .invalidClosedGroupUpdate): return true
|
||||||
|
case (.blindingFailed, .blindingFailed): return true
|
||||||
|
|
||||||
case (.other(let lhsError), .other(let rhsError)):
|
case (.other(let lhsError), .other(let rhsError)):
|
||||||
// Not ideal but the best we can do
|
// Not ideal but the best we can do
|
||||||
|
|
|
@ -194,7 +194,11 @@ extension MessageReceiver {
|
||||||
|
|
||||||
// MARK: - Convenience
|
// 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)
|
let messageInfo: CallMessage.MessageInfo = CallMessage.MessageInfo(state: .missed)
|
||||||
|
|
||||||
guard
|
guard
|
||||||
|
@ -229,7 +233,7 @@ extension MessageReceiver {
|
||||||
.inserted(db)
|
.inserted(db)
|
||||||
|
|
||||||
MessageSender.sendImmediate(
|
MessageSender.sendImmediate(
|
||||||
preparedSendData: try MessageSender
|
data: try MessageSender
|
||||||
.preparedSendData(
|
.preparedSendData(
|
||||||
db,
|
db,
|
||||||
message: CallMessage(
|
message: CallMessage(
|
||||||
|
@ -242,8 +246,10 @@ extension MessageReceiver {
|
||||||
namespace: try Message.Destination
|
namespace: try Message.Destination
|
||||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||||
.defaultNamespace,
|
.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))
|
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||||
.sinkUntilComplete()
|
.sinkUntilComplete()
|
||||||
|
|
|
@ -12,10 +12,11 @@ extension MessageReceiver {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
threadId: String,
|
threadId: String,
|
||||||
threadVariant: SessionThread.Variant,
|
threadVariant: SessionThread.Variant,
|
||||||
message: ClosedGroupControlMessage
|
message: ClosedGroupControlMessage,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws {
|
) throws {
|
||||||
switch message.kind {
|
switch message.kind {
|
||||||
case .new: try handleNewClosedGroup(db, message: message)
|
case .new: try handleNewClosedGroup(db, message: message, using: dependencies)
|
||||||
|
|
||||||
case .encryptionKeyPair:
|
case .encryptionKeyPair:
|
||||||
try handleClosedGroupEncryptionKeyPair(
|
try handleClosedGroupEncryptionKeyPair(
|
||||||
|
@ -65,7 +66,11 @@ extension MessageReceiver {
|
||||||
|
|
||||||
// MARK: - Specific Handling
|
// 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 {
|
guard case let .new(publicKeyAsData, name, encryptionKeyPair, membersAsData, adminsAsData, expirationTimer) = message.kind else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -112,7 +117,8 @@ extension MessageReceiver {
|
||||||
admins: adminsAsData.map { $0.toHexString() },
|
admins: adminsAsData.map { $0.toHexString() },
|
||||||
expirationTimer: expirationTimer,
|
expirationTimer: expirationTimer,
|
||||||
formationTimestampMs: sentTimestamp,
|
formationTimestampMs: sentTimestamp,
|
||||||
calledFromConfigHandling: false
|
calledFromConfigHandling: false,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +131,8 @@ extension MessageReceiver {
|
||||||
admins: [String],
|
admins: [String],
|
||||||
expirationTimer: UInt32,
|
expirationTimer: UInt32,
|
||||||
formationTimestampMs: UInt64,
|
formationTimestampMs: UInt64,
|
||||||
calledFromConfigHandling: Bool
|
calledFromConfigHandling: Bool,
|
||||||
|
using dependencies: Dependencies
|
||||||
) throws {
|
) throws {
|
||||||
// With new closed groups we only want to create them if the admin creating the closed group is an
|
// 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
|
// approved contact (to prevent spam via closed groups getting around message requests if users are
|
||||||
|
@ -222,7 +229,7 @@ extension MessageReceiver {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start polling
|
// Start polling
|
||||||
ClosedGroupPoller.shared.startIfNeeded(for: groupPublicKey)
|
ClosedGroupPoller.shared.startIfNeeded(for: groupPublicKey, using: dependencies)
|
||||||
|
|
||||||
// Notify the PN server
|
// Notify the PN server
|
||||||
let _ = PushNotificationAPI.performOperation(.subscribe, for: groupPublicKey, publicKey: getUserHexEncodedPublicKey(db))
|
let _ = PushNotificationAPI.performOperation(.subscribe, for: groupPublicKey, publicKey: getUserHexEncodedPublicKey(db))
|
||||||
|
|
|
@ -7,7 +7,11 @@ import SessionUIKit
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
extension MessageReceiver {
|
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
|
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
|
||||||
guard !SessionUtil.userConfigsEnabled(db) else {
|
guard !SessionUtil.userConfigsEnabled(db) else {
|
||||||
TopBannerController.show(warning: .outdatedUserConfig)
|
TopBannerController.show(warning: .outdatedUserConfig)
|
||||||
|
@ -46,7 +50,8 @@ extension MessageReceiver {
|
||||||
)
|
)
|
||||||
}(),
|
}(),
|
||||||
sentTimestamp: messageSentTimestamp,
|
sentTimestamp: messageSentTimestamp,
|
||||||
calledFromConfigHandling: true
|
calledFromConfigHandling: true,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create a contact for the current user if needed (also force-approve the current user
|
// 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),
|
admins: [String](closedGroup.admins),
|
||||||
expirationTimer: closedGroup.expirationTimer,
|
expirationTimer: closedGroup.expirationTimer,
|
||||||
formationTimestampMs: message.sentTimestamp!,
|
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(
|
internal static func handleMessageRequestResponse(
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
message: MessageRequestResponse,
|
message: MessageRequestResponse,
|
||||||
dependencies: SMKDependencies
|
using dependencies: Dependencies
|
||||||
) throws {
|
) throws {
|
||||||
let userPublicKey = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
let userPublicKey = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||||
var blindedContactIds: [String] = []
|
var blindedContactIds: [String] = []
|
||||||
|
|
||||||
// Ignore messages which were sent from the current user
|
// Ignore messages which were sent from the current user
|
||||||
|
@ -42,7 +42,8 @@ extension MessageReceiver {
|
||||||
fileName: nil
|
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
|
// If the sessionId matches the blindedId then this thread needs to be converted to an
|
||||||
// un-blinded thread
|
// un-blinded thread
|
||||||
guard
|
guard
|
||||||
dependencies.sodium.sessionId(
|
dependencies.crypto.verify(
|
||||||
senderId,
|
.sessionId(
|
||||||
matchesBlindedId: blindedIdLookup.blindedId,
|
senderId,
|
||||||
serverPublicKey: blindedIdLookup.openGroupPublicKey,
|
matchesBlindedId: blindedIdLookup.blindedId,
|
||||||
genericHash: dependencies.genericHash
|
serverPublicKey: blindedIdLookup.openGroupPublicKey,
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ extension MessageReceiver {
|
||||||
threadVariant: SessionThread.Variant,
|
threadVariant: SessionThread.Variant,
|
||||||
message: VisibleMessage,
|
message: VisibleMessage,
|
||||||
associatedWithProto proto: SNProtoContent,
|
associatedWithProto proto: SNProtoContent,
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> Int64 {
|
) throws -> Int64 {
|
||||||
guard let sender: String = message.sender, let dataMessage = proto.dataMessage else {
|
guard let sender: String = message.sender, let dataMessage = proto.dataMessage else {
|
||||||
throw MessageReceiverError.invalidMessage
|
throw MessageReceiverError.invalidMessage
|
||||||
|
@ -43,7 +43,8 @@ extension MessageReceiver {
|
||||||
fileName: nil
|
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
|
// 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
|
let thread: SessionThread = try SessionThread
|
||||||
.fetchOrCreate(db, id: threadId, variant: threadVariant, shouldBeVisible: nil)
|
.fetchOrCreate(db, id: threadId, variant: threadVariant, shouldBeVisible: nil)
|
||||||
let maybeOpenGroup: OpenGroup? = {
|
let maybeOpenGroup: OpenGroup? = {
|
||||||
|
@ -90,10 +91,12 @@ extension MessageReceiver {
|
||||||
|
|
||||||
guard
|
guard
|
||||||
let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
||||||
let blindedKeyPair: KeyPair = sodium.blindedKeyPair(
|
let blindedKeyPair: KeyPair = try? dependencies.crypto.generate(
|
||||||
serverPublicKey: openGroup.publicKey,
|
.blindedKeyPair(
|
||||||
edKeyPair: userEdKeyPair,
|
serverPublicKey: openGroup.publicKey,
|
||||||
genericHash: sodium.genericHash
|
edKeyPair: userEdKeyPair,
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else { return .standardIncoming }
|
else { return .standardIncoming }
|
||||||
|
|
||||||
|
@ -163,7 +166,8 @@ extension MessageReceiver {
|
||||||
db,
|
db,
|
||||||
threadId: thread.id,
|
threadId: thread.id,
|
||||||
body: message.text,
|
body: message.text,
|
||||||
quoteAuthorId: dataMessage.quote?.author
|
quoteAuthorId: dataMessage.quote?.author,
|
||||||
|
using: dependencies
|
||||||
),
|
),
|
||||||
// Note: Ensure we don't ever expire open group messages
|
// Note: Ensure we don't ever expire open group messages
|
||||||
expiresInSeconds: (disappearingMessagesConfiguration.isEnabled && message.openGroupServerMessageId == nil ?
|
expiresInSeconds: (disappearingMessagesConfiguration.isEnabled && message.openGroupServerMessageId == nil ?
|
||||||
|
@ -294,7 +298,7 @@ extension MessageReceiver {
|
||||||
.appending(quote?.attachmentId)
|
.appending(quote?.attachmentId)
|
||||||
.appending(linkPreview?.attachmentId)
|
.appending(linkPreview?.attachmentId)
|
||||||
.forEach { attachmentId in
|
.forEach { attachmentId in
|
||||||
JobRunner.add(
|
dependencies.jobRunner.add(
|
||||||
db,
|
db,
|
||||||
job: Job(
|
job: Job(
|
||||||
variant: .attachmentDownload,
|
variant: .attachmentDownload,
|
||||||
|
@ -304,7 +308,8 @@ extension MessageReceiver {
|
||||||
attachmentId: attachmentId
|
attachmentId: attachmentId
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
canStartJob: isMainAppActive
|
canStartJob: isMainAppActive,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import GRDB
|
import GRDB
|
||||||
import Sodium
|
import Sodium
|
||||||
import Curve25519Kit
|
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
import SessionSnodeKit
|
import SessionSnodeKit
|
||||||
|
|
||||||
|
@ -13,21 +12,21 @@ extension MessageSender {
|
||||||
|
|
||||||
public static func createClosedGroup(
|
public static func createClosedGroup(
|
||||||
name: String,
|
name: String,
|
||||||
members: Set<String>
|
members: Set<String>,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<SessionThread, Error> {
|
) -> AnyPublisher<SessionThread, Error> {
|
||||||
Storage.shared
|
dependencies.storage
|
||||||
.writePublisher { db -> (String, SessionThread, [MessageSender.PreparedSendData]) in
|
.writePublisher { db -> (String, SessionThread, [MessageSender.PreparedSendData]) in
|
||||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
// Generate the group's two keys
|
||||||
var members: Set<String> = members
|
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
|
// Includes the 'SessionId.Prefix.standard' prefix
|
||||||
let groupKeyPair: ECKeyPair = Curve25519.generateKeyPair()
|
let groupPublicKey: String = groupKeyPair.hexEncodedPublicKey
|
||||||
let groupPublicKey: String = KeyPair(
|
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||||
publicKey: groupKeyPair.publicKey.bytes,
|
var members: Set<String> = members
|
||||||
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()
|
|
||||||
|
|
||||||
// Create the group
|
// Create the group
|
||||||
members.insert(userPublicKey) // Ensure the current user is included in the member list
|
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)
|
let latestKeyPairReceivedTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000)
|
||||||
try ClosedGroupKeyPair(
|
try ClosedGroupKeyPair(
|
||||||
threadId: groupPublicKey,
|
threadId: groupPublicKey,
|
||||||
publicKey: encryptionKeyPair.publicKey,
|
publicKey: Data(encryptionKeyPair.publicKey),
|
||||||
secretKey: encryptionKeyPair.privateKey,
|
secretKey: Data(encryptionKeyPair.secretKey),
|
||||||
receivedTimestamp: latestKeyPairReceivedTimestamp
|
receivedTimestamp: latestKeyPairReceivedTimestamp
|
||||||
).insert(db)
|
).insert(db)
|
||||||
|
|
||||||
|
@ -78,8 +77,8 @@ extension MessageSender {
|
||||||
db,
|
db,
|
||||||
groupPublicKey: groupPublicKey,
|
groupPublicKey: groupPublicKey,
|
||||||
name: name,
|
name: name,
|
||||||
latestKeyPairPublicKey: encryptionKeyPair.publicKey,
|
latestKeyPairPublicKey: Data(encryptionKeyPair.publicKey),
|
||||||
latestKeyPairSecretKey: encryptionKeyPair.privateKey,
|
latestKeyPairSecretKey: Data(encryptionKeyPair.secretKey),
|
||||||
latestKeyPairReceivedTimestamp: latestKeyPairReceivedTimestamp,
|
latestKeyPairReceivedTimestamp: latestKeyPairReceivedTimestamp,
|
||||||
disappearingConfig: DisappearingMessagesConfiguration.defaultWith(groupPublicKey),
|
disappearingConfig: DisappearingMessagesConfiguration.defaultWith(groupPublicKey),
|
||||||
members: members,
|
members: members,
|
||||||
|
@ -94,10 +93,7 @@ extension MessageSender {
|
||||||
kind: .new(
|
kind: .new(
|
||||||
publicKey: Data(hex: groupPublicKey),
|
publicKey: Data(hex: groupPublicKey),
|
||||||
name: name,
|
name: name,
|
||||||
encryptionKeyPair: KeyPair(
|
encryptionKeyPair: encryptionKeyPair,
|
||||||
publicKey: encryptionKeyPair.publicKey.bytes,
|
|
||||||
secretKey: encryptionKeyPair.privateKey.bytes
|
|
||||||
),
|
|
||||||
members: membersAsData,
|
members: membersAsData,
|
||||||
admins: adminsAsData,
|
admins: adminsAsData,
|
||||||
expirationTimer: 0
|
expirationTimer: 0
|
||||||
|
@ -108,7 +104,8 @@ extension MessageSender {
|
||||||
),
|
),
|
||||||
to: .contact(publicKey: memberId),
|
to: .contact(publicKey: memberId),
|
||||||
namespace: Message.Destination.contact(publicKey: memberId).defaultNamespace,
|
namespace: Message.Destination.contact(publicKey: memberId).defaultNamespace,
|
||||||
interactionId: nil
|
interactionId: nil,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +116,7 @@ extension MessageSender {
|
||||||
.MergeMany(
|
.MergeMany(
|
||||||
// Send a closed group update message to all members individually
|
// Send a closed group update message to all members individually
|
||||||
memberSendData
|
memberSendData
|
||||||
.map { MessageSender.sendImmediate(preparedSendData: $0) }
|
.map { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||||
.appending(
|
.appending(
|
||||||
// Notify the PN server
|
// Notify the PN server
|
||||||
PushNotificationAPI.performOperation(
|
PushNotificationAPI.performOperation(
|
||||||
|
@ -135,7 +132,7 @@ extension MessageSender {
|
||||||
.handleEvents(
|
.handleEvents(
|
||||||
receiveOutput: { thread in
|
receiveOutput: { thread in
|
||||||
// Start polling
|
// Start polling
|
||||||
ClosedGroupPoller.shared.startIfNeeded(for: thread.id)
|
ClosedGroupPoller.shared.startIfNeeded(for: thread.id, using: dependencies)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
@ -151,21 +148,25 @@ extension MessageSender {
|
||||||
targetMembers: Set<String>,
|
targetMembers: Set<String>,
|
||||||
userPublicKey: String,
|
userPublicKey: String,
|
||||||
allGroupMembers: [GroupMember],
|
allGroupMembers: [GroupMember],
|
||||||
closedGroup: ClosedGroup
|
closedGroup: ClosedGroup,
|
||||||
|
using dependencies: Dependencies
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
guard allGroupMembers.contains(where: { $0.role == .admin && $0.profileId == userPublicKey }) else {
|
guard allGroupMembers.contains(where: { $0.role == .admin && $0.profileId == userPublicKey }) else {
|
||||||
return Fail(error: MessageSenderError.invalidClosedGroupUpdate)
|
return Fail(error: MessageSenderError.invalidClosedGroupUpdate)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
return Storage.shared
|
return dependencies.storage
|
||||||
.readPublisher { db -> (ClosedGroupKeyPair, MessageSender.PreparedSendData) in
|
.readPublisher { db -> (ClosedGroupKeyPair, MessageSender.PreparedSendData) in
|
||||||
// Generate the new encryption key pair
|
// 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(
|
let newKeyPair: ClosedGroupKeyPair = ClosedGroupKeyPair(
|
||||||
threadId: closedGroup.threadId,
|
threadId: closedGroup.threadId,
|
||||||
publicKey: legacyNewKeyPair.publicKey,
|
publicKey: Data(legacyNewKeyPair.publicKey),
|
||||||
secretKey: legacyNewKeyPair.privateKey,
|
secretKey: Data(legacyNewKeyPair.secretKey),
|
||||||
receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000)
|
receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -193,7 +194,8 @@ extension MessageSender {
|
||||||
encryptedKeyPair: try MessageSender.encryptWithSessionProtocol(
|
encryptedKeyPair: try MessageSender.encryptWithSessionProtocol(
|
||||||
db,
|
db,
|
||||||
plaintext: plaintext,
|
plaintext: plaintext,
|
||||||
for: memberPublicKey
|
for: memberPublicKey,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -204,20 +206,21 @@ extension MessageSender {
|
||||||
namespace: try Message.Destination
|
namespace: try Message.Destination
|
||||||
.from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup)
|
.from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup)
|
||||||
.defaultNamespace,
|
.defaultNamespace,
|
||||||
interactionId: nil
|
interactionId: nil,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
return (newKeyPair, sendData)
|
return (newKeyPair, sendData)
|
||||||
}
|
}
|
||||||
.flatMap { newKeyPair, sendData -> AnyPublisher<ClosedGroupKeyPair, Error> in
|
.flatMap { newKeyPair, sendData -> AnyPublisher<ClosedGroupKeyPair, Error> in
|
||||||
MessageSender.sendImmediate(preparedSendData: sendData)
|
MessageSender.sendImmediate(data: sendData, using: dependencies)
|
||||||
.map { _ in newKeyPair }
|
.map { _ in newKeyPair }
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
.handleEvents(
|
.handleEvents(
|
||||||
receiveOutput: { newKeyPair in
|
receiveOutput: { newKeyPair in
|
||||||
/// Store it **after** having sent out the message to the group
|
/// 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)
|
try newKeyPair.insert(db)
|
||||||
|
|
||||||
// Update libSession
|
// Update libSession
|
||||||
|
@ -251,11 +254,12 @@ extension MessageSender {
|
||||||
public static func update(
|
public static func update(
|
||||||
groupPublicKey: String,
|
groupPublicKey: String,
|
||||||
with members: Set<String>,
|
with members: Set<String>,
|
||||||
name: String
|
name: String,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
return Storage.shared
|
return Storage.shared
|
||||||
.writePublisher { db -> (String, ClosedGroup, [GroupMember], Set<String>) in
|
.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
|
// Get the group, check preconditions & prepare
|
||||||
guard (try? SessionThread.exists(db, id: groupPublicKey)) == true else {
|
guard (try? SessionThread.exists(db, id: groupPublicKey)) == true else {
|
||||||
|
@ -292,7 +296,8 @@ extension MessageSender {
|
||||||
message: ClosedGroupControlMessage(kind: .nameChange(name: name)),
|
message: ClosedGroupControlMessage(kind: .nameChange(name: name)),
|
||||||
interactionId: interactionId,
|
interactionId: interactionId,
|
||||||
threadId: groupPublicKey,
|
threadId: groupPublicKey,
|
||||||
threadVariant: .legacyGroup
|
threadVariant: .legacyGroup,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
// Update libSession
|
// Update libSession
|
||||||
|
@ -321,7 +326,8 @@ extension MessageSender {
|
||||||
addedMembers: addedMembers,
|
addedMembers: addedMembers,
|
||||||
userPublicKey: userPublicKey,
|
userPublicKey: userPublicKey,
|
||||||
allGroupMembers: allGroupMembers,
|
allGroupMembers: allGroupMembers,
|
||||||
closedGroup: closedGroup
|
closedGroup: closedGroup,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
@ -348,7 +354,8 @@ extension MessageSender {
|
||||||
removedMembers: removedMembers,
|
removedMembers: removedMembers,
|
||||||
userPublicKey: userPublicKey,
|
userPublicKey: userPublicKey,
|
||||||
allGroupMembers: allGroupMembers,
|
allGroupMembers: allGroupMembers,
|
||||||
closedGroup: closedGroup
|
closedGroup: closedGroup,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
.catch { _ in Fail(error: MessageSenderError.invalidClosedGroupUpdate).eraseToAnyPublisher() }
|
.catch { _ in Fail(error: MessageSenderError.invalidClosedGroupUpdate).eraseToAnyPublisher() }
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
@ -364,7 +371,8 @@ extension MessageSender {
|
||||||
addedMembers: Set<String>,
|
addedMembers: Set<String>,
|
||||||
userPublicKey: String,
|
userPublicKey: String,
|
||||||
allGroupMembers: [GroupMember],
|
allGroupMembers: [GroupMember],
|
||||||
closedGroup: ClosedGroup
|
closedGroup: ClosedGroup,
|
||||||
|
using dependencies: Dependencies
|
||||||
) throws {
|
) throws {
|
||||||
guard let disappearingMessagesConfig: DisappearingMessagesConfiguration = try DisappearingMessagesConfiguration.fetchOne(db, id: closedGroup.threadId) else {
|
guard let disappearingMessagesConfig: DisappearingMessagesConfiguration = try DisappearingMessagesConfiguration.fetchOne(db, id: closedGroup.threadId) else {
|
||||||
throw StorageError.objectNotFound
|
throw StorageError.objectNotFound
|
||||||
|
@ -419,7 +427,8 @@ extension MessageSender {
|
||||||
),
|
),
|
||||||
interactionId: interactionId,
|
interactionId: interactionId,
|
||||||
threadId: closedGroup.threadId,
|
threadId: closedGroup.threadId,
|
||||||
threadVariant: .legacyGroup
|
threadVariant: .legacyGroup,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
try addedMembers.forEach { member in
|
try addedMembers.forEach { member in
|
||||||
|
@ -446,7 +455,8 @@ extension MessageSender {
|
||||||
),
|
),
|
||||||
interactionId: nil,
|
interactionId: nil,
|
||||||
threadId: member,
|
threadId: member,
|
||||||
threadVariant: .contact
|
threadVariant: .contact,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
// Add the users to the group
|
// Add the users to the group
|
||||||
|
@ -469,7 +479,8 @@ extension MessageSender {
|
||||||
removedMembers: Set<String>,
|
removedMembers: Set<String>,
|
||||||
userPublicKey: String,
|
userPublicKey: String,
|
||||||
allGroupMembers: [GroupMember],
|
allGroupMembers: [GroupMember],
|
||||||
closedGroup: ClosedGroup
|
closedGroup: ClosedGroup,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
guard !removedMembers.contains(userPublicKey) else {
|
guard !removedMembers.contains(userPublicKey) else {
|
||||||
SNLog("Invalid closed group update.")
|
SNLog("Invalid closed group update.")
|
||||||
|
@ -490,7 +501,7 @@ extension MessageSender {
|
||||||
.map { $0.profileId }
|
.map { $0.profileId }
|
||||||
let members: Set<String> = Set(groupMemberIds).subtracting(removedMembers)
|
let members: Set<String> = Set(groupMemberIds).subtracting(removedMembers)
|
||||||
|
|
||||||
return Storage.shared
|
return dependencies.storage
|
||||||
.writePublisher { db in
|
.writePublisher { db in
|
||||||
// Update zombie & member list
|
// Update zombie & member list
|
||||||
try GroupMember
|
try GroupMember
|
||||||
|
@ -535,16 +546,18 @@ extension MessageSender {
|
||||||
namespace: try Message.Destination
|
namespace: try Message.Destination
|
||||||
.from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup)
|
.from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup)
|
||||||
.defaultNamespace,
|
.defaultNamespace,
|
||||||
interactionId: interactionId
|
interactionId: interactionId,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||||
.flatMap { _ -> AnyPublisher<Void, Error> in
|
.flatMap { _ -> AnyPublisher<Void, Error> in
|
||||||
MessageSender.generateAndSendNewEncryptionKeyPair(
|
MessageSender.generateAndSendNewEncryptionKeyPair(
|
||||||
targetMembers: members,
|
targetMembers: members,
|
||||||
userPublicKey: userPublicKey,
|
userPublicKey: userPublicKey,
|
||||||
allGroupMembers: allGroupMembers,
|
allGroupMembers: allGroupMembers,
|
||||||
closedGroup: closedGroup
|
closedGroup: closedGroup,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
@ -561,9 +574,10 @@ extension MessageSender {
|
||||||
public static func leave(
|
public static func leave(
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
groupPublicKey: String,
|
groupPublicKey: String,
|
||||||
deleteThread: Bool
|
deleteThread: Bool,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws {
|
) throws {
|
||||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||||
|
|
||||||
// Notify the user
|
// Notify the user
|
||||||
let interaction: Interaction = try Interaction(
|
let interaction: Interaction = try Interaction(
|
||||||
|
@ -574,7 +588,7 @@ extension MessageSender {
|
||||||
timestampMs: SnodeAPI.currentOffsetTimestampMs()
|
timestampMs: SnodeAPI.currentOffsetTimestampMs()
|
||||||
).inserted(db)
|
).inserted(db)
|
||||||
|
|
||||||
JobRunner.upsert(
|
dependencies.jobRunner.upsert(
|
||||||
db,
|
db,
|
||||||
job: Job(
|
job: Job(
|
||||||
variant: .groupLeaving,
|
variant: .groupLeaving,
|
||||||
|
@ -583,14 +597,17 @@ extension MessageSender {
|
||||||
details: GroupLeavingJob.Details(
|
details: GroupLeavingJob.Details(
|
||||||
deleteThread: deleteThread
|
deleteThread: deleteThread
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
canStartJob: true,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func sendLatestEncryptionKeyPair(
|
public static func sendLatestEncryptionKeyPair(
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
to publicKey: String,
|
to publicKey: String,
|
||||||
for groupPublicKey: String
|
for groupPublicKey: String,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) {
|
) {
|
||||||
guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: groupPublicKey) else {
|
guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: groupPublicKey) else {
|
||||||
return SNLog("Couldn't send key pair for nonexistent closed group.")
|
return SNLog("Couldn't send key pair for nonexistent closed group.")
|
||||||
|
@ -626,7 +643,8 @@ extension MessageSender {
|
||||||
let ciphertext = try MessageSender.encryptWithSessionProtocol(
|
let ciphertext = try MessageSender.encryptWithSessionProtocol(
|
||||||
db,
|
db,
|
||||||
plaintext: plaintext,
|
plaintext: plaintext,
|
||||||
for: publicKey
|
for: publicKey,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
SNLog("Sending latest encryption key pair to: \(publicKey).")
|
SNLog("Sending latest encryption key pair to: \(publicKey).")
|
||||||
|
@ -645,7 +663,8 @@ extension MessageSender {
|
||||||
),
|
),
|
||||||
interactionId: nil,
|
interactionId: nil,
|
||||||
threadId: thread.id,
|
threadId: thread.id,
|
||||||
threadVariant: thread.variant
|
threadVariant: thread.variant,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
catch {}
|
catch {}
|
||||||
|
|
|
@ -6,18 +6,24 @@ import Sodium
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
extension MessageReceiver {
|
extension MessageReceiver {
|
||||||
internal static func decryptWithSessionProtocol(ciphertext: Data, using x25519KeyPair: KeyPair, dependencies: SMKDependencies = SMKDependencies()) throws -> (plaintext: Data, senderX25519PublicKey: String) {
|
internal static func decryptWithSessionProtocol(
|
||||||
let recipientX25519PrivateKey = x25519KeyPair.secretKey
|
ciphertext: Data,
|
||||||
let recipientX25519PublicKey = x25519KeyPair.publicKey
|
using x25519KeyPair: KeyPair,
|
||||||
let signatureSize = dependencies.sign.Bytes
|
using dependencies: Dependencies = Dependencies()
|
||||||
let ed25519PublicKeySize = dependencies.sign.PublicKeyBytes
|
) 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
|
// 1. ) Decrypt the message
|
||||||
guard
|
guard
|
||||||
let plaintextWithMetadata = dependencies.box.open(
|
let plaintextWithMetadata = try? dependencies.crypto.perform(
|
||||||
anonymousCipherText: Bytes(ciphertext),
|
.open(
|
||||||
recipientPublicKey: Box.PublicKey(Bytes(recipientX25519PublicKey)),
|
anonymousCipherText: Bytes(ciphertext),
|
||||||
recipientSecretKey: Bytes(recipientX25519PrivateKey)
|
recipientPublicKey: Box.PublicKey(Bytes(recipientX25519PublicKey)),
|
||||||
|
recipientSecretKey: Bytes(recipientX25519PrivateKey)
|
||||||
|
)
|
||||||
),
|
),
|
||||||
plaintextWithMetadata.count > (signatureSize + ed25519PublicKeySize)
|
plaintextWithMetadata.count > (signatureSize + ed25519PublicKeySize)
|
||||||
else {
|
else {
|
||||||
|
@ -32,79 +38,100 @@ extension MessageReceiver {
|
||||||
// 3. ) Verify the signature
|
// 3. ) Verify the signature
|
||||||
let verificationData = plaintext + senderED25519PublicKey + recipientX25519PublicKey
|
let verificationData = plaintext + senderED25519PublicKey + recipientX25519PublicKey
|
||||||
|
|
||||||
guard dependencies.sign.verify(message: verificationData, publicKey: senderED25519PublicKey, signature: signature) else {
|
guard
|
||||||
throw MessageReceiverError.invalidSignature
|
dependencies.crypto.verify(
|
||||||
}
|
.signature(message: verificationData, publicKey: senderED25519PublicKey, signature: signature)
|
||||||
|
)
|
||||||
|
else { throw MessageReceiverError.invalidSignature }
|
||||||
|
|
||||||
// 4. ) Get the sender's X25519 public key
|
// 4. ) Get the sender's X25519 public key
|
||||||
guard let senderX25519PublicKey = dependencies.sign.toX25519(ed25519PublicKey: senderED25519PublicKey) else {
|
guard
|
||||||
throw MessageReceiverError.decryptionFailed
|
let senderX25519PublicKey = try? dependencies.crypto.perform(
|
||||||
}
|
.toX25519(ed25519PublicKey: senderED25519PublicKey)
|
||||||
|
)
|
||||||
|
else { throw MessageReceiverError.decryptionFailed }
|
||||||
|
|
||||||
return (Data(plaintext), SessionId(.standard, publicKey: senderX25519PublicKey).hexString)
|
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
|
/// Ensure the data is at least long enough to have the required components
|
||||||
guard
|
guard
|
||||||
data.count > (dependencies.nonceGenerator24.NonceBytes + 2),
|
data.count > (dependencies.crypto.size(.nonce24) + 2),
|
||||||
let blindedKeyPair = dependencies.sodium.blindedKeyPair(
|
let blindedKeyPair = dependencies.crypto.generate(
|
||||||
serverPublicKey: openGroupPublicKey,
|
.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, using: dependencies)
|
||||||
edKeyPair: userEd25519KeyPair,
|
|
||||||
genericHash: dependencies.genericHash
|
|
||||||
)
|
)
|
||||||
else { throw MessageReceiverError.decryptionFailed }
|
else { throw MessageReceiverError.decryptionFailed }
|
||||||
|
|
||||||
/// Step one: calculate the shared encryption key, receiving from A to B
|
/// Step one: calculate the shared encryption key, receiving from A to B
|
||||||
let otherKeyBytes: Bytes = Data(hex: otherBlindedPublicKey.removingIdPrefixIfNeeded()).bytes
|
let otherKeyBytes: Bytes = Data(hex: otherBlindedPublicKey.removingIdPrefixIfNeeded()).bytes
|
||||||
let kA: Bytes = (isOutgoing ? blindedKeyPair.publicKey : otherKeyBytes)
|
let kA: Bytes = (isOutgoing ? blindedKeyPair.publicKey : otherKeyBytes)
|
||||||
guard let dec_key: Bytes = dependencies.sodium.sharedBlindedEncryptionKey(
|
guard
|
||||||
secretKey: userEd25519KeyPair.secretKey,
|
let dec_key: Bytes = try? dependencies.crypto.perform(
|
||||||
otherBlindedPublicKey: otherKeyBytes,
|
.sharedBlindedEncryptionKey(
|
||||||
fromBlindedPublicKey: kA,
|
secretKey: userEd25519KeyPair.secretKey,
|
||||||
toBlindedPublicKey: (isOutgoing ? otherKeyBytes : blindedKeyPair.publicKey),
|
otherBlindedPublicKey: otherKeyBytes,
|
||||||
genericHash: dependencies.genericHash
|
fromBlindedPublicKey: kA,
|
||||||
) else {
|
toBlindedPublicKey: (isOutgoing ? otherKeyBytes : blindedKeyPair.publicKey),
|
||||||
throw MessageReceiverError.decryptionFailed
|
using: dependencies
|
||||||
}
|
)
|
||||||
|
)
|
||||||
|
else { throw MessageReceiverError.decryptionFailed }
|
||||||
|
|
||||||
/// v, ct, nc = data[0], data[1:-24], data[-24:]
|
/// v, ct, nc = data[0], data[1:-24], data[-24:]
|
||||||
let version: UInt8 = data.bytes[0]
|
let version: UInt8 = data.bytes[0]
|
||||||
let ciphertext: Bytes = Bytes(data.bytes[1..<(data.count - dependencies.nonceGenerator24.NonceBytes)])
|
let ciphertext: Bytes = Bytes(data.bytes[1..<(data.count - dependencies.crypto.size(.nonce24))])
|
||||||
let nonce: Bytes = Bytes(data.bytes[(data.count - dependencies.nonceGenerator24.NonceBytes)..<data.count])
|
let nonce: Bytes = Bytes(data.bytes[(data.count - dependencies.crypto.size(.nonce24))..<data.count])
|
||||||
|
|
||||||
/// Make sure our encryption version is okay
|
/// Make sure our encryption version is okay
|
||||||
guard version == 0 else { throw MessageReceiverError.decryptionFailed }
|
guard version == 0 else { throw MessageReceiverError.decryptionFailed }
|
||||||
|
|
||||||
/// Decrypt
|
/// Decrypt
|
||||||
guard let innerBytes: Bytes = dependencies.aeadXChaCha20Poly1305Ietf.decrypt(authenticatedCipherText: ciphertext, secretKey: dec_key, nonce: nonce) else {
|
guard
|
||||||
throw MessageReceiverError.decryptionFailed
|
let innerBytes: Bytes = try? dependencies.crypto.perform(
|
||||||
}
|
.decryptAeadXChaCha20(
|
||||||
|
authenticatedCipherText: ciphertext,
|
||||||
|
secretKey: dec_key,
|
||||||
|
nonce: nonce
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else { throw MessageReceiverError.decryptionFailed }
|
||||||
|
|
||||||
/// Ensure the length is correct
|
/// 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
|
/// Split up: the last 32 bytes are the sender's *unblinded* ed25519 key
|
||||||
let plaintext: Bytes = Bytes(innerBytes[
|
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[
|
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
|
/// 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 {
|
guard
|
||||||
throw MessageReceiverError.invalidSignature
|
let blindingFactor: Bytes = try? dependencies.crypto.perform(
|
||||||
}
|
.generateBlindingFactor(serverPublicKey: openGroupPublicKey, using: dependencies)
|
||||||
guard let sharedSecret: Bytes = dependencies.sodium.combineKeys(lhsKeyBytes: blindingFactor, rhsKeyBytes: sender_edpk) else {
|
),
|
||||||
throw MessageReceiverError.invalidSignature
|
let sharedSecret: Bytes = try? dependencies.crypto.perform(
|
||||||
}
|
.combineKeys(lhsKeyBytes: blindingFactor, rhsKeyBytes: sender_edpk)
|
||||||
guard kA == sharedSecret else { throw MessageReceiverError.invalidSignature }
|
),
|
||||||
|
kA == sharedSecret
|
||||||
|
else { throw MessageReceiverError.invalidSignature }
|
||||||
|
|
||||||
/// Get the sender's X25519 public key
|
/// Get the sender's X25519 public key
|
||||||
guard let senderSessionIdBytes: Bytes = dependencies.sign.toX25519(ed25519PublicKey: sender_edpk) else {
|
guard
|
||||||
throw MessageReceiverError.decryptionFailed
|
let senderSessionIdBytes: Bytes = try? dependencies.crypto.perform(
|
||||||
}
|
.toX25519(ed25519PublicKey: sender_edpk)
|
||||||
|
)
|
||||||
|
else { throw MessageReceiverError.decryptionFailed }
|
||||||
|
|
||||||
return (Data(plaintext), SessionId(.standard, publicKey: senderSessionIdBytes).hexString)
|
return (Data(plaintext), SessionId(.standard, publicKey: senderSessionIdBytes).hexString)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@ public enum MessageReceiver {
|
||||||
openGroupServerPublicKey: String?,
|
openGroupServerPublicKey: String?,
|
||||||
isOutgoing: Bool? = nil,
|
isOutgoing: Bool? = nil,
|
||||||
otherBlindedPublicKey: String? = nil,
|
otherBlindedPublicKey: String? = nil,
|
||||||
dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> (Message, SNProtoContent, String, SessionThread.Variant) {
|
) 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)
|
let isOpenGroupMessage: Bool = (openGroupId != nil)
|
||||||
|
|
||||||
// Decrypt the contents
|
// Decrypt the contents
|
||||||
|
@ -183,7 +183,7 @@ public enum MessageReceiver {
|
||||||
message: Message,
|
message: Message,
|
||||||
serverExpirationTimestamp: TimeInterval?,
|
serverExpirationTimestamp: TimeInterval?,
|
||||||
associatedWithProto proto: SNProtoContent,
|
associatedWithProto proto: SNProtoContent,
|
||||||
dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws {
|
) throws {
|
||||||
// Check if the message requires an existing conversation (if it does and the conversation isn't in
|
// 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)
|
// the config then the message will be dropped)
|
||||||
|
@ -198,7 +198,7 @@ public enum MessageReceiver {
|
||||||
message: message,
|
message: message,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: threadVariant,
|
threadVariant: threadVariant,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
switch message {
|
switch message {
|
||||||
|
@ -222,7 +222,8 @@ public enum MessageReceiver {
|
||||||
db,
|
db,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: threadVariant,
|
threadVariant: threadVariant,
|
||||||
message: message
|
message: message,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
case let message as DataExtractionNotification:
|
case let message as DataExtractionNotification:
|
||||||
|
@ -242,7 +243,11 @@ public enum MessageReceiver {
|
||||||
)
|
)
|
||||||
|
|
||||||
case let message as ConfigurationMessage:
|
case let message as ConfigurationMessage:
|
||||||
try MessageReceiver.handleLegacyConfigurationMessage(db, message: message)
|
try MessageReceiver.handleLegacyConfigurationMessage(
|
||||||
|
db,
|
||||||
|
message: message,
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
|
|
||||||
case let message as UnsendRequest:
|
case let message as UnsendRequest:
|
||||||
try MessageReceiver.handleUnsendRequest(
|
try MessageReceiver.handleUnsendRequest(
|
||||||
|
@ -264,7 +269,7 @@ public enum MessageReceiver {
|
||||||
try MessageReceiver.handleMessageRequestResponse(
|
try MessageReceiver.handleMessageRequestResponse(
|
||||||
db,
|
db,
|
||||||
message: message,
|
message: message,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
case let message as VisibleMessage:
|
case let message as VisibleMessage:
|
||||||
|
@ -360,7 +365,7 @@ public enum MessageReceiver {
|
||||||
message: Message,
|
message: Message,
|
||||||
threadId: String,
|
threadId: String,
|
||||||
threadVariant: SessionThread.Variant,
|
threadVariant: SessionThread.Variant,
|
||||||
dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws {
|
) throws {
|
||||||
switch message {
|
switch message {
|
||||||
case is ReadReceipt: return // No visible artifact created so better to keep for more reliable read states
|
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
|
// 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(
|
let conversationVisibleInConfig: Bool = SessionUtil.conversationInConfig(
|
||||||
db,
|
db,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
|
|
|
@ -14,7 +14,8 @@ extension MessageSender {
|
||||||
interaction: Interaction,
|
interaction: Interaction,
|
||||||
threadId: String,
|
threadId: String,
|
||||||
threadVariant: SessionThread.Variant,
|
threadVariant: SessionThread.Variant,
|
||||||
isSyncMessage: Bool = false
|
isSyncMessage: Bool = false,
|
||||||
|
using dependencies: Dependencies
|
||||||
) throws {
|
) throws {
|
||||||
// Only 'VisibleMessage' types can be sent via this method
|
// Only 'VisibleMessage' types can be sent via this method
|
||||||
guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage }
|
guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage }
|
||||||
|
@ -26,7 +27,8 @@ extension MessageSender {
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
interactionId: interactionId,
|
interactionId: interactionId,
|
||||||
to: try Message.Destination.from(db, threadId: threadId, threadVariant: threadVariant),
|
to: try Message.Destination.from(db, threadId: threadId, threadVariant: threadVariant),
|
||||||
isSyncMessage: isSyncMessage
|
isSyncMessage: isSyncMessage,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +38,8 @@ extension MessageSender {
|
||||||
interactionId: Int64?,
|
interactionId: Int64?,
|
||||||
threadId: String,
|
threadId: String,
|
||||||
threadVariant: SessionThread.Variant,
|
threadVariant: SessionThread.Variant,
|
||||||
isSyncMessage: Bool = false
|
isSyncMessage: Bool = false,
|
||||||
|
using dependencies: Dependencies
|
||||||
) throws {
|
) throws {
|
||||||
send(
|
send(
|
||||||
db,
|
db,
|
||||||
|
@ -44,7 +47,8 @@ extension MessageSender {
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
interactionId: interactionId,
|
interactionId: interactionId,
|
||||||
to: try Message.Destination.from(db, threadId: threadId, threadVariant: threadVariant),
|
to: try Message.Destination.from(db, threadId: threadId, threadVariant: threadVariant),
|
||||||
isSyncMessage: isSyncMessage
|
isSyncMessage: isSyncMessage,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +58,8 @@ extension MessageSender {
|
||||||
threadId: String?,
|
threadId: String?,
|
||||||
interactionId: Int64?,
|
interactionId: Int64?,
|
||||||
to destination: Message.Destination,
|
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
|
// 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
|
// sync message sending process instead of the standard process
|
||||||
|
@ -65,12 +70,13 @@ extension MessageSender {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
interactionId: interactionId,
|
interactionId: interactionId,
|
||||||
isAlreadySyncMessage: false
|
isAlreadySyncMessage: false,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
JobRunner.add(
|
dependencies.jobRunner.add(
|
||||||
db,
|
db,
|
||||||
job: Job(
|
job: Job(
|
||||||
variant: .messageSend,
|
variant: .messageSend,
|
||||||
|
@ -81,7 +87,9 @@ extension MessageSender {
|
||||||
message: message,
|
message: message,
|
||||||
isSyncMessage: isSyncMessage
|
isSyncMessage: isSyncMessage
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
canStartJob: true,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +99,8 @@ extension MessageSender {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
interaction: Interaction,
|
interaction: Interaction,
|
||||||
threadId: String,
|
threadId: String,
|
||||||
threadVariant: SessionThread.Variant
|
threadVariant: SessionThread.Variant,
|
||||||
|
using dependencies: Dependencies
|
||||||
) throws -> PreparedSendData {
|
) throws -> PreparedSendData {
|
||||||
// Only 'VisibleMessage' types can be sent via this method
|
// Only 'VisibleMessage' types can be sent via this method
|
||||||
guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage }
|
guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage }
|
||||||
|
@ -104,11 +113,15 @@ extension MessageSender {
|
||||||
namespace: try Message.Destination
|
namespace: try Message.Destination
|
||||||
.from(db, threadId: threadId, threadVariant: threadVariant)
|
.from(db, threadId: threadId, threadVariant: threadVariant)
|
||||||
.defaultNamespace,
|
.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
|
// We need an interactionId in order for a message to have uploads
|
||||||
guard let interactionId: Int64 = preparedSendData.interactionId else {
|
guard let interactionId: Int64 = preparedSendData.interactionId else {
|
||||||
return Just(preparedSendData)
|
return Just(preparedSendData)
|
||||||
|
@ -127,7 +140,7 @@ extension MessageSender {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return Storage.shared
|
return dependencies.storage
|
||||||
.readPublisher { db -> (attachments: [Attachment], openGroup: OpenGroup?) in
|
.readPublisher { db -> (attachments: [Attachment], openGroup: OpenGroup?) in
|
||||||
let attachmentStateInfo: [Attachment.StateInfo] = (try? Attachment
|
let attachmentStateInfo: [Attachment.StateInfo] = (try? Attachment
|
||||||
.stateInfo(interactionId: interactionId, state: .uploading)
|
.stateInfo(interactionId: interactionId, state: .uploading)
|
||||||
|
@ -162,7 +175,8 @@ extension MessageSender {
|
||||||
to: (
|
to: (
|
||||||
openGroup.map { Attachment.Destination.openGroup($0) } ??
|
openGroup.map { Attachment.Destination.openGroup($0) } ??
|
||||||
.fileServer
|
.fileServer
|
||||||
)
|
),
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,7 +10,7 @@ extension MessageSender {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
plaintext: Data,
|
plaintext: Data,
|
||||||
for recipientHexEncodedX25519PublicKey: String,
|
for recipientHexEncodedX25519PublicKey: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) throws -> Data {
|
) throws -> Data {
|
||||||
guard let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else {
|
guard let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else {
|
||||||
throw MessageSenderError.noUserED25519KeyPair
|
throw MessageSenderError.noUserED25519KeyPair
|
||||||
|
@ -19,14 +19,21 @@ extension MessageSender {
|
||||||
let recipientX25519PublicKey = Data(hex: recipientHexEncodedX25519PublicKey.removingIdPrefixIfNeeded())
|
let recipientX25519PublicKey = Data(hex: recipientHexEncodedX25519PublicKey.removingIdPrefixIfNeeded())
|
||||||
|
|
||||||
let verificationData = plaintext + Data(userEd25519KeyPair.publicKey) + recipientX25519PublicKey
|
let verificationData = plaintext + Data(userEd25519KeyPair.publicKey) + recipientX25519PublicKey
|
||||||
guard let signature = dependencies.sign.signature(message: Bytes(verificationData), secretKey: userEd25519KeyPair.secretKey) else {
|
guard
|
||||||
throw MessageSenderError.signingFailed
|
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)
|
let plaintextWithMetadata = plaintext + Data(userEd25519KeyPair.publicKey) + Data(signature)
|
||||||
guard let ciphertext = dependencies.box.seal(message: Bytes(plaintextWithMetadata), recipientPublicKey: Bytes(recipientX25519PublicKey)) else {
|
guard
|
||||||
throw MessageSenderError.encryptionFailed
|
let ciphertext = try? dependencies.crypto.perform(
|
||||||
}
|
.seal(
|
||||||
|
message: Bytes(plaintextWithMetadata),
|
||||||
|
recipientPublicKey: Bytes(recipientX25519PublicKey)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else { throw MessageSenderError.encryptionFailed }
|
||||||
|
|
||||||
return Data(ciphertext)
|
return Data(ciphertext)
|
||||||
}
|
}
|
||||||
|
@ -36,7 +43,7 @@ extension MessageSender {
|
||||||
plaintext: Data,
|
plaintext: Data,
|
||||||
for recipientBlindedId: String,
|
for recipientBlindedId: String,
|
||||||
openGroupPublicKey: String,
|
openGroupPublicKey: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) throws -> Data {
|
) throws -> Data {
|
||||||
guard
|
guard
|
||||||
SessionId.Prefix(from: recipientBlindedId) == .blinded15 ||
|
SessionId.Prefix(from: recipientBlindedId) == .blinded15 ||
|
||||||
|
@ -45,32 +52,37 @@ extension MessageSender {
|
||||||
guard let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else {
|
guard let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else {
|
||||||
throw MessageSenderError.noUserED25519KeyPair
|
throw MessageSenderError.noUserED25519KeyPair
|
||||||
}
|
}
|
||||||
guard let blindedKeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, genericHash: dependencies.genericHash) else {
|
guard
|
||||||
throw MessageSenderError.signingFailed
|
let blindedKeyPair = dependencies.crypto.generate(
|
||||||
}
|
.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, using: dependencies)
|
||||||
|
)
|
||||||
|
else { throw MessageSenderError.signingFailed }
|
||||||
|
|
||||||
let recipientBlindedPublicKey = Data(hex: recipientBlindedId.removingIdPrefixIfNeeded())
|
let recipientBlindedPublicKey = Data(hex: recipientBlindedId.removingIdPrefixIfNeeded())
|
||||||
|
|
||||||
/// Step one: calculate the shared encryption key, sending from A to B
|
/// Step one: calculate the shared encryption key, sending from A to B
|
||||||
guard let enc_key: Bytes = dependencies.sodium.sharedBlindedEncryptionKey(
|
guard
|
||||||
secretKey: userEd25519KeyPair.secretKey,
|
let enc_key: Bytes = try? dependencies.crypto.perform(
|
||||||
otherBlindedPublicKey: recipientBlindedPublicKey.bytes,
|
.sharedBlindedEncryptionKey(
|
||||||
fromBlindedPublicKey: blindedKeyPair.publicKey,
|
secretKey: userEd25519KeyPair.secretKey,
|
||||||
toBlindedPublicKey: recipientBlindedPublicKey.bytes,
|
otherBlindedPublicKey: recipientBlindedPublicKey.bytes,
|
||||||
genericHash: dependencies.genericHash
|
fromBlindedPublicKey: blindedKeyPair.publicKey,
|
||||||
) else {
|
toBlindedPublicKey: recipientBlindedPublicKey.bytes,
|
||||||
throw MessageSenderError.signingFailed
|
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)
|
/// Inner data: msg || A (i.e. the sender's ed25519 master pubkey, *not* kA blinded pubkey)
|
||||||
let innerBytes: Bytes = (plaintext.bytes + userEd25519KeyPair.publicKey)
|
let innerBytes: Bytes = (plaintext.bytes + userEd25519KeyPair.publicKey)
|
||||||
|
|
||||||
/// Encrypt using xchacha20-poly1305
|
/// Encrypt using xchacha20-poly1305
|
||||||
let nonce: Bytes = dependencies.nonceGenerator24.nonce()
|
guard
|
||||||
|
let ciphertext = try? dependencies.crypto.perform(
|
||||||
guard let ciphertext = dependencies.aeadXChaCha20Poly1305Ietf.encrypt(message: innerBytes, secretKey: enc_key, nonce: nonce) else {
|
.encryptAeadXChaCha20(message: innerBytes, secretKey: enc_key, nonce: nonce, using: dependencies)
|
||||||
throw MessageSenderError.encryptionFailed
|
)
|
||||||
}
|
else { throw MessageSenderError.encryptionFailed }
|
||||||
|
|
||||||
/// data = b'\x00' + ciphertext + nonce
|
/// data = b'\x00' + ciphertext + nonce
|
||||||
return Data(Bytes(arrayLiteral: 0) + ciphertext + nonce)
|
return Data(Bytes(arrayLiteral: 0) + ciphertext + nonce)
|
||||||
|
|
|
@ -140,10 +140,10 @@ public final class MessageSender {
|
||||||
namespace: SnodeAPI.Namespace?,
|
namespace: SnodeAPI.Namespace?,
|
||||||
interactionId: Int64?,
|
interactionId: Int64?,
|
||||||
isSyncMessage: Bool = false,
|
isSyncMessage: Bool = false,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws -> PreparedSendData {
|
) throws -> PreparedSendData {
|
||||||
// Common logic for all destinations
|
// 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 messageSendTimestamp: Int64 = SnodeAPI.currentOffsetTimestampMs()
|
||||||
let updatedMessage: Message = message
|
let updatedMessage: Message = message
|
||||||
|
|
||||||
|
@ -199,7 +199,7 @@ public final class MessageSender {
|
||||||
userPublicKey: String,
|
userPublicKey: String,
|
||||||
messageSendTimestamp: Int64,
|
messageSendTimestamp: Int64,
|
||||||
isSyncMessage: Bool = false,
|
isSyncMessage: Bool = false,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) throws -> PreparedSendData {
|
) throws -> PreparedSendData {
|
||||||
message.sender = userPublicKey
|
message.sender = userPublicKey
|
||||||
message.recipient = {
|
message.recipient = {
|
||||||
|
@ -276,7 +276,7 @@ public final class MessageSender {
|
||||||
do {
|
do {
|
||||||
switch destination {
|
switch destination {
|
||||||
case .contact(let publicKey):
|
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):
|
case .closedGroup(let groupPublicKey):
|
||||||
guard let encryptionKeyPair: ClosedGroupKeyPair = try? ClosedGroupKeyPair.fetchLatestKeyPair(db, threadId: groupPublicKey) else {
|
guard let encryptionKeyPair: ClosedGroupKeyPair = try? ClosedGroupKeyPair.fetchLatestKeyPair(db, threadId: groupPublicKey) else {
|
||||||
|
@ -286,7 +286,8 @@ public final class MessageSender {
|
||||||
ciphertext = try encryptWithSessionProtocol(
|
ciphertext = try encryptWithSessionProtocol(
|
||||||
db,
|
db,
|
||||||
plaintext: plaintext,
|
plaintext: plaintext,
|
||||||
for: SessionId(.standard, publicKey: encryptionKeyPair.publicKey.bytes).hexString
|
for: SessionId(.standard, publicKey: encryptionKeyPair.publicKey.bytes).hexString,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
case .openGroup, .openGroupInbox: preconditionFailure()
|
case .openGroup, .openGroupInbox: preconditionFailure()
|
||||||
|
@ -365,7 +366,7 @@ public final class MessageSender {
|
||||||
to destination: Message.Destination,
|
to destination: Message.Destination,
|
||||||
interactionId: Int64?,
|
interactionId: Int64?,
|
||||||
messageSendTimestamp: Int64,
|
messageSendTimestamp: Int64,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) throws -> PreparedSendData {
|
) throws -> PreparedSendData {
|
||||||
let threadId: String
|
let threadId: String
|
||||||
|
|
||||||
|
@ -394,7 +395,7 @@ public final class MessageSender {
|
||||||
throw MessageSenderError.invalidMessage
|
throw MessageSenderError.invalidMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
message.sender = {
|
message.sender = try {
|
||||||
let capabilities: [Capability.Variant] = (try? Capability
|
let capabilities: [Capability.Variant] = (try? Capability
|
||||||
.select(.variant)
|
.select(.variant)
|
||||||
.filter(Capability.Columns.openGroupServer == server)
|
.filter(Capability.Columns.openGroupServer == server)
|
||||||
|
@ -407,9 +408,11 @@ public final class MessageSender {
|
||||||
guard capabilities.isEmpty || capabilities.contains(.blind) else {
|
guard capabilities.isEmpty || capabilities.contains(.blind) else {
|
||||||
return SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString
|
return SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString
|
||||||
}
|
}
|
||||||
guard let blindedKeyPair: KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroup.publicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else {
|
guard
|
||||||
preconditionFailure()
|
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
|
return SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString
|
||||||
}()
|
}()
|
||||||
|
@ -492,7 +495,7 @@ public final class MessageSender {
|
||||||
interactionId: Int64?,
|
interactionId: Int64?,
|
||||||
userPublicKey: String,
|
userPublicKey: String,
|
||||||
messageSendTimestamp: Int64,
|
messageSendTimestamp: Int64,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) throws -> PreparedSendData {
|
) throws -> PreparedSendData {
|
||||||
guard case .openGroupInbox(_, let openGroupPublicKey, let recipientBlindedPublicKey) = destination else {
|
guard case .openGroupInbox(_, let openGroupPublicKey, let recipientBlindedPublicKey) = destination else {
|
||||||
throw MessageSenderError.invalidMessage
|
throw MessageSenderError.invalidMessage
|
||||||
|
@ -582,10 +585,10 @@ public final class MessageSender {
|
||||||
// MARK: - Sending
|
// MARK: - Sending
|
||||||
|
|
||||||
public static func sendImmediate(
|
public static func sendImmediate(
|
||||||
preparedSendData: PreparedSendData,
|
data: PreparedSendData,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
guard preparedSendData.shouldSend else {
|
guard data.shouldSend else {
|
||||||
return Just(())
|
return Just(())
|
||||||
.setFailureType(to: Error.self)
|
.setFailureType(to: Error.self)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
|
@ -597,7 +600,7 @@ public final class MessageSender {
|
||||||
//
|
//
|
||||||
// If you see this error then you need to call
|
// If you see this error then you need to call
|
||||||
// `MessageSender.performUploadsIfNeeded(queue:preparedSendData:)` before calling this function
|
// `MessageSender.performUploadsIfNeeded(queue:preparedSendData:)` before calling this function
|
||||||
switch preparedSendData.message {
|
switch data.message {
|
||||||
case let visibleMessage as VisibleMessage:
|
case let visibleMessage as VisibleMessage:
|
||||||
let expectedAttachmentUploadCount: Int = (
|
let expectedAttachmentUploadCount: Int = (
|
||||||
visibleMessage.attachmentIds.count +
|
visibleMessage.attachmentIds.count +
|
||||||
|
@ -605,17 +608,17 @@ public final class MessageSender {
|
||||||
(visibleMessage.quote?.attachmentId != nil ? 1 : 0)
|
(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
|
// Make sure to actually handle this as a failure (if we don't then the message
|
||||||
// won't go into an error state correctly)
|
// 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
|
dependencies.storage.read { db in
|
||||||
MessageSender.handleFailedMessageSend(
|
MessageSender.handleFailedMessageSend(
|
||||||
db,
|
db,
|
||||||
message: message,
|
message: message,
|
||||||
with: .attachmentsNotUploaded,
|
with: .attachmentsNotUploaded,
|
||||||
interactionId: preparedSendData.interactionId,
|
interactionId: data.interactionId,
|
||||||
isSyncMessage: (preparedSendData.isSyncMessage == true),
|
isSyncMessage: (data.isSyncMessage == true),
|
||||||
using: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -630,10 +633,10 @@ public final class MessageSender {
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
|
|
||||||
switch preparedSendData.destination {
|
switch data.destination {
|
||||||
case .contact, .closedGroup: return sendToSnodeDestination(data: preparedSendData, using: dependencies)
|
case .contact, .closedGroup: return sendToSnodeDestination(data: data, using: dependencies)
|
||||||
case .openGroup: return sendToOpenGroupDestination(data: preparedSendData, using: dependencies)
|
case .openGroup: return sendToOpenGroupDestination(data: data, using: dependencies)
|
||||||
case .openGroupInbox: return sendToOpenGroupInbox(data: preparedSendData, using: dependencies)
|
case .openGroupInbox: return sendToOpenGroupInbox(data: data, using: dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -641,7 +644,7 @@ public final class MessageSender {
|
||||||
|
|
||||||
private static func sendToSnodeDestination(
|
private static func sendToSnodeDestination(
|
||||||
data: PreparedSendData,
|
data: PreparedSendData,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
guard
|
guard
|
||||||
let message: Message = data.message,
|
let message: Message = data.message,
|
||||||
|
@ -653,14 +656,11 @@ public final class MessageSender {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
return SnodeAPI
|
return dependencies.network
|
||||||
.sendMessage(
|
.send(.message(snodeMessage, in: namespace, using: dependencies))
|
||||||
snodeMessage,
|
.flatMap { info, response -> AnyPublisher<Void, Error> in
|
||||||
in: namespace
|
|
||||||
)
|
|
||||||
.flatMap { response -> AnyPublisher<Void, Error> in
|
|
||||||
let updatedMessage: Message = message
|
let updatedMessage: Message = message
|
||||||
updatedMessage.serverHash = response.1.hash
|
updatedMessage.serverHash = response.hash
|
||||||
|
|
||||||
let job: Job? = Job(
|
let job: Job? = Job(
|
||||||
variant: .notifyPushServer,
|
variant: .notifyPushServer,
|
||||||
|
@ -695,7 +695,7 @@ public final class MessageSender {
|
||||||
|
|
||||||
guard shouldNotify else { return () }
|
guard shouldNotify else { return () }
|
||||||
|
|
||||||
JobRunner.add(db, job: job)
|
dependencies.jobRunner.add(db, job: job, canStartJob: true, using: dependencies)
|
||||||
return ()
|
return ()
|
||||||
}
|
}
|
||||||
.flatMap { _ -> AnyPublisher<Void, Error> in
|
.flatMap { _ -> AnyPublisher<Void, Error> in
|
||||||
|
@ -726,7 +726,8 @@ public final class MessageSender {
|
||||||
deferred: { _, _ in
|
deferred: { _, _ in
|
||||||
// Always fulfill because the notify PN server job isn't critical.
|
// Always fulfill because the notify PN server job isn't critical.
|
||||||
resolver(Result.success(()))
|
resolver(Result.success(()))
|
||||||
}
|
},
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -761,7 +762,7 @@ public final class MessageSender {
|
||||||
|
|
||||||
private static func sendToOpenGroupDestination(
|
private static func sendToOpenGroupDestination(
|
||||||
data: PreparedSendData,
|
data: PreparedSendData,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
guard
|
guard
|
||||||
let message: Message = data.message,
|
let message: Message = data.message,
|
||||||
|
@ -829,7 +830,7 @@ public final class MessageSender {
|
||||||
|
|
||||||
private static func sendToOpenGroupInbox(
|
private static func sendToOpenGroupInbox(
|
||||||
data: PreparedSendData,
|
data: PreparedSendData,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
guard
|
guard
|
||||||
let message: Message = data.message,
|
let message: Message = data.message,
|
||||||
|
@ -926,7 +927,7 @@ public final class MessageSender {
|
||||||
interactionId: Int64?,
|
interactionId: Int64?,
|
||||||
serverTimestampMs: UInt64? = nil,
|
serverTimestampMs: UInt64? = nil,
|
||||||
isSyncMessage: Bool = false,
|
isSyncMessage: Bool = false,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) throws {
|
) throws {
|
||||||
// If the message was a reaction then we want to update the reaction instead of the original
|
// If the message was a reaction then we want to update the reaction instead of the original
|
||||||
// interaction (which the 'interactionId' is pointing to
|
// 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))
|
.updateAll(db, RecipientState.Columns.state.set(to: RecipientState.State.sent))
|
||||||
|
|
||||||
// Start the disappearing messages timer if needed
|
// Start the disappearing messages timer if needed
|
||||||
JobRunner.upsert(
|
dependencies.jobRunner.upsert(
|
||||||
db,
|
db,
|
||||||
job: DisappearingMessagesJob.updateNextRunIfNeeded(
|
job: DisappearingMessagesJob.updateNextRunIfNeeded(
|
||||||
db,
|
db,
|
||||||
interaction: interaction,
|
interaction: interaction,
|
||||||
startedAtMs: TimeInterval(SnodeAPI.currentOffsetTimestampMs())
|
startedAtMs: TimeInterval(SnodeAPI.currentOffsetTimestampMs())
|
||||||
)
|
),
|
||||||
|
canStartJob: true,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -995,7 +998,8 @@ public final class MessageSender {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
interactionId: interactionId,
|
interactionId: interactionId,
|
||||||
isAlreadySyncMessage: isSyncMessage
|
isAlreadySyncMessage: isSyncMessage,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1005,7 +1009,7 @@ public final class MessageSender {
|
||||||
with error: MessageSenderError,
|
with error: MessageSenderError,
|
||||||
interactionId: Int64?,
|
interactionId: Int64?,
|
||||||
isSyncMessage: Bool = false,
|
isSyncMessage: Bool = false,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) -> Error {
|
) -> Error {
|
||||||
// If the message was a reaction then we don't want to do anything to the original
|
// If the message was a reaction then we don't want to do anything to the original
|
||||||
// interaciton (which the 'interactionId' is pointing to
|
// interaciton (which the 'interactionId' is pointing to
|
||||||
|
@ -1072,11 +1076,12 @@ public final class MessageSender {
|
||||||
destination: Message.Destination,
|
destination: Message.Destination,
|
||||||
threadId: String?,
|
threadId: String?,
|
||||||
interactionId: Int64?,
|
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
|
// 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
|
// it's a message type which should be synced
|
||||||
let currentUserPublicKey = getUserHexEncodedPublicKey(db)
|
let currentUserPublicKey = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||||
|
|
||||||
if
|
if
|
||||||
case .contact(let publicKey) = destination,
|
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? VisibleMessage { message.syncTarget = publicKey }
|
||||||
if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey }
|
if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey }
|
||||||
|
|
||||||
JobRunner.add(
|
dependencies.jobRunner.add(
|
||||||
db,
|
db,
|
||||||
job: Job(
|
job: Job(
|
||||||
variant: .messageSend,
|
variant: .messageSend,
|
||||||
|
@ -1098,7 +1103,9 @@ public final class MessageSender {
|
||||||
message: message,
|
message: message,
|
||||||
isSyncMessage: true
|
isSyncMessage: true
|
||||||
)
|
)
|
||||||
)
|
),
|
||||||
|
canStartJob: true,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,23 +23,23 @@ public final class ClosedGroupPoller: Poller {
|
||||||
|
|
||||||
// MARK: - Public API
|
// 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
|
// 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)
|
// GroupMemeber as the user is no longer a member of those)
|
||||||
Storage.shared
|
dependencies.storage
|
||||||
.read { db in
|
.read { db in
|
||||||
try ClosedGroup
|
try ClosedGroup
|
||||||
.select(.threadId)
|
.select(.threadId)
|
||||||
.joining(
|
.joining(
|
||||||
required: ClosedGroup.members
|
required: ClosedGroup.members
|
||||||
.filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db))
|
.filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db, using: dependencies))
|
||||||
)
|
)
|
||||||
.asRequest(of: String.self)
|
.asRequest(of: String.self)
|
||||||
.fetchAll(db)
|
.fetchAll(db)
|
||||||
}
|
}
|
||||||
.defaulting(to: [])
|
.defaulting(to: [])
|
||||||
.forEach { [weak self] publicKey in
|
.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)"
|
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
|
// 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
|
// any messages yet, pick some reasonable fake time interval to use instead
|
||||||
let lastMessageDate: Date = Storage.shared
|
let lastMessageDate: Date = Storage.shared
|
||||||
|
@ -68,7 +68,7 @@ public final class ClosedGroupPoller: Poller {
|
||||||
}
|
}
|
||||||
.defaulting(to: Date().addingTimeInterval(-5 * 60))
|
.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 minPollInterval: Double = ClosedGroupPoller.minPollInterval
|
||||||
let limit: Double = (12 * 60 * 60)
|
let limit: Double = (12 * 60 * 60)
|
||||||
let a: TimeInterval = ((ClosedGroupPoller.maxPollInterval - minPollInterval) / limit)
|
let a: TimeInterval = ((ClosedGroupPoller.maxPollInterval - minPollInterval) / limit)
|
||||||
|
@ -78,11 +78,7 @@ public final class ClosedGroupPoller: Poller {
|
||||||
return nextPollInterval
|
return nextPollInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
override func handlePollError(
|
override func handlePollError(_ error: Error, for publicKey: String, using dependencies: Dependencies) -> Bool {
|
||||||
_ error: Error,
|
|
||||||
for publicKey: String,
|
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
|
||||||
) -> Bool {
|
|
||||||
SNLog("Polling failed for closed group with public key: \(publicKey) due to error: \(error).")
|
SNLog("Polling failed for closed group with public key: \(publicKey) due to error: \(error).")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,13 +33,13 @@ public final class CurrentUserPoller: Poller {
|
||||||
|
|
||||||
// MARK: - Convenience Functions
|
// MARK: - Convenience Functions
|
||||||
|
|
||||||
public func start() {
|
public func start(using dependencies: Dependencies = Dependencies()) {
|
||||||
let publicKey: String = getUserHexEncodedPublicKey()
|
let publicKey: String = getUserHexEncodedPublicKey(using: dependencies)
|
||||||
|
|
||||||
guard isPolling.wrappedValue[publicKey] != true else { return }
|
guard isPolling.wrappedValue[publicKey] != true else { return }
|
||||||
|
|
||||||
SNLog("Started polling.")
|
SNLog("Started polling.")
|
||||||
super.startIfNeeded(for: publicKey)
|
super.startIfNeeded(for: publicKey, using: dependencies)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func stop() {
|
public func stop() {
|
||||||
|
@ -53,7 +53,7 @@ public final class CurrentUserPoller: Poller {
|
||||||
return "Main 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)
|
let failureCount: TimeInterval = TimeInterval(failureCount.wrappedValue[publicKey] ?? 0)
|
||||||
|
|
||||||
// If there have been no failures then just use the 'minPollInterval'
|
// If there have been no failures then just use the 'minPollInterval'
|
||||||
|
@ -65,11 +65,7 @@ public final class CurrentUserPoller: Poller {
|
||||||
return min(maxRetryInterval, nextDelay)
|
return min(maxRetryInterval, nextDelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func handlePollError(
|
override func handlePollError(_ error: Error, for publicKey: String, using dependencies: Dependencies) -> Bool {
|
||||||
_ error: Error,
|
|
||||||
for publicKey: String,
|
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
|
||||||
) -> Bool {
|
|
||||||
if UserDefaults.sharedLokiProject?[.isMainAppActive] != true {
|
if UserDefaults.sharedLokiProject?[.isMainAppActive] != true {
|
||||||
// Do nothing when an error gets throws right after returning from the background (happens frequently)
|
// Do nothing when an error gets throws right after returning from the background (happens frequently)
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ extension OpenGroupAPI {
|
||||||
self.server = server
|
self.server = server
|
||||||
}
|
}
|
||||||
|
|
||||||
public func startIfNeeded(using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()) {
|
public func startIfNeeded(using dependencies: Dependencies) {
|
||||||
guard !hasStarted else { return }
|
guard !hasStarted else { return }
|
||||||
|
|
||||||
hasStarted = true
|
hasStarted = true
|
||||||
|
@ -49,20 +49,15 @@ extension OpenGroupAPI {
|
||||||
|
|
||||||
// MARK: - Polling
|
// MARK: - Polling
|
||||||
|
|
||||||
private func pollRecursively(
|
private func pollRecursively(using dependencies: Dependencies) {
|
||||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies(
|
|
||||||
subscribeQueue: Threading.pollerQueue,
|
|
||||||
receiveQueue: OpenGroupAPI.workQueue
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
guard hasStarted else { return }
|
guard hasStarted else { return }
|
||||||
|
|
||||||
let server: String = self.server
|
let server: String = self.server
|
||||||
let lastPollStart: TimeInterval = Date().timeIntervalSince1970
|
let lastPollStart: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||||
|
|
||||||
poll(using: dependencies)
|
poll(using: dependencies)
|
||||||
.subscribe(on: dependencies.subscribeQueue)
|
.subscribe(on: Threading.pollerQueue, using: dependencies)
|
||||||
.receive(on: dependencies.receiveQueue)
|
.receive(on: OpenGroupAPI.workQueue, using: dependencies)
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
receiveCompletion: { [weak self] _ in
|
receiveCompletion: { [weak self] _ in
|
||||||
let minPollFailureCount: Int64 = dependencies.storage
|
let minPollFailureCount: Int64 = dependencies.storage
|
||||||
|
@ -76,7 +71,7 @@ extension OpenGroupAPI {
|
||||||
.defaulting(to: 0)
|
.defaulting(to: 0)
|
||||||
|
|
||||||
// Calculate the remaining poll delay
|
// Calculate the remaining poll delay
|
||||||
let currentTime: TimeInterval = Date().timeIntervalSince1970
|
let currentTime: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||||
let nextPollInterval: TimeInterval = Poller.getInterval(
|
let nextPollInterval: TimeInterval = Poller.getInterval(
|
||||||
for: TimeInterval(minPollFailureCount),
|
for: TimeInterval(minPollFailureCount),
|
||||||
minInterval: Poller.minPollInterval,
|
minInterval: Poller.minPollInterval,
|
||||||
|
@ -86,12 +81,12 @@ extension OpenGroupAPI {
|
||||||
|
|
||||||
// Schedule the next poll
|
// Schedule the next poll
|
||||||
guard remainingInterval > 0 else {
|
guard remainingInterval > 0 else {
|
||||||
return dependencies.subscribeQueue.async {
|
return Threading.pollerQueue.async(using: dependencies) {
|
||||||
self?.pollRecursively(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)
|
self?.pollRecursively(using: dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +94,7 @@ extension OpenGroupAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
public func poll(
|
public func poll(
|
||||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
return poll(
|
return poll(
|
||||||
calledFromBackgroundPoller: false,
|
calledFromBackgroundPoller: false,
|
||||||
|
@ -112,7 +107,7 @@ extension OpenGroupAPI {
|
||||||
calledFromBackgroundPoller: Bool,
|
calledFromBackgroundPoller: Bool,
|
||||||
isBackgroundPollerValid: @escaping (() -> Bool) = { true },
|
isBackgroundPollerValid: @escaping (() -> Bool) = { true },
|
||||||
isPostCapabilitiesRetry: Bool,
|
isPostCapabilitiesRetry: Bool,
|
||||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
guard !self.isPolling else {
|
guard !self.isPolling else {
|
||||||
return Just(())
|
return Just(())
|
||||||
|
@ -122,10 +117,12 @@ extension OpenGroupAPI {
|
||||||
|
|
||||||
self.isPolling = true
|
self.isPolling = true
|
||||||
let server: String = self.server
|
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 = (
|
let timeSinceLastPoll: TimeInterval = (
|
||||||
dependencies.cache.timeSinceLastPoll[server] ??
|
dependencies.caches[.openGroupManager].timeSinceLastPoll[server] ??
|
||||||
dependencies.mutableCache.mutate { $0.getTimeSinceLastOpen(using: dependencies) }
|
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||||
|
cache.getTimeSinceLastOpen(using: dependencies)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return dependencies.storage
|
return dependencies.storage
|
||||||
|
@ -170,10 +167,11 @@ extension OpenGroupAPI {
|
||||||
using: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
dependencies.mutableCache.mutate { cache in
|
|
||||||
|
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||||
cache.hasPerformedInitialPoll[server] = true
|
cache.hasPerformedInitialPoll[server] = true
|
||||||
cache.timeSinceLastPoll[server] = Date().timeIntervalSince1970
|
cache.timeSinceLastPoll[server] = dependencies.dateNow.timeIntervalSince1970
|
||||||
UserDefaults.standard[.lastOpen] = Date()
|
dependencies.standardUserDefaults[.lastOpen] = dependencies.dateNow
|
||||||
}
|
}
|
||||||
|
|
||||||
SNLog("Open group polling finished for \(server).")
|
SNLog("Open group polling finished for \(server).")
|
||||||
|
@ -303,7 +301,7 @@ extension OpenGroupAPI {
|
||||||
isBackgroundPollerValid: @escaping (() -> Bool) = { true },
|
isBackgroundPollerValid: @escaping (() -> Bool) = { true },
|
||||||
isPostCapabilitiesRetry: Bool,
|
isPostCapabilitiesRetry: Bool,
|
||||||
error: Error,
|
error: Error,
|
||||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<Bool, Error> {
|
) -> 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
|
/// 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
|
/// OpenGroup before blinding was enabled and need to update it's capabilities
|
||||||
|
@ -376,7 +374,7 @@ extension OpenGroupAPI {
|
||||||
info: ResponseInfoType,
|
info: ResponseInfoType,
|
||||||
response: BatchResponse,
|
response: BatchResponse,
|
||||||
failureCount: Int64,
|
failureCount: Int64,
|
||||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
let server: String = self.server
|
let server: String = self.server
|
||||||
let validResponses: [OpenGroupAPI.Endpoint: Decodable] = response.data
|
let validResponses: [OpenGroupAPI.Endpoint: Decodable] = response.data
|
||||||
|
@ -550,7 +548,7 @@ extension OpenGroupAPI {
|
||||||
publicKey: nil,
|
publicKey: nil,
|
||||||
for: roomToken,
|
for: roomToken,
|
||||||
on: server,
|
on: server,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _):
|
case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _):
|
||||||
|
@ -564,7 +562,7 @@ extension OpenGroupAPI {
|
||||||
messages: responseBody.compactMap { $0.value },
|
messages: responseBody.compactMap { $0.value },
|
||||||
for: roomToken,
|
for: roomToken,
|
||||||
on: server,
|
on: server,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
case .inbox, .inboxSince, .outbox, .outboxSince:
|
case .inbox, .inboxSince, .outbox, .outboxSince:
|
||||||
|
@ -587,7 +585,7 @@ extension OpenGroupAPI {
|
||||||
messages: messages,
|
messages: messages,
|
||||||
fromOutbox: fromOutbox,
|
fromOutbox: fromOutbox,
|
||||||
on: server,
|
on: server,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
default: break // No custom handling needed
|
default: break // No custom handling needed
|
||||||
|
|
|
@ -53,18 +53,18 @@ public class Poller {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the delay which should occur before the next poll
|
/// 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")
|
preconditionFailure("abstract class - override in subclass")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform and logic which should occur when the poll errors, will stop polling if `false` is returned
|
/// 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")
|
preconditionFailure("abstract class - override in subclass")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private API
|
// 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
|
// Run on the 'pollerQueue' to ensure any 'Atomic' access doesn't block the main thread
|
||||||
// on startup
|
// on startup
|
||||||
Threading.pollerQueue.async { [weak self] in
|
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
|
// 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
|
// after setUpPolling. So the poller may not work, thus misses messages
|
||||||
self?.isPolling.mutate { $0[publicKey] = true }
|
self?.isPolling.mutate { $0[publicKey] = true }
|
||||||
self?.pollRecursively(for: publicKey)
|
self?.pollRecursively(for: publicKey, using: dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func getSnodeForPolling(
|
internal func getSnodeForPolling(
|
||||||
for publicKey: String,
|
for publicKey: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) -> AnyPublisher<Snode, Error> {
|
) -> AnyPublisher<Snode, Error> {
|
||||||
// If we don't want to poll a snode multiple times then just grab a random one from the swarm
|
// If we don't want to poll a snode multiple times then just grab a random one from the swarm
|
||||||
guard maxNodePollCount > 0 else {
|
guard maxNodePollCount > 0 else {
|
||||||
|
@ -135,14 +135,14 @@ public class Poller {
|
||||||
|
|
||||||
private func pollRecursively(
|
private func pollRecursively(
|
||||||
for publicKey: String,
|
for publicKey: String,
|
||||||
using dependencies: SMKDependencies = SMKDependencies()
|
using dependencies: Dependencies
|
||||||
) {
|
) {
|
||||||
guard isPolling.wrappedValue[publicKey] == true else { return }
|
guard isPolling.wrappedValue[publicKey] == true else { return }
|
||||||
|
|
||||||
let namespaces: [SnodeAPI.Namespace] = self.namespaces
|
let namespaces: [SnodeAPI.Namespace] = self.namespaces
|
||||||
let lastPollStart: TimeInterval = Date().timeIntervalSince1970
|
let lastPollStart: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||||
let lastPollInterval: TimeInterval = nextPollDelay(for: publicKey)
|
let lastPollInterval: TimeInterval = nextPollDelay(for: publicKey, using: dependencies)
|
||||||
let getSnodePublisher: AnyPublisher<Snode, Error> = getSnodeForPolling(for: publicKey)
|
let getSnodePublisher: AnyPublisher<Snode, Error> = getSnodeForPolling(for: publicKey, using: dependencies)
|
||||||
|
|
||||||
// Store the publisher intp the cancellables dictionary
|
// Store the publisher intp the cancellables dictionary
|
||||||
cancellables.mutate { [weak self] cancellables in
|
cancellables.mutate { [weak self] cancellables in
|
||||||
|
@ -156,8 +156,8 @@ public class Poller {
|
||||||
using: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.subscribe(on: dependencies.subscribeQueue)
|
.subscribe(on: Threading.pollerQueue, using: dependencies)
|
||||||
.receive(on: dependencies.receiveQueue)
|
.receive(on: Threading.pollerQueue, using: dependencies)
|
||||||
.sink(
|
.sink(
|
||||||
receiveCompletion: { result in
|
receiveCompletion: { result in
|
||||||
switch result {
|
switch result {
|
||||||
|
@ -174,21 +174,21 @@ public class Poller {
|
||||||
self?.incrementPollCount(publicKey: publicKey)
|
self?.incrementPollCount(publicKey: publicKey)
|
||||||
|
|
||||||
// Calculate the remaining poll delay
|
// Calculate the remaining poll delay
|
||||||
let currentTime: TimeInterval = Date().timeIntervalSince1970
|
let currentTime: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||||
let nextPollInterval: TimeInterval = (
|
let nextPollInterval: TimeInterval = (
|
||||||
self?.nextPollDelay(for: publicKey) ??
|
self?.nextPollDelay(for: publicKey, using: dependencies) ??
|
||||||
lastPollInterval
|
lastPollInterval
|
||||||
)
|
)
|
||||||
let remainingInterval: TimeInterval = max(0, nextPollInterval - (currentTime - lastPollStart))
|
let remainingInterval: TimeInterval = max(0, nextPollInterval - (currentTime - lastPollStart))
|
||||||
|
|
||||||
// Schedule the next poll
|
// Schedule the next poll
|
||||||
guard remainingInterval > 0 else {
|
guard remainingInterval > 0 else {
|
||||||
return dependencies.subscribeQueue.async {
|
return Threading.pollerQueue.async(using: dependencies) {
|
||||||
self?.pollRecursively(for: publicKey, 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)
|
self?.pollRecursively(for: publicKey, using: dependencies)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -209,10 +209,7 @@ public class Poller {
|
||||||
calledFromBackgroundPoller: Bool = false,
|
calledFromBackgroundPoller: Bool = false,
|
||||||
isBackgroundPollValid: @escaping (() -> Bool) = { true },
|
isBackgroundPollValid: @escaping (() -> Bool) = { true },
|
||||||
poller: Poller? = nil,
|
poller: Poller? = nil,
|
||||||
using dependencies: SMKDependencies = SMKDependencies(
|
using dependencies: Dependencies = Dependencies()
|
||||||
subscribeQueue: Threading.pollerQueue,
|
|
||||||
receiveQueue: Threading.pollerQueue
|
|
||||||
)
|
|
||||||
) -> AnyPublisher<[Message], Error> {
|
) -> AnyPublisher<[Message], Error> {
|
||||||
// If the polling has been cancelled then don't continue
|
// If the polling has been cancelled then don't continue
|
||||||
guard
|
guard
|
||||||
|
@ -276,7 +273,7 @@ public class Poller {
|
||||||
var standardMessageJobsToRun: [Job] = []
|
var standardMessageJobsToRun: [Job] = []
|
||||||
var pollerLogOutput: String = "\(pollerName) failed to process any messages"
|
var pollerLogOutput: String = "\(pollerName) failed to process any messages"
|
||||||
|
|
||||||
Storage.shared.write { db in
|
dependencies.storage.write { db in
|
||||||
let allProcessedMessages: [ProcessedMessage] = allMessages
|
let allProcessedMessages: [ProcessedMessage] = allMessages
|
||||||
.compactMap { message -> ProcessedMessage? in
|
.compactMap { message -> ProcessedMessage? in
|
||||||
do {
|
do {
|
||||||
|
@ -338,7 +335,7 @@ public class Poller {
|
||||||
db,
|
db,
|
||||||
job: jobToRun,
|
job: jobToRun,
|
||||||
canStartJob: !calledFromBackgroundPoller,
|
canStartJob: !calledFromBackgroundPoller,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
return updatedJob?.id
|
return updatedJob?.id
|
||||||
|
@ -372,7 +369,7 @@ public class Poller {
|
||||||
db,
|
db,
|
||||||
job: jobToRun,
|
job: jobToRun,
|
||||||
canStartJob: !calledFromBackgroundPoller,
|
canStartJob: !calledFromBackgroundPoller,
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create the dependency between the jobs
|
// Create the dependency between the jobs
|
||||||
|
@ -429,10 +426,11 @@ public class Poller {
|
||||||
// Note: In the background we just want jobs to fail silently
|
// Note: In the background we just want jobs to fail silently
|
||||||
ConfigMessageReceiveJob.run(
|
ConfigMessageReceiveJob.run(
|
||||||
job,
|
job,
|
||||||
queue: dependencies.receiveQueue,
|
queue: Threading.pollerQueue,
|
||||||
success: { _, _, _ in resolver(Result.success(())) },
|
success: { _, _, _ in resolver(Result.success(())) },
|
||||||
failure: { _, _, _, _ 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
|
// Note: In the background we just want jobs to fail silently
|
||||||
MessageReceiveJob.run(
|
MessageReceiveJob.run(
|
||||||
job,
|
job,
|
||||||
queue: dependencies.receiveQueue,
|
queue: Threading.pollerQueue,
|
||||||
success: { _, _, _ in resolver(Result.success(())) },
|
success: { _, _, _ in resolver(Result.success(())) },
|
||||||
failure: { _, _, _, _ 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())
|
self.timestampMs = (timestampMs ?? SnodeAPI.currentOffsetTimestampMs())
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func start(_ db: Database) {
|
fileprivate func start(_ db: Database, using dependencies: Dependencies = Dependencies()) {
|
||||||
// Start the typing indicator
|
// Start the typing indicator
|
||||||
switch direction {
|
switch direction {
|
||||||
case .outgoing:
|
case .outgoing:
|
||||||
scheduleRefreshCallback(db, shouldSend: (refreshTimer == nil))
|
scheduleRefreshCallback(db, shouldSend: (refreshTimer == nil), using: dependencies)
|
||||||
|
|
||||||
case .incoming:
|
case .incoming:
|
||||||
try? ThreadTypingIndicator(
|
try? ThreadTypingIndicator(
|
||||||
|
@ -72,7 +72,7 @@ public class TypingIndicators {
|
||||||
refreshTimeout()
|
refreshTimeout()
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func stop(_ db: Database) {
|
fileprivate func stop(_ db: Database, using dependencies: Dependencies = Dependencies()) {
|
||||||
self.refreshTimer?.invalidate()
|
self.refreshTimer?.invalidate()
|
||||||
self.refreshTimer = nil
|
self.refreshTimer = nil
|
||||||
self.stopTimer?.invalidate()
|
self.stopTimer?.invalidate()
|
||||||
|
@ -85,7 +85,8 @@ public class TypingIndicators {
|
||||||
message: TypingIndicator(kind: .stopped),
|
message: TypingIndicator(kind: .stopped),
|
||||||
interactionId: nil,
|
interactionId: nil,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: threadVariant
|
threadVariant: threadVariant,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
case .incoming:
|
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 {
|
if shouldSend {
|
||||||
try? MessageSender.send(
|
try? MessageSender.send(
|
||||||
db,
|
db,
|
||||||
message: TypingIndicator(kind: .started),
|
message: TypingIndicator(kind: .started),
|
||||||
interactionId: nil,
|
interactionId: nil,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: threadVariant
|
threadVariant: threadVariant,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,8 +133,8 @@ public class TypingIndicators {
|
||||||
withTimeInterval: 10,
|
withTimeInterval: 10,
|
||||||
repeats: false
|
repeats: false
|
||||||
) { [weak self] _ in
|
) { [weak self] _ in
|
||||||
Storage.shared.writeAsync { db in
|
dependencies.storage.writeAsync { db in
|
||||||
self?.scheduleRefreshCallback(db)
|
self?.scheduleRefreshCallback(db, using: dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,8 @@ internal extension SessionUtil {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
in conf: UnsafeMutablePointer<config_object>?,
|
in conf: UnsafeMutablePointer<config_object>?,
|
||||||
mergeNeedsDump: Bool,
|
mergeNeedsDump: Bool,
|
||||||
latestConfigSentTimestampMs: Int64
|
latestConfigSentTimestampMs: Int64,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws {
|
) throws {
|
||||||
guard mergeNeedsDump else { return }
|
guard mergeNeedsDump else { return }
|
||||||
guard conf != nil else { throw SessionUtilError.nilConfigObject }
|
guard conf != nil else { throw SessionUtilError.nilConfigObject }
|
||||||
|
@ -236,7 +237,8 @@ internal extension SessionUtil {
|
||||||
admins: updatedAdmins.map { $0.profileId },
|
admins: updatedAdmins.map { $0.profileId },
|
||||||
expirationTimer: UInt32(group.disappearingConfig?.durationSeconds ?? 0),
|
expirationTimer: UInt32(group.disappearingConfig?.durationSeconds ?? 0),
|
||||||
formationTimestampMs: UInt64((group.joinedAt.map { $0 * 1000 } ?? latestConfigSentTimestampMs)),
|
formationTimestampMs: UInt64((group.joinedAt.map { $0 * 1000 } ?? latestConfigSentTimestampMs)),
|
||||||
calledFromConfigHandling: true
|
calledFromConfigHandling: true,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -18,7 +18,8 @@ internal extension SessionUtil {
|
||||||
_ db: Database,
|
_ db: Database,
|
||||||
in conf: UnsafeMutablePointer<config_object>?,
|
in conf: UnsafeMutablePointer<config_object>?,
|
||||||
mergeNeedsDump: Bool,
|
mergeNeedsDump: Bool,
|
||||||
latestConfigSentTimestampMs: Int64
|
latestConfigSentTimestampMs: Int64,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) throws {
|
) throws {
|
||||||
typealias ProfileData = (profileName: String, profilePictureUrl: String?, profilePictureKey: Data?)
|
typealias ProfileData = (profileName: String, profilePictureUrl: String?, profilePictureKey: Data?)
|
||||||
|
|
||||||
|
@ -51,7 +52,8 @@ internal extension SessionUtil {
|
||||||
)
|
)
|
||||||
}(),
|
}(),
|
||||||
sentTimestamp: (TimeInterval(latestConfigSentTimestampMs) / 1000),
|
sentTimestamp: (TimeInterval(latestConfigSentTimestampMs) / 1000),
|
||||||
calledFromConfigHandling: true
|
calledFromConfigHandling: true,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
// Update the 'Note to Self' visibility and priority
|
// 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,
|
profileName: String,
|
||||||
avatarUpdate: AvatarUpdate = .none,
|
avatarUpdate: AvatarUpdate = .none,
|
||||||
success: ((Database) throws -> ())? = nil,
|
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 = {
|
let isRemovingAvatar: Bool = {
|
||||||
switch avatarUpdate {
|
switch avatarUpdate {
|
||||||
case .remove: return true
|
case .remove: return true
|
||||||
|
@ -298,7 +299,7 @@ public struct ProfileManager {
|
||||||
|
|
||||||
switch avatarUpdate {
|
switch avatarUpdate {
|
||||||
case .none, .remove, .updateTo:
|
case .none, .remove, .updateTo:
|
||||||
Storage.shared.writeAsync { db in
|
dependencies.storage.writeAsync { db in
|
||||||
if isRemovingAvatar {
|
if isRemovingAvatar {
|
||||||
let existingProfileUrl: String? = try Profile
|
let existingProfileUrl: String? = try Profile
|
||||||
.filter(id: userPublicKey)
|
.filter(id: userPublicKey)
|
||||||
|
@ -327,7 +328,8 @@ public struct ProfileManager {
|
||||||
publicKey: userPublicKey,
|
publicKey: userPublicKey,
|
||||||
name: profileName,
|
name: profileName,
|
||||||
avatarUpdate: avatarUpdate,
|
avatarUpdate: avatarUpdate,
|
||||||
sentTimestamp: Date().timeIntervalSince1970
|
sentTimestamp: dependencies.dateNow.timeIntervalSince1970,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
SNLog("Successfully updated service with profile.")
|
SNLog("Successfully updated service with profile.")
|
||||||
|
@ -345,7 +347,8 @@ public struct ProfileManager {
|
||||||
publicKey: userPublicKey,
|
publicKey: userPublicKey,
|
||||||
name: profileName,
|
name: profileName,
|
||||||
avatarUpdate: .updateTo(url: downloadUrl, key: newProfileKey, fileName: fileName),
|
avatarUpdate: .updateTo(url: downloadUrl, key: newProfileKey, fileName: fileName),
|
||||||
sentTimestamp: Date().timeIntervalSince1970
|
sentTimestamp: dependencies.dateNow.timeIntervalSince1970,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
SNLog("Successfully updated service with profile.")
|
SNLog("Successfully updated service with profile.")
|
||||||
|
@ -498,9 +501,9 @@ public struct ProfileManager {
|
||||||
avatarUpdate: AvatarUpdate,
|
avatarUpdate: AvatarUpdate,
|
||||||
sentTimestamp: TimeInterval,
|
sentTimestamp: TimeInterval,
|
||||||
calledFromConfigHandling: Bool = false,
|
calledFromConfigHandling: Bool = false,
|
||||||
dependencies: Dependencies = Dependencies()
|
using dependencies: Dependencies
|
||||||
) throws {
|
) throws {
|
||||||
let isCurrentUser = (publicKey == getUserHexEncodedPublicKey(db, dependencies: dependencies))
|
let isCurrentUser = (publicKey == getUserHexEncodedPublicKey(db, using: dependencies))
|
||||||
let profile: Profile = Profile.fetchOrCreate(db, id: publicKey)
|
let profile: Profile = Profile.fetchOrCreate(db, id: publicKey)
|
||||||
var profileChanges: [ConfigColumnAssignment] = []
|
var profileChanges: [ConfigColumnAssignment] = []
|
||||||
|
|
||||||
|
@ -604,7 +607,7 @@ public struct ProfileManager {
|
||||||
let targetProfile: Profile = Profile.fetchOrCreate(db, id: publicKey)
|
let targetProfile: Profile = Profile.fetchOrCreate(db, id: publicKey)
|
||||||
|
|
||||||
// FIXME: Refactor avatar downloading to be a proper Job so we can avoid this
|
// FIXME: Refactor avatar downloading to be a proper Job so we can avoid this
|
||||||
JobRunner.afterBlockingQueue {
|
dependencies.jobRunner.afterBlockingQueue {
|
||||||
ProfileManager.downloadAvatar(for: targetProfile)
|
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() {
|
override func spec() {
|
||||||
var job: Job!
|
var job: Job!
|
||||||
var interaction: Interaction!
|
var interaction: Interaction!
|
||||||
var attachment1: Attachment!
|
var attachment: Attachment!
|
||||||
var interactionAttachment1: InteractionAttachment!
|
var interactionAttachment: InteractionAttachment!
|
||||||
var mockStorage: Storage!
|
var mockStorage: Storage!
|
||||||
var mockJobRunner: MockJobRunner!
|
var mockJobRunner: MockJobRunner!
|
||||||
var dependencies: Dependencies!
|
var dependencies: Dependencies!
|
||||||
|
@ -24,8 +24,10 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
// MARK: - JobRunner
|
// MARK: - JobRunner
|
||||||
|
|
||||||
describe("a MessageSendJob") {
|
describe("a MessageSendJob") {
|
||||||
|
// MARK: - Configuration
|
||||||
|
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage = Storage(
|
mockStorage = SynchronousStorage(
|
||||||
customWriter: try! DatabaseQueue(),
|
customWriter: try! DatabaseQueue(),
|
||||||
customMigrationTargets: [
|
customMigrationTargets: [
|
||||||
SNUtilitiesKit.self,
|
SNUtilitiesKit.self,
|
||||||
|
@ -36,9 +38,9 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
dependencies = Dependencies(
|
dependencies = Dependencies(
|
||||||
storage: mockStorage,
|
storage: mockStorage,
|
||||||
jobRunner: mockJobRunner,
|
jobRunner: mockJobRunner,
|
||||||
date: Date(timeIntervalSince1970: 1234567890)
|
dateNow: Date(timeIntervalSince1970: 1234567890)
|
||||||
)
|
)
|
||||||
attachment1 = Attachment(
|
attachment = Attachment(
|
||||||
id: "200",
|
id: "200",
|
||||||
variant: .standard,
|
variant: .standard,
|
||||||
state: .failedDownload,
|
state: .failedDownload,
|
||||||
|
@ -60,7 +62,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
.thenReturn([:])
|
.thenReturn([:])
|
||||||
mockJobRunner
|
mockJobRunner
|
||||||
.when { $0.insert(any(), job: any(), before: any(), dependencies: dependencies) }
|
.when { $0.insert(any(), job: any(), before: any()) }
|
||||||
.then { args in
|
.then { args in
|
||||||
let db: Database = args[0] as! Database
|
let db: Database = args[0] as! Database
|
||||||
var job: Job = args[1] as! Job
|
var job: Job = args[1] as! Job
|
||||||
|
@ -77,6 +79,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
dependencies = nil
|
dependencies = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - fails when not given any details
|
||||||
it("fails when not given any details") {
|
it("fails when not given any details") {
|
||||||
job = Job(variant: .messageSend)
|
job = Job(variant: .messageSend)
|
||||||
|
|
||||||
|
@ -92,14 +95,15 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
permanentFailure = runPermanentFailure
|
permanentFailure = runPermanentFailure
|
||||||
},
|
},
|
||||||
deferred: { _, _ in },
|
deferred: { _, _ in },
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
||||||
expect(permanentFailure).to(beTrue())
|
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(
|
job = Job(
|
||||||
variant: .messageSend,
|
variant: .messageSend,
|
||||||
details: MessageReceiveJob.Details(messages: [], calledFromBackgroundPoller: false)
|
details: MessageReceiveJob.Details(messages: [], calledFromBackgroundPoller: false)
|
||||||
|
@ -117,13 +121,14 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
permanentFailure = runPermanentFailure
|
permanentFailure = runPermanentFailure
|
||||||
},
|
},
|
||||||
deferred: { _, _ in },
|
deferred: { _, _ in },
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
||||||
expect(permanentFailure).to(beTrue())
|
expect(permanentFailure).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - of VisibleMessage
|
||||||
context("of VisibleMessage") {
|
context("of VisibleMessage") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
interaction = Interaction(
|
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") {
|
it("fails when there is no job id") {
|
||||||
job = Job(
|
job = Job(
|
||||||
variant: .messageSend,
|
variant: .messageSend,
|
||||||
|
@ -186,13 +192,14 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
permanentFailure = runPermanentFailure
|
permanentFailure = runPermanentFailure
|
||||||
},
|
},
|
||||||
deferred: { _, _ in },
|
deferred: { _, _ in },
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
||||||
expect(permanentFailure).to(beTrue())
|
expect(permanentFailure).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- fails when there is no interaction id
|
||||||
it("fails when there is no interaction id") {
|
it("fails when there is no interaction id") {
|
||||||
job = Job(
|
job = Job(
|
||||||
variant: .messageSend,
|
variant: .messageSend,
|
||||||
|
@ -216,13 +223,14 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
permanentFailure = runPermanentFailure
|
permanentFailure = runPermanentFailure
|
||||||
},
|
},
|
||||||
deferred: { _, _ in },
|
deferred: { _, _ in },
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
||||||
expect(permanentFailure).to(beTrue())
|
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") {
|
it("fails when there is no interaction for the provided interaction id") {
|
||||||
job = Job(
|
job = Job(
|
||||||
variant: .messageSend,
|
variant: .messageSend,
|
||||||
|
@ -248,29 +256,32 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
permanentFailure = runPermanentFailure
|
permanentFailure = runPermanentFailure
|
||||||
},
|
},
|
||||||
deferred: { _, _ in },
|
deferred: { _, _ in },
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(error).to(matchError(StorageError.objectNotFound))
|
expect(error).to(matchError(StorageError.objectNotFound))
|
||||||
expect(permanentFailure).to(beTrue())
|
expect(permanentFailure).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- with an attachment
|
||||||
context("with an attachment") {
|
context("with an attachment") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
interactionAttachment1 = InteractionAttachment(
|
interactionAttachment = InteractionAttachment(
|
||||||
albumIndex: 0,
|
albumIndex: 0,
|
||||||
interactionId: interaction.id!,
|
interactionId: interaction.id!,
|
||||||
attachmentId: attachment1.id
|
attachmentId: attachment.id
|
||||||
)
|
)
|
||||||
|
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try attachment1.insert(db)
|
try attachment.insert(db)
|
||||||
try interactionAttachment1.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") {
|
it("it fails when trying to send with an attachment which previously failed to download") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try attachment1.with(state: .failedDownload).save(db)
|
try attachment.with(state: .failedDownload).save(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
var error: Error? = nil
|
var error: Error? = nil
|
||||||
|
@ -285,54 +296,27 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
permanentFailure = runPermanentFailure
|
permanentFailure = runPermanentFailure
|
||||||
},
|
},
|
||||||
deferred: { _, _ in },
|
deferred: { _, _ in },
|
||||||
dependencies: dependencies
|
using: 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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(error).to(matchError(AttachmentError.notUploaded))
|
expect(error).to(matchError(AttachmentError.notUploaded))
|
||||||
expect(permanentFailure).to(beTrue())
|
expect(permanentFailure).to(beTrue())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ---- with a pending upload
|
||||||
context("with a pending upload") {
|
context("with a pending upload") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage.write { db in
|
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") {
|
it("it defers when trying to send with an attachment which is still pending upload") {
|
||||||
var didDefer: Bool = false
|
var didDefer: Bool = false
|
||||||
|
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try attachment1.with(state: .uploading).save(db)
|
try attachment.with(state: .uploading).save(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageSendJob.run(
|
MessageSendJob.run(
|
||||||
|
@ -341,12 +325,38 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
success: { _, _, _ in },
|
success: { _, _, _ in },
|
||||||
failure: { _, _, _, _ in },
|
failure: { _, _, _, _ in },
|
||||||
deferred: { _, _ in didDefer = true },
|
deferred: { _, _ in didDefer = true },
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(didDefer).to(beTrue())
|
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") {
|
it("inserts an attachment upload job before the message send job") {
|
||||||
mockJobRunner
|
mockJobRunner
|
||||||
.when {
|
.when {
|
||||||
|
@ -356,17 +366,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
variant: .attachmentUpload
|
variant: .attachmentUpload
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn([
|
.thenReturn([:])
|
||||||
2: JobRunner.JobInfo(
|
|
||||||
variant: .attachmentUpload,
|
|
||||||
threadId: nil,
|
|
||||||
interactionId: 100,
|
|
||||||
detailsData: try! JSONEncoder().encode(AttachmentUploadJob.Details(
|
|
||||||
messageSendJobId: 1,
|
|
||||||
attachmentId: "200"
|
|
||||||
))
|
|
||||||
)
|
|
||||||
])
|
|
||||||
|
|
||||||
MessageSendJob.run(
|
MessageSendJob.run(
|
||||||
job,
|
job,
|
||||||
|
@ -374,7 +374,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
success: { _, _, _ in },
|
success: { _, _, _ in },
|
||||||
failure: { _, _, _, _ in },
|
failure: { _, _, _, _ in },
|
||||||
deferred: { _, _ in },
|
deferred: { _, _ in },
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(mockJobRunner)
|
expect(mockJobRunner)
|
||||||
|
@ -392,12 +392,12 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
attachmentId: "200"
|
attachmentId: "200"
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
before: job,
|
before: job
|
||||||
dependencies: dependencies
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: ------ creates a dependency between the new job and the existing one
|
||||||
it("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(
|
MessageSendJob.run(
|
||||||
job,
|
job,
|
||||||
|
@ -405,7 +405,7 @@ class MessageSendJobSpec: QuickSpec {
|
||||||
success: { _, _, _ in },
|
success: { _, _, _ in },
|
||||||
failure: { _, _, _, _ in },
|
failure: { _, _, _, _ in },
|
||||||
deferred: { _, _ in },
|
deferred: { _, _ in },
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(mockStorage.read { db in try JobDependencies.fetchOne(db) })
|
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)
|
let requestData: Data? = try? JSONEncoder().encode(request)
|
||||||
.to(equal("[{\"path\":\"\\/batch\",\"method\":\"GET\",\"b64\":\"testBody\"}]"))
|
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") {
|
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)
|
let requestData: Data? = try? JSONEncoder().encode(request)
|
||||||
.to(equal("[{\"path\":\"\\/batch\",\"method\":\"GET\",\"bytes\":[1,2,3]}]"))
|
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") {
|
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)
|
let requestData: Data? = try? JSONEncoder().encode(request)
|
||||||
.to(equal("[{\"path\":\"\\/batch\",\"method\":\"GET\",\"json\":{\"stringValue\":\"testValue\"}}]"))
|
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") {
|
it("strips authentication headers") {
|
||||||
|
|
|
@ -16,9 +16,8 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
var messageJson: String!
|
var messageJson: String!
|
||||||
var messageData: Data!
|
var messageData: Data!
|
||||||
var decoder: JSONDecoder!
|
var decoder: JSONDecoder!
|
||||||
var mockSign: MockSign!
|
var mockCrypto: MockCrypto!
|
||||||
var mockEd25519: MockEd25519!
|
var dependencies: Dependencies!
|
||||||
var dependencies: SMKDependencies!
|
|
||||||
|
|
||||||
beforeEach {
|
beforeEach {
|
||||||
messageJson = """
|
messageJson = """
|
||||||
|
@ -35,18 +34,16 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
messageData = messageJson.data(using: .utf8)!
|
messageData = messageJson.data(using: .utf8)!
|
||||||
mockSign = MockSign()
|
mockCrypto = MockCrypto()
|
||||||
mockEd25519 = MockEd25519()
|
dependencies = Dependencies(
|
||||||
dependencies = SMKDependencies(
|
crypto: mockCrypto
|
||||||
sign: mockSign,
|
|
||||||
ed25519: mockEd25519
|
|
||||||
)
|
)
|
||||||
decoder = JSONDecoder()
|
decoder = JSONDecoder()
|
||||||
decoder.userInfo = [ Dependencies.userInfoKey: dependencies as Any ]
|
decoder.userInfo = [ Dependencies.userInfoKey: dependencies as Any ]
|
||||||
}
|
}
|
||||||
|
|
||||||
afterEach {
|
afterEach {
|
||||||
mockSign = nil
|
mockCrypto = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
context("when decoding") {
|
context("when decoding") {
|
||||||
|
@ -204,8 +201,10 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("succeeds if it succeeds verification") {
|
it("succeeds if it succeeds verification") {
|
||||||
mockSign
|
mockCrypto
|
||||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
.when {
|
||||||
|
$0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray()))
|
||||||
|
}
|
||||||
.thenReturn(true)
|
.thenReturn(true)
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
|
@ -215,25 +214,31 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("provides the correct values as parameters") {
|
it("provides the correct values as parameters") {
|
||||||
mockSign
|
mockCrypto
|
||||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
.when {
|
||||||
|
$0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray()))
|
||||||
|
}
|
||||||
.thenReturn(true)
|
.thenReturn(true)
|
||||||
|
|
||||||
_ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
_ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
||||||
|
|
||||||
expect(mockSign)
|
expect(mockCrypto)
|
||||||
.to(call(matchingParameters: true) {
|
.to(call(matchingParameters: true) {
|
||||||
$0.verify(
|
$0.verify(
|
||||||
message: Data(base64Encoded: "VGVzdERhdGE=")!.bytes,
|
.signature(
|
||||||
publicKey: Data(hex: TestConstants.publicKey).bytes,
|
message: Data(base64Encoded: "VGVzdERhdGE=")!.bytes,
|
||||||
signature: Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!.bytes
|
publicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||||
|
signature: Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!.bytes
|
||||||
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
it("throws if it fails verification") {
|
it("throws if it fails verification") {
|
||||||
mockSign
|
mockCrypto
|
||||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
.when {
|
||||||
|
$0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray()))
|
||||||
|
}
|
||||||
.thenReturn(false)
|
.thenReturn(false)
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
|
@ -245,7 +250,9 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
|
|
||||||
context("that is unblinded") {
|
context("that is unblinded") {
|
||||||
it("succeeds if it succeeds verification") {
|
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 {
|
expect {
|
||||||
try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
||||||
|
@ -254,22 +261,28 @@ class SOGSMessageSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("provides the correct values as parameters") {
|
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)
|
_ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
||||||
|
|
||||||
expect(mockEd25519)
|
expect(mockCrypto)
|
||||||
.to(call(matchingParameters: true) {
|
.to(call(matchingParameters: true) {
|
||||||
try $0.verifySignature(
|
$0.verify(
|
||||||
Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!,
|
.signatureEd25519(
|
||||||
publicKey: Data(hex: TestConstants.publicKey),
|
Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!,
|
||||||
data: Data(base64Encoded: "VGVzdERhdGE=")!
|
publicKey: Data(hex: TestConstants.publicKey),
|
||||||
|
data: Data(base64Encoded: "VGVzdERhdGE=")!
|
||||||
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
it("throws if it fails verification") {
|
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 {
|
expect {
|
||||||
try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
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() {
|
override func spec() {
|
||||||
var mockStorage: Storage!
|
var mockStorage: Storage!
|
||||||
var mockSodium: MockSodium!
|
var mockCrypto: MockCrypto!
|
||||||
var mockBox: MockBox!
|
var dependencies: Dependencies!
|
||||||
var mockGenericHash: MockGenericHash!
|
|
||||||
var mockSign: MockSign!
|
|
||||||
var mockAeadXChaCha: MockAeadXChaCha20Poly1305Ietf!
|
|
||||||
var mockNonce24Generator: MockNonce24Generator!
|
|
||||||
var dependencies: SMKDependencies!
|
|
||||||
|
|
||||||
describe("a MessageReceiver") {
|
describe("a MessageReceiver") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage = Storage(
|
mockStorage = SynchronousStorage(
|
||||||
customWriter: try! DatabaseQueue(),
|
customWriter: try! DatabaseQueue(),
|
||||||
customMigrationTargets: [
|
customMigrationTargets: [
|
||||||
SNUtilitiesKit.self,
|
SNUtilitiesKit.self,
|
||||||
SNMessagingKit.self
|
SNMessagingKit.self
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
mockSodium = MockSodium()
|
mockCrypto = MockCrypto()
|
||||||
mockBox = MockBox()
|
dependencies = Dependencies(
|
||||||
mockGenericHash = MockGenericHash()
|
|
||||||
mockSign = MockSign()
|
|
||||||
mockAeadXChaCha = MockAeadXChaCha20Poly1305Ietf()
|
|
||||||
mockNonce24Generator = MockNonce24Generator()
|
|
||||||
|
|
||||||
mockAeadXChaCha
|
|
||||||
.when { $0.encrypt(message: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
|
||||||
.thenReturn(nil)
|
|
||||||
|
|
||||||
dependencies = SMKDependencies(
|
|
||||||
storage: mockStorage,
|
storage: mockStorage,
|
||||||
sodium: mockSodium,
|
crypto: mockCrypto
|
||||||
box: mockBox,
|
|
||||||
genericHash: mockGenericHash,
|
|
||||||
sign: mockSign,
|
|
||||||
aeadXChaCha20Poly1305Ietf: mockAeadXChaCha,
|
|
||||||
nonceGenerator24: mockNonce24Generator
|
|
||||||
)
|
)
|
||||||
|
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
||||||
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).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 {
|
.when {
|
||||||
$0.open(
|
try $0.perform(
|
||||||
anonymousCipherText: anyArray(),
|
.open(
|
||||||
recipientPublicKey: anyArray(),
|
anonymousCipherText: anyArray(),
|
||||||
recipientSecretKey: anyArray()
|
recipientPublicKey: anyArray(),
|
||||||
|
recipientSecretKey: anyArray()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn([UInt8](repeating: 0, count: 100))
|
.thenReturn([UInt8](repeating: 0, count: 100))
|
||||||
mockSodium
|
mockCrypto
|
||||||
.when { $0.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), genericHash: mockGenericHash) }
|
.when { [dependencies = dependencies!] crypto in
|
||||||
|
crypto.generate(
|
||||||
|
.blindedKeyPair(
|
||||||
|
serverPublicKey: any(),
|
||||||
|
edKeyPair: any(),
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
.thenReturn(
|
.thenReturn(
|
||||||
KeyPair(
|
KeyPair(
|
||||||
publicKey: Data(hex: TestConstants.blindedPublicKey).bytes,
|
publicKey: Data(hex: TestConstants.blindedPublicKey).bytes,
|
||||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mockSodium
|
mockCrypto
|
||||||
.when {
|
.when { [dependencies = dependencies!] crypto in
|
||||||
$0.sharedBlindedEncryptionKey(
|
try crypto.perform(
|
||||||
secretKey: anyArray(),
|
.sharedBlindedEncryptionKey(
|
||||||
otherBlindedPublicKey: anyArray(),
|
secretKey: anyArray(),
|
||||||
fromBlindedPublicKey: anyArray(),
|
otherBlindedPublicKey: anyArray(),
|
||||||
toBlindedPublicKey: anyArray(),
|
fromBlindedPublicKey: anyArray(),
|
||||||
genericHash: mockGenericHash
|
toBlindedPublicKey: anyArray(),
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn([])
|
.thenReturn([])
|
||||||
mockSodium
|
mockCrypto
|
||||||
.when { $0.generateBlindingFactor(serverPublicKey: any(), genericHash: mockGenericHash) }
|
.when { [dependencies = dependencies!] crypto in
|
||||||
|
try crypto.perform(.generateBlindingFactor(serverPublicKey: any(), using: dependencies))
|
||||||
|
}
|
||||||
.thenReturn([])
|
.thenReturn([])
|
||||||
mockSodium
|
mockCrypto
|
||||||
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||||
.thenReturn(Data(hex: TestConstants.blindedPublicKey).bytes)
|
.thenReturn(Data(hex: TestConstants.blindedPublicKey).bytes)
|
||||||
mockSign
|
mockCrypto
|
||||||
.when { $0.toX25519(ed25519PublicKey: anyArray()) }
|
.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }
|
||||||
.thenReturn(Data(hex: TestConstants.publicKey).bytes)
|
.thenReturn(Data(hex: TestConstants.publicKey).bytes)
|
||||||
mockSign
|
mockCrypto
|
||||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
.when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) }
|
||||||
.thenReturn(true)
|
.thenReturn(true)
|
||||||
mockAeadXChaCha
|
mockCrypto
|
||||||
.when { $0.decrypt(authenticatedCipherText: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
.when {
|
||||||
|
try $0.perform(
|
||||||
|
.decryptAeadXChaCha20(
|
||||||
|
authenticatedCipherText: anyArray(),
|
||||||
|
secretKey: anyArray(),
|
||||||
|
nonce: anyArray()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
.thenReturn("TestMessage".data(using: .utf8)!.bytes + [UInt8](repeating: 0, count: 32))
|
.thenReturn("TestMessage".data(using: .utf8)!.bytes + [UInt8](repeating: 0, count: 32))
|
||||||
mockNonce24Generator
|
mockCrypto.when { $0.size(.nonce24) }.thenReturn(24)
|
||||||
.when { $0.nonce() }
|
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)
|
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +134,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||||
),
|
),
|
||||||
dependencies: SMKDependencies()
|
using: Dependencies()
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(String(data: (result?.plaintext ?? Data()), encoding: .utf8)).to(equal("TestMessage"))
|
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") {
|
it("throws an error if it cannot open the message") {
|
||||||
mockBox
|
mockCrypto
|
||||||
.when {
|
.when {
|
||||||
$0.open(
|
try $0.perform(
|
||||||
anonymousCipherText: anyArray(),
|
.open(
|
||||||
recipientPublicKey: anyArray(),
|
anonymousCipherText: anyArray(),
|
||||||
recipientSecretKey: anyArray()
|
recipientPublicKey: anyArray(),
|
||||||
|
recipientSecretKey: anyArray()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn(nil)
|
.thenReturn(nil)
|
||||||
|
@ -143,19 +162,21 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||||
),
|
),
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||||
}
|
}
|
||||||
|
|
||||||
it("throws an error if the open message is too short") {
|
it("throws an error if the open message is too short") {
|
||||||
mockBox
|
mockCrypto
|
||||||
.when {
|
.when {
|
||||||
$0.open(
|
try $0.perform(
|
||||||
anonymousCipherText: anyArray(),
|
.open(
|
||||||
recipientPublicKey: anyArray(),
|
anonymousCipherText: anyArray(),
|
||||||
recipientSecretKey: anyArray()
|
recipientPublicKey: anyArray(),
|
||||||
|
recipientSecretKey: anyArray()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn([1, 2, 3])
|
.thenReturn([1, 2, 3])
|
||||||
|
@ -167,15 +188,15 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||||
),
|
),
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||||
}
|
}
|
||||||
|
|
||||||
it("throws an error if it cannot verify the message") {
|
it("throws an error if it cannot verify the message") {
|
||||||
mockSign
|
mockCrypto
|
||||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
.when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) }
|
||||||
.thenReturn(false)
|
.thenReturn(false)
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
|
@ -185,14 +206,14 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||||
),
|
),
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.to(throwError(MessageReceiverError.invalidSignature))
|
.to(throwError(MessageReceiverError.invalidSignature))
|
||||||
}
|
}
|
||||||
|
|
||||||
it("throws an error if it cannot get the senders x25519 public key") {
|
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 {
|
expect {
|
||||||
try MessageReceiver.decryptWithSessionProtocol(
|
try MessageReceiver.decryptWithSessionProtocol(
|
||||||
|
@ -201,7 +222,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||||
),
|
),
|
||||||
dependencies: dependencies
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||||
|
@ -223,7 +244,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
|
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
|
||||||
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
||||||
),
|
),
|
||||||
using: SMKDependencies()
|
using: Dependencies()
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(String(data: (result?.plaintext ?? Data()), encoding: .utf8)).to(equal("TestMessage"))
|
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") {
|
it("throws an error if it cannot get the blinded keyPair") {
|
||||||
mockSodium
|
mockCrypto
|
||||||
.when { $0.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), genericHash: mockGenericHash) }
|
.when { [dependencies = dependencies!] crypto in
|
||||||
|
crypto.generate(
|
||||||
|
.blindedKeyPair(
|
||||||
|
serverPublicKey: any(),
|
||||||
|
edKeyPair: any(),
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
.thenReturn(nil)
|
.thenReturn(nil)
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
|
@ -296,14 +325,16 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("throws an error if it cannot get the decryption key") {
|
it("throws an error if it cannot get the decryption key") {
|
||||||
mockSodium
|
mockCrypto
|
||||||
.when {
|
.when { [dependencies = dependencies!] crypto in
|
||||||
$0.sharedBlindedEncryptionKey(
|
try crypto.perform(
|
||||||
secretKey: anyArray(),
|
.sharedBlindedEncryptionKey(
|
||||||
otherBlindedPublicKey: anyArray(),
|
secretKey: anyArray(),
|
||||||
fromBlindedPublicKey: anyArray(),
|
otherBlindedPublicKey: anyArray(),
|
||||||
toBlindedPublicKey: anyArray(),
|
fromBlindedPublicKey: anyArray(),
|
||||||
genericHash: mockGenericHash
|
toBlindedPublicKey: anyArray(),
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn(nil)
|
.thenReturn(nil)
|
||||||
|
@ -350,8 +381,16 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("throws an error if it cannot decrypt the data") {
|
it("throws an error if it cannot decrypt the data") {
|
||||||
mockAeadXChaCha
|
mockCrypto
|
||||||
.when { $0.decrypt(authenticatedCipherText: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
.when {
|
||||||
|
try $0.perform(
|
||||||
|
.decryptAeadXChaCha20(
|
||||||
|
authenticatedCipherText: anyArray(),
|
||||||
|
secretKey: anyArray(),
|
||||||
|
nonce: anyArray()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
.thenReturn(nil)
|
.thenReturn(nil)
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
|
@ -375,8 +414,16 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("throws an error if the inner bytes are too short") {
|
it("throws an error if the inner bytes are too short") {
|
||||||
mockAeadXChaCha
|
mockCrypto
|
||||||
.when { $0.decrypt(authenticatedCipherText: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
.when {
|
||||||
|
try $0.perform(
|
||||||
|
.decryptAeadXChaCha20(
|
||||||
|
authenticatedCipherText: anyArray(),
|
||||||
|
secretKey: anyArray(),
|
||||||
|
nonce: anyArray()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
.thenReturn([1, 2, 3])
|
.thenReturn([1, 2, 3])
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
|
@ -400,8 +447,10 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("throws an error if it cannot generate the blinding factor") {
|
it("throws an error if it cannot generate the blinding factor") {
|
||||||
mockSodium
|
mockCrypto
|
||||||
.when { $0.generateBlindingFactor(serverPublicKey: any(), genericHash: mockGenericHash) }
|
.when { [dependencies = dependencies!] crypto in
|
||||||
|
try crypto.perform(.generateBlindingFactor(serverPublicKey: any(), using: dependencies))
|
||||||
|
}
|
||||||
.thenReturn(nil)
|
.thenReturn(nil)
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
|
@ -425,8 +474,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("throws an error if it cannot generate the combined key") {
|
it("throws an error if it cannot generate the combined key") {
|
||||||
mockSodium
|
mockCrypto
|
||||||
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||||
.thenReturn(nil)
|
.thenReturn(nil)
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
|
@ -450,8 +499,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("throws an error if the combined key does not match kA") {
|
it("throws an error if the combined key does not match kA") {
|
||||||
mockSodium
|
mockCrypto
|
||||||
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||||
.thenReturn(Data(hex: TestConstants.publicKey).bytes)
|
.thenReturn(Data(hex: TestConstants.publicKey).bytes)
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
|
@ -475,8 +524,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
it("throws an error if it cannot get the senders x25519 public key") {
|
it("throws an error if it cannot get the senders x25519 public key") {
|
||||||
mockSign
|
mockCrypto
|
||||||
.when { $0.toX25519(ed25519PublicKey: anyArray()) }
|
.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }
|
||||||
.thenReturn(nil)
|
.thenReturn(nil)
|
||||||
|
|
||||||
expect {
|
expect {
|
||||||
|
|
|
@ -15,53 +15,55 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
|
|
||||||
override func spec() {
|
override func spec() {
|
||||||
var mockStorage: Storage!
|
var mockStorage: Storage!
|
||||||
var mockBox: MockBox!
|
var mockCrypto: MockCrypto!
|
||||||
var mockSign: MockSign!
|
var dependencies: Dependencies!
|
||||||
var mockNonce24Generator: MockNonce24Generator!
|
|
||||||
var dependencies: SMKDependencies!
|
|
||||||
|
|
||||||
describe("a MessageSender") {
|
describe("a MessageSender") {
|
||||||
|
// MARK: - Configuration
|
||||||
|
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockStorage = Storage(
|
mockStorage = SynchronousStorage(
|
||||||
customWriter: try! DatabaseQueue(),
|
customWriter: try! DatabaseQueue(),
|
||||||
customMigrationTargets: [
|
customMigrationTargets: [
|
||||||
SNUtilitiesKit.self,
|
SNUtilitiesKit.self,
|
||||||
SNMessagingKit.self
|
SNMessagingKit.self
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
mockBox = MockBox()
|
mockCrypto = MockCrypto()
|
||||||
mockSign = MockSign()
|
|
||||||
mockNonce24Generator = MockNonce24Generator()
|
|
||||||
|
|
||||||
dependencies = SMKDependencies(
|
dependencies = Dependencies(
|
||||||
storage: mockStorage,
|
storage: mockStorage,
|
||||||
box: mockBox,
|
crypto: mockCrypto
|
||||||
sign: mockSign,
|
|
||||||
nonceGenerator24: mockNonce24Generator
|
|
||||||
)
|
)
|
||||||
|
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
||||||
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
|
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
|
||||||
}
|
}
|
||||||
mockNonce24Generator
|
mockCrypto
|
||||||
.when { $0.nonce() }
|
.when { try $0.perform(.generateNonce24()) }
|
||||||
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - when encrypting with the session protocol
|
||||||
context("when encrypting with the session protocol") {
|
context("when encrypting with the session protocol") {
|
||||||
beforeEach {
|
beforeEach {
|
||||||
mockBox.when { $0.seal(message: anyArray(), recipientPublicKey: anyArray()) }.thenReturn([1, 2, 3])
|
mockCrypto
|
||||||
mockSign.when { $0.signature(message: anyArray(), secretKey: anyArray()) }.thenReturn([])
|
.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") {
|
it("can encrypt correctly") {
|
||||||
let result = mockStorage.write { db in
|
let result: Data? = mockStorage.read { db in
|
||||||
try? MessageSender.encryptWithSessionProtocol(
|
try? MessageSender.encryptWithSessionProtocol(
|
||||||
db,
|
db,
|
||||||
plaintext: "TestMessage".data(using: .utf8)!,
|
plaintext: "TestMessage".data(using: .utf8)!,
|
||||||
for: "05\(TestConstants.publicKey)",
|
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))
|
expect(result?.count).to(equal(155))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- returns the correct value when mocked
|
||||||
it("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(
|
try? MessageSender.encryptWithSessionProtocol(
|
||||||
db,
|
db,
|
||||||
plaintext: "TestMessage".data(using: .utf8)!,
|
plaintext: "TestMessage".data(using: .utf8)!,
|
||||||
|
@ -83,13 +86,14 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
expect(result?.bytes).to(equal([1, 2, 3]))
|
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") {
|
it("throws an error if there is no ed25519 keyPair") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||||
_ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db)
|
_ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
mockStorage.write { db in
|
mockStorage.read { db in
|
||||||
expect {
|
expect {
|
||||||
try MessageSender.encryptWithSessionProtocol(
|
try MessageSender.encryptWithSessionProtocol(
|
||||||
db,
|
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") {
|
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 {
|
expect {
|
||||||
try MessageSender.encryptWithSessionProtocol(
|
try MessageSender.encryptWithSessionProtocol(
|
||||||
db,
|
db,
|
||||||
|
@ -118,10 +125,13 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- throws an error if the encryption fails
|
||||||
it("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 {
|
expect {
|
||||||
try MessageSender.encryptWithSessionProtocol(
|
try MessageSender.encryptWithSessionProtocol(
|
||||||
db,
|
db,
|
||||||
|
@ -135,9 +145,67 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - when encrypting with the blinded session protocol
|
||||||
context("when encrypting with the blinded session protocol") {
|
context("when encrypting with the blinded session protocol") {
|
||||||
it("successfully encrypts") {
|
beforeEach {
|
||||||
let result = mockStorage.write { db in
|
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(
|
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||||
db,
|
db,
|
||||||
plaintext: "TestMessage".data(using: .utf8)!,
|
plaintext: "TestMessage".data(using: .utf8)!,
|
||||||
|
@ -148,15 +216,12 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(result?.toHexString())
|
expect(result?.toHexString())
|
||||||
.to(equal(
|
.to(equal("00020304a5b4d48b3ade4f4b2a2764762e5a2c7900f254bd91633b43"))
|
||||||
"00db16b6687382811d69875a5376f66acad9c49fe5e26bcf770c7e6e9c230299" +
|
|
||||||
"f61b315299dd1fa700dd7f34305c0465af9e64dc791d7f4123f1eeafa5b4d48b" +
|
|
||||||
"3ade4f4b2a2764762e5a2c7900f254bd91633b43"
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: -- includes a version at the start of the encrypted value
|
||||||
it("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(
|
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||||
db,
|
db,
|
||||||
plaintext: "TestMessage".data(using: .utf8)!,
|
plaintext: "TestMessage".data(using: .utf8)!,
|
||||||
|
@ -169,8 +234,9 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
expect(result?.toHexString().prefix(2)).to(equal("00"))
|
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") {
|
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(
|
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||||
db,
|
db,
|
||||||
plaintext: "TestMessage".data(using: .utf8)!,
|
plaintext: "TestMessage".data(using: .utf8)!,
|
||||||
|
@ -186,8 +252,9 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
||||||
.to(equal("pbTUizreT0sqJ2R2LloseQDyVL2RYztD"))
|
.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") {
|
it("throws an error if the recipient isn't a blinded id") {
|
||||||
mockStorage.write { db in
|
mockStorage.read { db in
|
||||||
expect {
|
expect {
|
||||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||||
db,
|
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") {
|
it("throws an error if there is no ed25519 keyPair") {
|
||||||
mockStorage.write { db in
|
mockStorage.write { db in
|
||||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||||
_ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db)
|
_ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db)
|
||||||
}
|
}
|
||||||
|
|
||||||
mockStorage.write { db in
|
mockStorage.read { db in
|
||||||
expect {
|
expect {
|
||||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||||
db,
|
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") {
|
it("throws an error if it fails to generate a blinded keyPair") {
|
||||||
let mockSodium: MockSodium = MockSodium()
|
mockCrypto
|
||||||
let mockGenericHash: MockGenericHash = MockGenericHash()
|
.when { [dependencies = dependencies!] crypto in
|
||||||
dependencies = dependencies.with(sodium: mockSodium, genericHash: mockGenericHash)
|
crypto.generate(
|
||||||
|
.blindedKeyPair(
|
||||||
mockSodium
|
serverPublicKey: any(),
|
||||||
.when {
|
edKeyPair: any(),
|
||||||
$0.blindedKeyPair(
|
using: dependencies
|
||||||
serverPublicKey: any(),
|
)
|
||||||
edKeyPair: any(),
|
|
||||||
genericHash: mockGenericHash
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn(nil)
|
.thenReturn(nil)
|
||||||
|
|
||||||
mockStorage.write { db in
|
mockStorage.read { db in
|
||||||
expect {
|
expect {
|
||||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||||
db,
|
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") {
|
it("throws an error if it fails to generate an encryption key") {
|
||||||
let mockSodium: MockSodium = MockSodium()
|
mockCrypto
|
||||||
let mockGenericHash: MockGenericHash = MockGenericHash()
|
.when { [dependencies = dependencies!] crypto in
|
||||||
dependencies = dependencies.with(sodium: mockSodium, genericHash: mockGenericHash)
|
try crypto.perform(
|
||||||
|
.sharedBlindedEncryptionKey(
|
||||||
mockSodium
|
secretKey: anyArray(),
|
||||||
.when {
|
otherBlindedPublicKey: anyArray(),
|
||||||
$0.blindedKeyPair(
|
fromBlindedPublicKey: anyArray(),
|
||||||
serverPublicKey: any(),
|
toBlindedPublicKey: anyArray(),
|
||||||
edKeyPair: any(),
|
using: dependencies
|
||||||
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
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.thenReturn(nil)
|
.thenReturn(nil)
|
||||||
|
|
||||||
mockStorage.write { db in
|
mockStorage.read { db in
|
||||||
expect {
|
expect {
|
||||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||||
db,
|
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") {
|
it("throws an error if it fails to encrypt") {
|
||||||
let mockAeadXChaCha: MockAeadXChaCha20Poly1305Ietf = MockAeadXChaCha20Poly1305Ietf()
|
mockCrypto
|
||||||
dependencies = dependencies.with(aeadXChaCha20Poly1305Ietf: mockAeadXChaCha)
|
.when {
|
||||||
|
try $0.perform(
|
||||||
mockAeadXChaCha
|
.encryptAeadXChaCha20(
|
||||||
.when { $0.encrypt(message: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
message: anyArray(),
|
||||||
|
secretKey: anyArray(),
|
||||||
|
nonce: anyArray(),
|
||||||
|
additionalData: anyArray(),
|
||||||
|
using: dependencies
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
.thenReturn(nil)
|
.thenReturn(nil)
|
||||||
|
|
||||||
mockStorage.write { db in
|
mockStorage.read { db in
|
||||||
expect {
|
expect {
|
||||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||||
db,
|
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
|
@testable import SessionMessagingKit
|
||||||
|
|
||||||
class MockOGMCache: Mock<OGMMutableCacheType>, OGMMutableCacheType {
|
class MockOGMCache: Mock<OGMCacheType>, OGMCacheType {
|
||||||
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? {
|
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? {
|
||||||
get { return accept() as? AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error> }
|
get { return accept() as? AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error> }
|
||||||
set { accept(args: [newValue]) }
|
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
|
// Sharing a URL or plain text will populate the 'messageText' field so in those
|
||||||
// cases we should ignore the attachments
|
// cases we should ignore the attachments
|
||||||
let isSharingUrl: Bool = (attachments.count == 1 && attachments[0].isUrl)
|
let isSharingUrl: Bool = (attachments.count == 1 && attachments[0].isUrl)
|
||||||
|
@ -198,7 +204,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
|
||||||
// Resume database
|
// Resume database
|
||||||
NotificationCenter.default.post(name: Database.resumeNotification, object: self)
|
NotificationCenter.default.post(name: Database.resumeNotification, object: self)
|
||||||
|
|
||||||
Storage.shared
|
dependencies.storage
|
||||||
.writePublisher { db -> MessageSender.PreparedSendData in
|
.writePublisher { db -> MessageSender.PreparedSendData in
|
||||||
guard
|
guard
|
||||||
let threadVariant: SessionThread.Variant = try SessionThread
|
let threadVariant: SessionThread.Variant = try SessionThread
|
||||||
|
@ -262,12 +268,13 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
|
||||||
db,
|
db,
|
||||||
interaction: interaction,
|
interaction: interaction,
|
||||||
threadId: threadId,
|
threadId: threadId,
|
||||||
threadVariant: threadVariant
|
threadVariant: threadVariant,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||||
.flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0) }
|
.flatMap { MessageSender.performUploadsIfNeeded(preparedSendData: $0, using: dependencies) }
|
||||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
|
|
|
@ -76,11 +76,16 @@ public extension SnodeReceivedMessageInfo {
|
||||||
// MARK: - GRDB Interactions
|
// MARK: - GRDB Interactions
|
||||||
|
|
||||||
public extension SnodeReceivedMessageInfo {
|
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
|
// 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
|
// 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)
|
// 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
|
.read { db in
|
||||||
// Only prune the hashes if new hashes exist for this Snode (if they don't then
|
// 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)
|
// 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
|
// If there are no rowIds to delete then do nothing
|
||||||
guard !rowIds.isEmpty else { return }
|
guard !rowIds.isEmpty else { return }
|
||||||
|
|
||||||
Storage.shared.write { db in
|
dependencies.storage.write { db in
|
||||||
try SnodeReceivedMessageInfo
|
try SnodeReceivedMessageInfo
|
||||||
.filter(rowIds.contains(Column.rowID))
|
.filter(rowIds.contains(Column.rowID))
|
||||||
.deleteAll(db)
|
.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
|
/// **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
|
/// 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
|
/// 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? {
|
static func fetchLastNotExpired(
|
||||||
return Storage.shared.read { db in
|
for snode: Snode,
|
||||||
|
namespace: SnodeAPI.Namespace,
|
||||||
|
associatedWith publicKey: String,
|
||||||
|
using dependencies: Dependencies
|
||||||
|
) -> SnodeReceivedMessageInfo? {
|
||||||
|
return dependencies.storage.read { db in
|
||||||
let nonLegacyHash: SnodeReceivedMessageInfo? = try SnodeReceivedMessageInfo
|
let nonLegacyHash: SnodeReceivedMessageInfo? = try SnodeReceivedMessageInfo
|
||||||
.filter(
|
.filter(
|
||||||
SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == nil ||
|
SnodeReceivedMessageInfo.Columns.wasDeletedOrInvalid == nil ||
|
||||||
|
|
|
@ -17,7 +17,7 @@ public enum GetSnodePoolJob: JobExecutor {
|
||||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||||
deferred: @escaping (Job, 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'
|
// 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
|
// 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
|
// 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)
|
// speed up the onboarding process for new users because it can run before the user is created)
|
||||||
SnodeAPI.getSnodePool()
|
SnodeAPI.getSnodePool()
|
||||||
.flatMap { _ in OnionRequestAPI.getPath(excluding: nil) }
|
.flatMap { _ in OnionRequestAPI.getPath(excluding: nil, using: dependencies) }
|
||||||
.subscribe(on: queue)
|
.subscribe(on: queue)
|
||||||
.receive(on: queue)
|
.receive(on: queue)
|
||||||
.sinkUntilComplete(
|
.sinkUntilComplete(
|
||||||
|
@ -53,13 +53,14 @@ public enum GetSnodePoolJob: JobExecutor {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func run() {
|
public static func run(using dependencies: Dependencies = Dependencies()) {
|
||||||
GetSnodePoolJob.run(
|
GetSnodePoolJob.run(
|
||||||
Job(variant: .getSnodePool),
|
Job(variant: .getSnodePool),
|
||||||
queue: .global(qos: .background),
|
queue: .global(qos: .background),
|
||||||
success: { _, _, _ in },
|
success: { _, _, _ in },
|
||||||
failure: { _, _, _, _ 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 GRDB
|
||||||
import SessionUtilitiesKit
|
import SessionUtilitiesKit
|
||||||
|
|
||||||
public protocol OnionRequestAPIType {
|
public extension Network.RequestType {
|
||||||
static func sendOnionRequest(_ payload: Data, to snode: Snode, timeout: TimeInterval) -> AnyPublisher<(ResponseInfoType, Data?), Error>
|
static func onionRequest(_ payload: Data, to snode: Snode, timeout: TimeInterval = HTTP.defaultTimeout) -> Network.RequestType<Data?> {
|
||||||
static func sendOnionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String, timeout: TimeInterval) -> AnyPublisher<(ResponseInfoType, Data?), Error>
|
return Network.RequestType(
|
||||||
}
|
id: "onionRequest",
|
||||||
|
url: snode.address,
|
||||||
public extension OnionRequestAPIType {
|
method: "POST",
|
||||||
static func sendOnionRequest(_ payload: Data, to snode: Snode) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
|
body: payload,
|
||||||
return sendOnionRequest(payload, to: snode, timeout: HTTP.defaultTimeout)
|
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> {
|
static func onionRequest(_ request: URLRequest, to server: String, with x25519PublicKey: String, timeout: TimeInterval = HTTP.defaultTimeout) -> Network.RequestType<Data?> {
|
||||||
return sendOnionRequest(request, to: server, with: x25519PublicKey, timeout: HTTP.defaultTimeout)
|
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.
|
/// 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 buildPathsPublisher: Atomic<AnyPublisher<[[Snode]], Error>?> = Atomic(nil)
|
||||||
private static var pathFailureCount: Atomic<[[Snode]: UInt]> = Atomic([:])
|
private static var pathFailureCount: Atomic<[[Snode]: UInt]> = Atomic([:])
|
||||||
private static var snodeFailureCount: Atomic<[Snode: UInt]> = Atomic([:])
|
private static var snodeFailureCount: Atomic<[Snode: UInt]> = Atomic([:])
|
||||||
|
@ -66,12 +74,12 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
||||||
// MARK: - Private API
|
// MARK: - Private API
|
||||||
|
|
||||||
/// Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise.
|
/// Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise.
|
||||||
private static func testSnode(_ snode: 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 url = "\(snode.address):\(snode.port)/get_stats/v1"
|
||||||
let timeout: TimeInterval = 3 // Use a shorter timeout for testing
|
let timeout: TimeInterval = 3 // Use a shorter timeout for testing
|
||||||
|
|
||||||
return HTTP.execute(.get, url, timeout: timeout)
|
return HTTP.execute(.get, url, timeout: timeout)
|
||||||
.decoded(as: SnodeAPI.GetStatsResponse.self)
|
.decoded(as: SnodeAPI.GetStatsResponse.self, using: dependencies)
|
||||||
.tryMap { response -> Void in
|
.tryMap { response -> Void in
|
||||||
guard let version: Version = response.version else { throw OnionRequestAPIError.missingSnodeVersion }
|
guard let version: Version = response.version else { throw OnionRequestAPIError.missingSnodeVersion }
|
||||||
guard version >= Version(major: 2, minor: 0, patch: 7) else {
|
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
|
/// Finds `targetGuardSnodeCount` guard snodes to use for path building. The returned promise errors out with
|
||||||
/// `Error.insufficientSnodes` if not enough (reliable) snodes are available.
|
/// `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 {
|
guard guardSnodes.wrappedValue.count < targetGuardSnodeCount else {
|
||||||
return Just(guardSnodes.wrappedValue)
|
return Just(guardSnodes.wrappedValue)
|
||||||
.setFailureType(to: Error.self)
|
.setFailureType(to: Error.self)
|
||||||
|
@ -115,7 +126,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
||||||
SNLog("Testing guard snode: \(candidate).")
|
SNLog("Testing guard snode: \(candidate).")
|
||||||
|
|
||||||
// Loop until a reliable guard snode is found
|
// Loop until a reliable guard snode is found
|
||||||
return testSnode(candidate)
|
return testSnode(candidate, using: dependencies)
|
||||||
.map { _ in candidate }
|
.map { _ in candidate }
|
||||||
.catch { _ in
|
.catch { _ in
|
||||||
return Just(())
|
return Just(())
|
||||||
|
@ -143,7 +154,10 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
||||||
/// Builds and returns `targetPathCount` paths. The returned promise errors out with `Error.insufficientSnodes`
|
/// Builds and returns `targetPathCount` paths. The returned promise errors out with `Error.insufficientSnodes`
|
||||||
/// if not enough (reliable) snodes are available.
|
/// if not enough (reliable) snodes are available.
|
||||||
@discardableResult
|
@discardableResult
|
||||||
private static func buildPaths(reusing reusablePaths: [[Snode]]) -> AnyPublisher<[[Snode]], Error> {
|
private static func buildPaths(
|
||||||
|
reusing reusablePaths: [[Snode]],
|
||||||
|
using dependencies: Dependencies
|
||||||
|
) -> AnyPublisher<[[Snode]], Error> {
|
||||||
if let existingBuildPathsPublisher = buildPathsPublisher.wrappedValue {
|
if let existingBuildPathsPublisher = buildPathsPublisher.wrappedValue {
|
||||||
return existingBuildPathsPublisher
|
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
|
/// 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
|
/// multiple times as a result of multiple subscribers
|
||||||
let reusableGuardSnodes = reusablePaths.map { $0[0] }
|
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
|
.flatMap { (guardSnodes: Set<Snode>) -> AnyPublisher<[[Snode]], Error> in
|
||||||
var unusedSnodes: Set<Snode> = SnodeAPI.snodePool.wrappedValue
|
var unusedSnodes: Set<Snode> = SnodeAPI.snodePool.wrappedValue
|
||||||
.subtracting(guardSnodes)
|
.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.
|
/// 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.") }
|
guard pathSize >= 1 else { preconditionFailure("Can't build path of size zero.") }
|
||||||
|
|
||||||
let paths: [[Snode]] = OnionRequestAPI.paths
|
let paths: [[Snode]] = OnionRequestAPI.paths
|
||||||
|
@ -257,7 +274,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
||||||
else if !paths.isEmpty {
|
else if !paths.isEmpty {
|
||||||
if let snode = snode {
|
if let snode = snode {
|
||||||
if let path = paths.first(where: { !$0.contains(snode) }) {
|
if let path = paths.first(where: { !$0.contains(snode) }) {
|
||||||
buildPaths(reusing: paths) // Re-build paths in the background
|
buildPaths(reusing: paths, using: dependencies) // Re-build paths in the background
|
||||||
.subscribe(on: DispatchQueue.global(qos: .background))
|
.subscribe(on: DispatchQueue.global(qos: .background))
|
||||||
.sink(receiveCompletion: { _ in cancellable = [] }, receiveValue: { _ in })
|
.sink(receiveCompletion: { _ in cancellable = [] }, receiveValue: { _ in })
|
||||||
.store(in: &cancellable)
|
.store(in: &cancellable)
|
||||||
|
@ -267,7 +284,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return buildPaths(reusing: paths)
|
return buildPaths(reusing: paths, using: dependencies)
|
||||||
.flatMap { paths in
|
.flatMap { paths in
|
||||||
guard let path: [Snode] = paths.filter({ !$0.contains(snode) }).randomElement() else {
|
guard let path: [Snode] = paths.filter({ !$0.contains(snode) }).randomElement() else {
|
||||||
return Fail<[Snode], Error>(error: OnionRequestAPIError.insufficientSnodes)
|
return Fail<[Snode], Error>(error: OnionRequestAPIError.insufficientSnodes)
|
||||||
|
@ -282,7 +299,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
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))
|
.subscribe(on: DispatchQueue.global(qos: .background))
|
||||||
.sink(receiveCompletion: { _ in cancellable = [] }, receiveValue: { _ in })
|
.sink(receiveCompletion: { _ in cancellable = [] }, receiveValue: { _ in })
|
||||||
.store(in: &cancellable)
|
.store(in: &cancellable)
|
||||||
|
@ -298,7 +315,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return buildPaths(reusing: [])
|
return buildPaths(reusing: [], using: dependencies)
|
||||||
.flatMap { paths in
|
.flatMap { paths in
|
||||||
if let snode = snode {
|
if let snode = snode {
|
||||||
if let path = paths.filter({ !$0.contains(snode) }).randomElement() {
|
if let path = paths.filter({ !$0.contains(snode) }).randomElement() {
|
||||||
|
@ -330,7 +347,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
||||||
|
|
||||||
private static func drop(_ snode: Snode) throws {
|
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
|
// 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.
|
// in that case is async.
|
||||||
OnionRequestAPI.snodeFailureCount.mutate { $0[snode] = 0 }
|
OnionRequestAPI.snodeFailureCount.mutate { $0[snode] = 0 }
|
||||||
var oldPaths = paths
|
var oldPaths = paths
|
||||||
|
@ -375,7 +392,8 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
||||||
/// Builds an onion around `payload` and returns the result.
|
/// Builds an onion around `payload` and returns the result.
|
||||||
private static func buildOnion(
|
private static func buildOnion(
|
||||||
around payload: Data,
|
around payload: Data,
|
||||||
targetedAt destination: OnionRequestAPIDestination
|
targetedAt destination: OnionRequestAPIDestination,
|
||||||
|
using dependencies: Dependencies
|
||||||
) -> AnyPublisher<OnionBuildingResult, Error> {
|
) -> AnyPublisher<OnionBuildingResult, Error> {
|
||||||
var guardSnode: Snode!
|
var guardSnode: Snode!
|
||||||
var targetSnodeSymmetricKey: Data! // Needed by invoke(_:on:with:) to decrypt the response sent back by the destination
|
var targetSnodeSymmetricKey: Data! // Needed by invoke(_:on:with:) to decrypt the response sent back by the destination
|
||||||
|
@ -384,7 +402,7 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
||||||
|
|
||||||
if case .snode(let snode) = destination { snodeToExclude = snode }
|
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
|
.flatMap { path -> AnyPublisher<AES.GCM.EncryptionResult, Error> in
|
||||||
guardSnode = path.first!
|
guardSnode = path.first!
|
||||||
|
|
||||||
|
@ -490,11 +508,12 @@ public enum OnionRequestAPI: OnionRequestAPIType {
|
||||||
with payload: Data,
|
with payload: Data,
|
||||||
to destination: OnionRequestAPIDestination,
|
to destination: OnionRequestAPIDestination,
|
||||||
version: OnionRequestAPIVersion,
|
version: OnionRequestAPIVersion,
|
||||||
timeout: TimeInterval = HTTP.defaultTimeout
|
timeout: TimeInterval = HTTP.defaultTimeout,
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
|
) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
|
||||||
var guardSnode: Snode?
|
var guardSnode: Snode?
|
||||||
|
|
||||||
return buildOnion(around: payload, targetedAt: destination)
|
return buildOnion(around: payload, targetedAt: destination, using: dependencies)
|
||||||
.flatMap { intermediate -> AnyPublisher<(ResponseInfoType, Data?), Error> in
|
.flatMap { intermediate -> AnyPublisher<(ResponseInfoType, Data?), Error> in
|
||||||
guardSnode = intermediate.guardSnode
|
guardSnode = intermediate.guardSnode
|
||||||
let url = "\(guardSnode!.address):\(guardSnode!.port)/onion_req/v2"
|
let url = "\(guardSnode!.address):\(guardSnode!.port)/onion_req/v2"
|
||||||
|
|
|
@ -6,6 +6,18 @@ import Sodium
|
||||||
import GRDB
|
import GRDB
|
||||||
import SessionUtilitiesKit
|
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 {
|
public final class SnodeAPI {
|
||||||
internal static let sodium: Atomic<Sodium> = Atomic(Sodium())
|
internal static let sodium: Atomic<Sodium> = Atomic(Sodium())
|
||||||
|
|
||||||
|
@ -135,11 +147,13 @@ public final class SnodeAPI {
|
||||||
return !hasInsufficientSnodes
|
return !hasInsufficientSnodes
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func getSnodePool() -> AnyPublisher<Set<Snode>, Error> {
|
public static func getSnodePool(
|
||||||
|
using dependencies: Dependencies = Dependencies()
|
||||||
|
) -> AnyPublisher<Set<Snode>, Error> {
|
||||||
loadSnodePoolIfNeeded()
|
loadSnodePoolIfNeeded()
|
||||||
|
|
||||||
let now: Date = Date()
|
let now: Date = Date()
|
||||||
let hasSnodePoolExpired: Bool = Storage.shared[.lastSnodePoolRefreshDate]
|
let hasSnodePoolExpired: Bool = dependencies.storage[.lastSnodePoolRefreshDate]
|
||||||
.map { now.timeIntervalSince($0) > 2 * 60 * 60 }
|
.map { now.timeIntervalSince($0) > 2 * 60 * 60 }
|
||||||
.defaulting(to: true)
|
.defaulting(to: true)
|
||||||
let snodePool: Set<Snode> = SnodeAPI.snodePool.wrappedValue
|
let snodePool: Set<Snode> = SnodeAPI.snodePool.wrappedValue
|
||||||
|
@ -163,10 +177,10 @@ public final class SnodeAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
let targetPublisher: AnyPublisher<Set<Snode>, Error> = {
|
let targetPublisher: AnyPublisher<Set<Snode>, Error> = {
|
||||||
guard snodePool.count >= minSnodePoolCount else { return getSnodePoolFromSeedNode() }
|
guard snodePool.count >= minSnodePoolCount else { return getSnodePoolFromSeedNode(using: dependencies) }
|
||||||
|
|
||||||
return getSnodePoolFromSnode()
|
return getSnodePoolFromSnode(using: dependencies)
|
||||||
.catch { _ in getSnodePoolFromSeedNode() }
|
.catch { _ in getSnodePoolFromSeedNode(using: dependencies) }
|
||||||
.eraseToAnyPublisher()
|
.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
|
let validationCount = 3
|
||||||
|
|
||||||
// The name must be lowercased
|
// The name must be lowercased
|
||||||
|
@ -236,7 +253,8 @@ public final class SnodeAPI {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
to: snode,
|
to: snode,
|
||||||
associatedWith: nil
|
associatedWith: nil,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
.decoded(as: ONSResolveResponse.self)
|
.decoded(as: ONSResolveResponse.self)
|
||||||
.tryMap { _, response -> String in
|
.tryMap { _, response -> String in
|
||||||
|
@ -264,7 +282,7 @@ public final class SnodeAPI {
|
||||||
|
|
||||||
public static func getSwarm(
|
public static func getSwarm(
|
||||||
for publicKey: String,
|
for publicKey: String,
|
||||||
using dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<Set<Snode>, Error> {
|
) -> AnyPublisher<Set<Snode>, Error> {
|
||||||
loadSwarmIfNeeded(for: publicKey)
|
loadSwarmIfNeeded(for: publicKey)
|
||||||
|
|
||||||
|
@ -304,14 +322,14 @@ public final class SnodeAPI {
|
||||||
refreshingConfigHashes: [String] = [],
|
refreshingConfigHashes: [String] = [],
|
||||||
from snode: Snode,
|
from snode: Snode,
|
||||||
associatedWith publicKey: String,
|
associatedWith publicKey: String,
|
||||||
using dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<[SnodeAPI.Namespace: (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?)], Error> {
|
) -> AnyPublisher<[SnodeAPI.Namespace: (info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?)], Error> {
|
||||||
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||||
return Fail(error: SnodeAPIError.noKeyPair)
|
return Fail(error: SnodeAPIError.noKeyPair)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
let userX25519PublicKey: String = getUserHexEncodedPublicKey()
|
let userX25519PublicKey: String = getUserHexEncodedPublicKey(using: dependencies)
|
||||||
|
|
||||||
return Just(())
|
return Just(())
|
||||||
.setFailureType(to: Error.self)
|
.setFailureType(to: Error.self)
|
||||||
|
@ -324,14 +342,16 @@ public final class SnodeAPI {
|
||||||
SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo(
|
SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo(
|
||||||
for: snode,
|
for: snode,
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
associatedWith: publicKey
|
associatedWith: publicKey,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
result[namespace] = SnodeReceivedMessageInfo
|
result[namespace] = SnodeReceivedMessageInfo
|
||||||
.fetchLastNotExpired(
|
.fetchLastNotExpired(
|
||||||
for: snode,
|
for: snode,
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
associatedWith: publicKey
|
associatedWith: publicKey,
|
||||||
|
using: dependencies
|
||||||
)?
|
)?
|
||||||
.hash
|
.hash
|
||||||
}
|
}
|
||||||
|
@ -445,7 +465,7 @@ public final class SnodeAPI {
|
||||||
.grouped(by: \.expiry)
|
.grouped(by: \.expiry)
|
||||||
.mapValues({ groupedResults in groupedResults.map { $0.hash } })
|
.mapValues({ groupedResults in groupedResults.map { $0.hash } })
|
||||||
{
|
{
|
||||||
Storage.shared.writeAsync { db in
|
dependencies.storage.writeAsync { db in
|
||||||
try groupedExpiryResult.forEach { updatedExpiry, hashes in
|
try groupedExpiryResult.forEach { updatedExpiry, hashes in
|
||||||
try SnodeReceivedMessageInfo
|
try SnodeReceivedMessageInfo
|
||||||
.filter(hashes.contains(SnodeReceivedMessageInfo.Columns.hash))
|
.filter(hashes.contains(SnodeReceivedMessageInfo.Columns.hash))
|
||||||
|
@ -493,7 +513,7 @@ public final class SnodeAPI {
|
||||||
in namespace: SnodeAPI.Namespace,
|
in namespace: SnodeAPI.Namespace,
|
||||||
from snode: Snode,
|
from snode: Snode,
|
||||||
associatedWith publicKey: String,
|
associatedWith publicKey: String,
|
||||||
using dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<(info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?), Error> {
|
) -> AnyPublisher<(info: ResponseInfoType, data: (messages: [SnodeReceivedMessage], lastHash: String?)?), Error> {
|
||||||
return Deferred {
|
return Deferred {
|
||||||
Future<String?, Error> { resolver in
|
Future<String?, Error> { resolver in
|
||||||
|
@ -501,14 +521,16 @@ public final class SnodeAPI {
|
||||||
SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo(
|
SnodeReceivedMessageInfo.pruneExpiredMessageHashInfo(
|
||||||
for: snode,
|
for: snode,
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
associatedWith: publicKey
|
associatedWith: publicKey,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
|
|
||||||
let maybeLastHash: String? = SnodeReceivedMessageInfo
|
let maybeLastHash: String? = SnodeReceivedMessageInfo
|
||||||
.fetchLastNotExpired(
|
.fetchLastNotExpired(
|
||||||
for: snode,
|
for: snode,
|
||||||
namespace: namespace,
|
namespace: namespace,
|
||||||
associatedWith: publicKey
|
associatedWith: publicKey,
|
||||||
|
using: dependencies
|
||||||
)?
|
)?
|
||||||
.hash
|
.hash
|
||||||
|
|
||||||
|
@ -592,7 +614,7 @@ public final class SnodeAPI {
|
||||||
public static func sendMessage(
|
public static func sendMessage(
|
||||||
_ message: SnodeMessage,
|
_ message: SnodeMessage,
|
||||||
in namespace: Namespace,
|
in namespace: Namespace,
|
||||||
using dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies
|
||||||
) -> AnyPublisher<(ResponseInfoType, SendMessagesResponse), Error> {
|
) -> AnyPublisher<(ResponseInfoType, SendMessagesResponse), Error> {
|
||||||
let publicKey: String = message.recipient
|
let publicKey: String = message.recipient
|
||||||
let userX25519PublicKey: String = getUserHexEncodedPublicKey()
|
let userX25519PublicKey: String = getUserHexEncodedPublicKey()
|
||||||
|
@ -661,7 +683,7 @@ public final class SnodeAPI {
|
||||||
public static func sendConfigMessages(
|
public static func sendConfigMessages(
|
||||||
_ messages: [(message: SnodeMessage, namespace: Namespace)],
|
_ messages: [(message: SnodeMessage, namespace: Namespace)],
|
||||||
allObsoleteHashes: [String],
|
allObsoleteHashes: [String],
|
||||||
using dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<HTTP.BatchResponse, Error> {
|
) -> AnyPublisher<HTTP.BatchResponse, Error> {
|
||||||
guard
|
guard
|
||||||
!messages.isEmpty,
|
!messages.isEmpty,
|
||||||
|
@ -755,7 +777,7 @@ public final class SnodeAPI {
|
||||||
publicKey: String,
|
publicKey: String,
|
||||||
serverHashes: [String],
|
serverHashes: [String],
|
||||||
updatedExpiryMs: UInt64,
|
updatedExpiryMs: UInt64,
|
||||||
using dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<[String: [(hash: String, expiry: UInt64)]], Error> {
|
) -> AnyPublisher<[String: [(hash: String, expiry: UInt64)]], Error> {
|
||||||
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||||
return Fail(error: SnodeAPIError.noKeyPair)
|
return Fail(error: SnodeAPIError.noKeyPair)
|
||||||
|
@ -796,7 +818,7 @@ public final class SnodeAPI {
|
||||||
public static func revokeSubkey(
|
public static func revokeSubkey(
|
||||||
publicKey: String,
|
publicKey: String,
|
||||||
subkeyToRevoke: String,
|
subkeyToRevoke: String,
|
||||||
using dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<Void, Error> {
|
) -> AnyPublisher<Void, Error> {
|
||||||
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||||
return Fail(error: SnodeAPIError.noKeyPair)
|
return Fail(error: SnodeAPIError.noKeyPair)
|
||||||
|
@ -839,14 +861,14 @@ public final class SnodeAPI {
|
||||||
public static func deleteMessages(
|
public static func deleteMessages(
|
||||||
publicKey: String,
|
publicKey: String,
|
||||||
serverHashes: [String],
|
serverHashes: [String],
|
||||||
using dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<[String: Bool], Error> {
|
) -> AnyPublisher<[String: Bool], Error> {
|
||||||
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||||
return Fail(error: SnodeAPIError.noKeyPair)
|
return Fail(error: SnodeAPIError.noKeyPair)
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
let userX25519PublicKey: String = getUserHexEncodedPublicKey()
|
let userX25519PublicKey: String = getUserHexEncodedPublicKey(using: dependencies)
|
||||||
|
|
||||||
return getSwarm(for: publicKey)
|
return getSwarm(for: publicKey)
|
||||||
.tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: Bool], Error> in
|
.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.
|
/// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation.
|
||||||
public static func deleteAllMessages(
|
public static func deleteAllMessages(
|
||||||
namespace: SnodeAPI.Namespace,
|
namespace: SnodeAPI.Namespace,
|
||||||
using dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<[String: Bool], Error> {
|
) -> AnyPublisher<[String: Bool], Error> {
|
||||||
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||||
return Fail(error: SnodeAPIError.noKeyPair)
|
return Fail(error: SnodeAPIError.noKeyPair)
|
||||||
|
@ -941,7 +963,7 @@ public final class SnodeAPI {
|
||||||
public static func deleteAllMessages(
|
public static func deleteAllMessages(
|
||||||
beforeMs: UInt64,
|
beforeMs: UInt64,
|
||||||
namespace: SnodeAPI.Namespace,
|
namespace: SnodeAPI.Namespace,
|
||||||
using dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<[String: Bool], Error> {
|
) -> AnyPublisher<[String: Bool], Error> {
|
||||||
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
guard let userED25519KeyPair = Identity.fetchUserEd25519KeyPair() else {
|
||||||
return Fail(error: SnodeAPIError.noKeyPair)
|
return Fail(error: SnodeAPIError.noKeyPair)
|
||||||
|
@ -989,7 +1011,7 @@ public final class SnodeAPI {
|
||||||
|
|
||||||
private static func getNetworkTime(
|
private static func getNetworkTime(
|
||||||
from snode: Snode,
|
from snode: Snode,
|
||||||
using dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies = Dependencies()
|
||||||
) -> AnyPublisher<UInt64, Error> {
|
) -> AnyPublisher<UInt64, Error> {
|
||||||
return SnodeAPI
|
return SnodeAPI
|
||||||
.send(
|
.send(
|
||||||
|
@ -998,7 +1020,8 @@ public final class SnodeAPI {
|
||||||
body: [:]
|
body: [:]
|
||||||
),
|
),
|
||||||
to: snode,
|
to: snode,
|
||||||
associatedWith: nil
|
associatedWith: nil,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
.decoded(as: GetNetworkTimestampResponse.self, using: dependencies)
|
.decoded(as: GetNetworkTimestampResponse.self, using: dependencies)
|
||||||
.map { _, response in response.timestamp }
|
.map { _, response in response.timestamp }
|
||||||
|
@ -1013,7 +1036,7 @@ public final class SnodeAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getSnodePoolFromSeedNode(
|
private static func getSnodePoolFromSeedNode(
|
||||||
dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies
|
||||||
) -> AnyPublisher<Set<Snode>, Error> {
|
) -> AnyPublisher<Set<Snode>, Error> {
|
||||||
let request: SnodeRequest = SnodeRequest(
|
let request: SnodeRequest = SnodeRequest(
|
||||||
endpoint: .jsonGetNServiceNodes,
|
endpoint: .jsonGetNServiceNodes,
|
||||||
|
@ -1073,7 +1096,7 @@ public final class SnodeAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func getSnodePoolFromSnode(
|
private static func getSnodePoolFromSnode(
|
||||||
dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies
|
||||||
) -> AnyPublisher<Set<Snode>, Error> {
|
) -> AnyPublisher<Set<Snode>, Error> {
|
||||||
var snodePool = SnodeAPI.snodePool.wrappedValue
|
var snodePool = SnodeAPI.snodePool.wrappedValue
|
||||||
var snodes: Set<Snode> = []
|
var snodes: Set<Snode> = []
|
||||||
|
@ -1110,7 +1133,8 @@ public final class SnodeAPI {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
to: snode,
|
to: snode,
|
||||||
associatedWith: nil
|
associatedWith: nil,
|
||||||
|
using: dependencies
|
||||||
)
|
)
|
||||||
.decoded(as: SnodePoolResponse.self, using: dependencies)
|
.decoded(as: SnodePoolResponse.self, using: dependencies)
|
||||||
.mapError { error -> Error in
|
.mapError { error -> Error in
|
||||||
|
@ -1149,7 +1173,7 @@ public final class SnodeAPI {
|
||||||
request: SnodeRequest<T>,
|
request: SnodeRequest<T>,
|
||||||
to snode: Snode,
|
to snode: Snode,
|
||||||
associatedWith publicKey: String?,
|
associatedWith publicKey: String?,
|
||||||
using dependencies: SSKDependencies = SSKDependencies()
|
using dependencies: Dependencies
|
||||||
) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
|
) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
|
||||||
guard let payload: Data = try? JSONEncoder().encode(request) else {
|
guard let payload: Data = try? JSONEncoder().encode(request) else {
|
||||||
return Fail(error: HTTPError.invalidJSON)
|
return Fail(error: HTTPError.invalidJSON)
|
||||||
|
@ -1175,11 +1199,8 @@ public final class SnodeAPI {
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
return dependencies.onionApi
|
return dependencies.network
|
||||||
.sendOnionRequest(
|
.send(.onionRequest(payload, to: snode))
|
||||||
payload,
|
|
||||||
to: snode
|
|
||||||
)
|
|
||||||
.mapError { error in
|
.mapError { error in
|
||||||
switch error {
|
switch error {
|
||||||
case HTTPError.httpRequestFailed(let statusCode, let data):
|
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