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:
Morgan Pretty 2023-08-01 14:27:41 +10:00
parent e768bebe6d
commit a41f1c1366
139 changed files with 7211 additions and 7867 deletions

View File

@ -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,

View File

@ -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

View File

@ -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;
}; };

View File

@ -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()
} }

View File

@ -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
) )
} }

View File

@ -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())
}
}

View File

@ -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

View File

@ -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

View File

@ -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
) )
} }
} }

View File

@ -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())
}
} }

View File

@ -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
) )
} }
} }

View File

@ -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)
} }

View File

@ -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 {

View File

@ -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 {

View File

@ -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 ()

View File

@ -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 = []
} }

View File

@ -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)

View File

@ -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()
} }

View File

@ -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)

View File

@ -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 }

View File

@ -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

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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,

View File

@ -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
) )
} }

View File

@ -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

View File

@ -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
) )
} }
} }

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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(

View File

@ -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,

View File

@ -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 {

View File

@ -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,

View File

@ -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 {

View File

@ -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

View File

@ -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) }

View File

@ -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

View File

@ -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
)
}
}
}

View File

@ -69,7 +69,6 @@ public extension OpenGroupAPI {
info = HTTP.ResponseInfo(code: 0, headers: [:]) info = HTTP.ResponseInfo(code: 0, headers: [:])
data = [:] data = [:]
#endif #endif
} }
} }
} }

View File

@ -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
} }

View File

@ -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()

View File

@ -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
)
}
}
}

View File

@ -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 }
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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
)
}
}

View File

@ -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

View File

@ -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()

View File

@ -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))

View File

@ -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
) )
} }
} }

View File

@ -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 }

View File

@ -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
) )
} }
} }

View File

@ -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 {}

View File

@ -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)
} }

View File

@ -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,

View File

@ -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
) )
} }
) )

View File

@ -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)

View File

@ -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
) )
} }
} }

View File

@ -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
} }

View File

@ -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)
} }

View File

@ -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

View File

@ -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
) )
} }
} }

View File

@ -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)
} }
} }
} }

View File

@ -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 {

View File

@ -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

View File

@ -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))
}
}
}

View File

@ -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)
} }
} }

View File

@ -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
}
}

View File

@ -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) })

View File

@ -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") {

View File

@ -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

View File

@ -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()
)
})
}
}
}
}

View File

@ -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 {

View File

@ -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,

View File

@ -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())
}
}
}
}
}
}

View File

@ -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())
}
}
}
}
}

View File

@ -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)
)
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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] }
}

View File

@ -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] }
}

View File

@ -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]) }

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
)
}
}

View File

@ -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()
}
}

View File

@ -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(

View File

@ -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 ||

View File

@ -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
) )
} }
} }

View File

@ -1,3 +0,0 @@
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import Foundation

View File

@ -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"

View File

@ -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):

View File

@ -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