Merge remote-tracking branch 'upstream/dev' into feature/updated-push-server
# Conflicts: # Session.xcodeproj/project.pbxproj # SessionMessagingKit/Open Groups/Types/SodiumProtocols.swift # SessionMessagingKit/Sending & Receiving/Message Handling/MessageSender+ClosedGroups.swift # SessionMessagingKitTests/_TestUtilities/MockAeadXChaCha20Poly1305Ietf.swift # SessionUtilitiesKit/JobRunner/JobRunner.swift
This commit is contained in:
commit
18ee9d34fa
|
@ -13,7 +13,9 @@ local ci_dep_mirror(want_mirror) = (if want_mirror then ' -DLOCAL_MIRROR=https:/
|
|||
// 'LANG' env var so we need to work around the with https://github.com/CocoaPods/CocoaPods/issues/6333
|
||||
local install_cocoapods = {
|
||||
name: 'Install CocoaPods',
|
||||
commands: ['LANG=en_US.UTF-8 pod install']
|
||||
commands: ['
|
||||
LANG=en_US.UTF-8 pod install || rm -rf ./Pods && LANG=en_US.UTF-8 pod install
|
||||
']
|
||||
};
|
||||
|
||||
// Load from the cached CocoaPods directory (to speed up the build)
|
||||
|
@ -21,8 +23,14 @@ local load_cocoapods_cache = {
|
|||
name: 'Load CocoaPods Cache',
|
||||
commands: [
|
||||
|||
|
||||
LOOP_BREAK=0
|
||||
while test -e /Users/drone/.cocoapods_cache.lock; do
|
||||
sleep 1
|
||||
LOOP_BREAK=$((LOOP_BREAK + 1))
|
||||
|
||||
if [[ $LOOP_BREAK -ge 600 ]]; then
|
||||
rm -f /Users/drone/.cocoapods_cache.lock
|
||||
fi
|
||||
done
|
||||
|||,
|
||||
'touch /Users/drone/.cocoapods_cache.lock',
|
||||
|
@ -31,7 +39,7 @@ local load_cocoapods_cache = {
|
|||
cp -r /Users/drone/.cocoapods_cache ./Pods
|
||||
fi
|
||||
|||,
|
||||
'rm /Users/drone/.cocoapods_cache.lock'
|
||||
'rm -f /Users/drone/.cocoapods_cache.lock'
|
||||
]
|
||||
};
|
||||
|
||||
|
@ -40,8 +48,14 @@ local update_cocoapods_cache = {
|
|||
name: 'Update CocoaPods Cache',
|
||||
commands: [
|
||||
|||
|
||||
LOOP_BREAK=0
|
||||
while test -e /Users/drone/.cocoapods_cache.lock; do
|
||||
sleep 1
|
||||
LOOP_BREAK=$((LOOP_BREAK + 1))
|
||||
|
||||
if [[ $LOOP_BREAK -ge 600 ]]; then
|
||||
rm -f /Users/drone/.cocoapods_cache.lock
|
||||
fi
|
||||
done
|
||||
|||,
|
||||
'touch /Users/drone/.cocoapods_cache.lock',
|
||||
|
@ -51,7 +65,7 @@ local update_cocoapods_cache = {
|
|||
cp -r ./Pods /Users/drone/.cocoapods_cache
|
||||
fi
|
||||
|||,
|
||||
'rm /Users/drone/.cocoapods_cache.lock'
|
||||
'rm -f /Users/drone/.cocoapods_cache.lock'
|
||||
]
|
||||
};
|
||||
|
||||
|
@ -71,7 +85,7 @@ local update_cocoapods_cache = {
|
|||
name: 'Run Unit Tests',
|
||||
commands: [
|
||||
'mkdir build',
|
||||
'NSUnbufferedIO=YES set -o pipefail && xcodebuild test -workspace Session.xcworkspace -scheme Session -destination "platform=iOS Simulator,name=iPhone 14" -destination "platform=iOS Simulator,name=iPhone 14 Pro Max" -parallel-testing-enabled YES -test-timeouts-enabled YES -maximum-test-execution-time-allowance 2 -collect-test-diagnostics never 2>&1 | ./Pods/xcbeautify/xcbeautify --is-ci --report junit --report-path ./build/reports --junit-report-filename junit2.xml'
|
||||
'NSUnbufferedIO=YES set -o pipefail && xcodebuild test -workspace Session.xcworkspace -scheme Session -derivedDataPath ./build/derivedData -destination "platform=iOS Simulator,name=iPhone 14" -destination "platform=iOS Simulator,name=iPhone 14 Pro Max" -parallel-testing-enabled YES -test-timeouts-enabled YES -maximum-test-execution-time-allowance 2 -collect-test-diagnostics never 2>&1 | ./Pods/xcbeautify/xcbeautify --is-ci --report junit --report-path ./build/reports --junit-report-filename junit2.xml'
|
||||
],
|
||||
},
|
||||
update_cocoapods_cache
|
||||
|
@ -83,6 +97,7 @@ local update_cocoapods_cache = {
|
|||
type: 'exec',
|
||||
name: 'Simulator Build',
|
||||
platform: { os: 'darwin', arch: 'amd64' },
|
||||
trigger: { event: { exclude: [ 'pull_request' ] } },
|
||||
steps: [
|
||||
clone_submodules,
|
||||
load_cocoapods_cache,
|
||||
|
@ -91,7 +106,7 @@ local update_cocoapods_cache = {
|
|||
name: 'Build',
|
||||
commands: [
|
||||
'mkdir build',
|
||||
'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | ./Pods/xcbeautify/xcbeautify --is-ci'
|
||||
'xcodebuild archive -workspace Session.xcworkspace -scheme Session -derivedDataPath ./build/derivedData -configuration "App Store Release" -sdk iphonesimulator -archivePath ./build/Session_sim.xcarchive -destination "generic/platform=iOS Simulator" | ./Pods/xcbeautify/xcbeautify --is-ci'
|
||||
],
|
||||
},
|
||||
update_cocoapods_cache,
|
||||
|
@ -110,6 +125,7 @@ local update_cocoapods_cache = {
|
|||
type: 'exec',
|
||||
name: 'AppStore Build',
|
||||
platform: { os: 'darwin', arch: 'amd64' },
|
||||
trigger: { event: { exclude: [ 'pull_request' ] } },
|
||||
steps: [
|
||||
clone_submodules,
|
||||
load_cocoapods_cache,
|
||||
|
@ -118,7 +134,7 @@ local update_cocoapods_cache = {
|
|||
name: 'Build',
|
||||
commands: [
|
||||
'mkdir build',
|
||||
'xcodebuild archive -workspace Session.xcworkspace -scheme Session -configuration "App Store Release" -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates'
|
||||
'xcodebuild archive -workspace Session.xcworkspace -scheme Session -derivedDataPath ./build/derivedData -configuration "App Store Release" -sdk iphoneos -archivePath ./build/Session.xcarchive -destination "generic/platform=iOS" -allowProvisioningUpdates CODE_SIGNING_ALLOWED=NO | ./Pods/xcbeautify/xcbeautify --is-ci'
|
||||
],
|
||||
},
|
||||
update_cocoapods_cache,
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
# Script used with Drone CI to upload build artifacts (because specifying all this in
|
||||
# .drone.jsonnet is too painful).
|
||||
|
||||
|
||||
|
||||
set -o errexit
|
||||
|
||||
if [ -z "$SSH_KEY" ]; then
|
||||
|
@ -19,33 +17,36 @@ set -o xtrace # Don't start tracing until *after* we write the ssh key
|
|||
|
||||
chmod 600 ssh_key
|
||||
|
||||
if [ -n "$DRONE_TAG" ]; then
|
||||
# For a tag build use something like `session-ios-v1.2.3`
|
||||
base="session-ios-$DRONE_TAG"
|
||||
else
|
||||
# Otherwise build a length name from the datetime and commit hash, such as:
|
||||
# session-ios-20200522T212342Z-04d7dcc54
|
||||
base="session-ios-$(date --date=@$DRONE_BUILD_CREATED +%Y%m%dT%H%M%SZ)-${DRONE_COMMIT:0:9}"
|
||||
fi
|
||||
|
||||
mkdir -v "$base"
|
||||
|
||||
# Copy over the build products
|
||||
# Define the output paths
|
||||
prod_path="build/Session.xcarchive"
|
||||
sim_path="build/Session_sim.xcarchive/Products/Applications/Session.app"
|
||||
|
||||
mkdir build
|
||||
echo "Test" > "build/test.txt"
|
||||
|
||||
if [ ! -d $prod_path ]; then
|
||||
cp -av $prod_path "$base"
|
||||
else if [ ! -d $sim_path ]; then
|
||||
cp -av $sim_path "$base"
|
||||
# Validate the paths exist
|
||||
if [ -d $prod_path ]; then
|
||||
suffix="store"
|
||||
target_path=$prod_path
|
||||
elif [ -d $sim_path ]; then
|
||||
suffix="sim"
|
||||
target_path=$sim_path
|
||||
else
|
||||
echo "Expected a file to upload, found none" >&2
|
||||
echo -e "\n\n\n\e[31;1mExpected a file to upload, found none\e[0m" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$DRONE_TAG" ]; then
|
||||
# For a tag build use something like `session-ios-v1.2.3`
|
||||
base="session-ios-$DRONE_TAG-$suffix"
|
||||
else
|
||||
# Otherwise build a length name from the datetime and commit hash, such as:
|
||||
# session-ios-20200522T212342Z-04d7dcc54
|
||||
base="session-ios-$(date --date=@$DRONE_BUILD_CREATED +%Y%m%dT%H%M%SZ)-${DRONE_COMMIT:0:9}-$suffix"
|
||||
fi
|
||||
|
||||
# Copy over the build products
|
||||
mkdir -vp "$base"
|
||||
mkdir -p build
|
||||
cp -av $target_path "$base"
|
||||
|
||||
# tar dat shiz up yo
|
||||
archive="$base.tar.xz"
|
||||
tar cJvf "$archive" "$base"
|
||||
|
|
|
@ -327,7 +327,6 @@
|
|||
C33FDD49255A582000E217F9 /* ParamParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDB8F255A581200E217F9 /* ParamParser.swift */; };
|
||||
C33FDD5B255A582000E217F9 /* OWSOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBA1255A581400E217F9 /* OWSOperation.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C33FDD6E255A582000E217F9 /* NSURLSessionDataTask+StatusCode.m in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBB4255A581600E217F9 /* NSURLSessionDataTask+StatusCode.m */; };
|
||||
C33FDD7C255A582000E217F9 /* SSKAsserts.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBC2255A581700E217F9 /* SSKAsserts.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
C33FDD8D255A582000E217F9 /* OWSSignalAddress.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */; };
|
||||
C33FDD92255A582000E217F9 /* SignalIOS.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBD8255A581900E217F9 /* SignalIOS.pb.swift */; };
|
||||
C33FDDB0255A582000E217F9 /* NSURLSessionDataTask+StatusCode.h in Headers */ = {isa = PBXBuildFile; fileRef = C33FDBF6255A581C00E217F9 /* NSURLSessionDataTask+StatusCode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
|
@ -471,11 +470,10 @@
|
|||
FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4727E02561000769AF /* CommonMockedExtensions.swift */; };
|
||||
FD078E4927E02576000769AF /* CommonMockedExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4727E02561000769AF /* CommonMockedExtensions.swift */; };
|
||||
FD078E4D27E17156000769AF /* MockOGMCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4C27E17156000769AF /* MockOGMCache.swift */; };
|
||||
FD078E4F27E175F1000769AF /* DependencyExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E4E27E175F1000769AF /* DependencyExtensions.swift */; };
|
||||
FD078E5227E1760A000769AF /* OGMDependencyExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E5127E1760A000769AF /* OGMDependencyExtensions.swift */; };
|
||||
FD078E5427E197CA000769AF /* OpenGroupManagerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */; };
|
||||
FD078E5A27E29F09000769AF /* MockNonce16Generator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E5927E29F09000769AF /* MockNonce16Generator.swift */; };
|
||||
FD078E5C27E29F78000769AF /* MockNonce24Generator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD078E5B27E29F78000769AF /* MockNonce24Generator.swift */; };
|
||||
FD0969F92A69FFE700C5C365 /* Mocked.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0969F82A69FFE700C5C365 /* Mocked.swift */; };
|
||||
FD0969FA2A6A00B000C5C365 /* Mocked.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0969F82A69FFE700C5C365 /* Mocked.swift */; };
|
||||
FD0969FB2A6A00B100C5C365 /* Mocked.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD0969F82A69FFE700C5C365 /* Mocked.swift */; };
|
||||
FD09796B27F6C67500936362 /* Failable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796A27F6C67500936362 /* Failable.swift */; };
|
||||
FD09796E27FA6D0000936362 /* Contact.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796D27FA6D0000936362 /* Contact.swift */; };
|
||||
FD09797027FA6FF300936362 /* Profile.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD09796F27FA6FF300936362 /* Profile.swift */; };
|
||||
|
@ -533,6 +531,21 @@
|
|||
FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */; };
|
||||
FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FD2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift */; };
|
||||
FD1C98E4282E3C5B00B76F9E /* UINavigationBar+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */; };
|
||||
FD23CE1B2A651E6D0000B97C /* NetworkType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1A2A651E6D0000B97C /* NetworkType.swift */; };
|
||||
FD23CE1F2A65269C0000B97C /* Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE1E2A65269C0000B97C /* Crypto.swift */; };
|
||||
FD23CE222A661D000000B97C /* OpenGroupAPI+Crypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */; };
|
||||
FD23CE242A675C440000B97C /* Crypto+SessionMessagingKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE232A675C440000B97C /* Crypto+SessionMessagingKit.swift */; };
|
||||
FD23CE262A676B5B0000B97C /* DependenciesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE252A676B5B0000B97C /* DependenciesSpec.swift */; };
|
||||
FD23CE282A67755C0000B97C /* MockCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE272A67755C0000B97C /* MockCrypto.swift */; };
|
||||
FD23CE292A6775650000B97C /* MockCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE272A67755C0000B97C /* MockCrypto.swift */; };
|
||||
FD23CE2A2A6775660000B97C /* MockCrypto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE272A67755C0000B97C /* MockCrypto.swift */; };
|
||||
FD23CE2C2A678DF80000B97C /* MockCaches.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE2B2A678DF80000B97C /* MockCaches.swift */; };
|
||||
FD23CE2D2A678E1E0000B97C /* MockCaches.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE2B2A678DF80000B97C /* MockCaches.swift */; };
|
||||
FD23CE2E2A678E1E0000B97C /* MockCaches.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE2B2A678DF80000B97C /* MockCaches.swift */; };
|
||||
FD23CE302A67B8820000B97C /* Caches.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE2F2A67B8820000B97C /* Caches.swift */; };
|
||||
FD23CE332A67C4D90000B97C /* MockNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE312A67C38D0000B97C /* MockNetwork.swift */; };
|
||||
FD23CE342A67C4D90000B97C /* MockNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE312A67C38D0000B97C /* MockNetwork.swift */; };
|
||||
FD23CE352A67C4DA0000B97C /* MockNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD23CE312A67C38D0000B97C /* MockNetwork.swift */; };
|
||||
FD23EA5C28ED00F80058676E /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; };
|
||||
FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9BD27CF2243005E1583 /* TestConstants.swift */; };
|
||||
FD23EA5E28ED00FD0058676E /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; };
|
||||
|
@ -616,9 +629,8 @@
|
|||
FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */; };
|
||||
FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */; };
|
||||
FD3C906727E416AF00CD579F /* BlindedIdLookupSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906627E416AF00CD579F /* BlindedIdLookupSpec.swift */; };
|
||||
FD3C906A27E417CE00CD579F /* SodiumUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906927E417CE00CD579F /* SodiumUtilitiesSpec.swift */; };
|
||||
FD3C906A27E417CE00CD579F /* CryptoSMKSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906927E417CE00CD579F /* CryptoSMKSpec.swift */; };
|
||||
FD3C906D27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906C27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift */; };
|
||||
FD3C906F27E43E8700CD579F /* MockBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C906E27E43E8700CD579F /* MockBox.swift */; };
|
||||
FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3C907027E445E500CD579F /* MessageReceiverDecryptionSpec.swift */; };
|
||||
FD3E0C84283B5835002A425C /* SessionThreadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */; };
|
||||
FD42F9A8285064B800A0C77D /* PushNotificationAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */; };
|
||||
|
@ -635,6 +647,7 @@
|
|||
FD52090528B4915F006098F6 /* PrivacySettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090428B4915F006098F6 /* PrivacySettingsViewModel.swift */; };
|
||||
FD52090928B59411006098F6 /* ScreenLockUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090828B59411006098F6 /* ScreenLockUI.swift */; };
|
||||
FD52090B28B59BB4006098F6 /* ScreenLockViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */; };
|
||||
FD559DF52A7368CB00C7C62A /* DispatchQueue+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */; };
|
||||
FD5C72F7284F0E560029977D /* MessageReceiver+ReadReceipts.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */; };
|
||||
FD5C72F9284F0E880029977D /* MessageReceiver+TypingIndicators.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */; };
|
||||
FD5C72FB284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */; };
|
||||
|
@ -698,7 +711,6 @@
|
|||
FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */; };
|
||||
FD7728962849E7E90018502F /* String+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728952849E7E90018502F /* String+Utilities.swift */; };
|
||||
FD7728982849E8110018502F /* UITableView+ReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD7728972849E8110018502F /* UITableView+ReusableView.swift */; };
|
||||
FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD772899284AF1BD0018502F /* Sodium+Utilities.swift */; };
|
||||
FD77289E284EF1C50018502F /* Sodium+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD77289D284EF1C50018502F /* Sodium+Utilities.swift */; };
|
||||
FD778B6429B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */; };
|
||||
FD83B9B327CF200A005E1583 /* SessionUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3C2A679255388CC00C340D1 /* SessionUtilitiesKit.framework */; platformFilter = ios; };
|
||||
|
@ -710,6 +722,7 @@
|
|||
FD83B9C927D0487A005E1583 /* SendDirectMessageResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */; };
|
||||
FD83B9CC27D179BC005E1583 /* FSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9CB27D179BC005E1583 /* FSEndpoint.swift */; };
|
||||
FD83B9D227D59495005E1583 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; };
|
||||
FD83DCDD2A739D350065FFAE /* RetryWithDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */; };
|
||||
FD848B8B283DC509000E298B /* PagedDatabaseObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8A283DC509000E298B /* PagedDatabaseObserver.swift */; };
|
||||
FD848B8D283E0B26000E298B /* MessageInputTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8C283E0B26000E298B /* MessageInputTypes.swift */; };
|
||||
FD848B8F283EF2A8000E298B /* UIScrollView+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B8E283EF2A8000E298B /* UIScrollView+Utilities.swift */; };
|
||||
|
@ -718,11 +731,6 @@
|
|||
FD848B9828422F1A000E298B /* Date+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9728422F1A000E298B /* Date+Utilities.swift */; };
|
||||
FD848B9A28442CE6000E298B /* StorageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9928442CE6000E298B /* StorageError.swift */; };
|
||||
FD848B9C284435D7000E298B /* AppSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD848B9B284435D7000E298B /* AppSetup.swift */; };
|
||||
FD859EF427C2F49200510D0C /* MockSodium.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF327C2F49200510D0C /* MockSodium.swift */; };
|
||||
FD859EF627C2F52C00510D0C /* MockSign.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF527C2F52C00510D0C /* MockSign.swift */; };
|
||||
FD859EF827C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF727C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift */; };
|
||||
FD859EFA27C2F5C500510D0C /* MockGenericHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EF927C2F5C500510D0C /* MockGenericHash.swift */; };
|
||||
FD859EFC27C2F60700510D0C /* MockEd25519.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD859EFB27C2F60700510D0C /* MockEd25519.swift */; };
|
||||
FD87DCFA28B74DB300AF0F98 /* ConversationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCF928B74DB300AF0F98 /* ConversationSettingsViewModel.swift */; };
|
||||
FD87DCFE28B7582C00AF0F98 /* BlockedContactsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */; };
|
||||
FD87DD0028B820F200AF0F98 /* BlockedContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD87DCFF28B820F200AF0F98 /* BlockedContactCell.swift */; };
|
||||
|
@ -740,11 +748,16 @@
|
|||
FD9004142818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */; };
|
||||
FD9004152818B46300ABAAF6 /* JobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF0B7432804EF1B004C14C5 /* JobRunner.swift */; };
|
||||
FD9004162818B46700ABAAF6 /* JobRunnerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE77F68280F9EDA002CFC5D /* JobRunnerError.swift */; };
|
||||
FD96F3A529DBC3DC00401309 /* MessageSendJobSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */; };
|
||||
FD96F3A729DBD43D00401309 /* MockJobRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD96F3A629DBD43D00401309 /* MockJobRunner.swift */; };
|
||||
FD97B2402A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */; };
|
||||
FD97B2422A3FEBF30027DD57 /* UnreadMarkerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD97B2412A3FEBF30027DD57 /* UnreadMarkerCell.swift */; };
|
||||
FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9B30F2293EA0BF008DEE3E /* BatchResponseSpec.swift */; };
|
||||
FD9BDE002A5D22B7005F1EBC /* libSessionUtil.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */; };
|
||||
FD9BDE012A5D24EA005F1EBC /* SessionUIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C331FF1B2558F9D300070591 /* SessionUIKit.framework */; };
|
||||
FD9DD2712A72516D00ECB68E /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9DD2702A72516D00ECB68E /* TestExtensions.swift */; };
|
||||
FD9DD2722A72516D00ECB68E /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9DD2702A72516D00ECB68E /* TestExtensions.swift */; };
|
||||
FD9DD2732A72516D00ECB68E /* TestExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD9DD2702A72516D00ECB68E /* TestExtensions.swift */; };
|
||||
FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */; };
|
||||
FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */; };
|
||||
FDA1E83B29A5F2D500C5C3BD /* SessionUtil+Shared.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */; };
|
||||
|
@ -778,14 +791,11 @@
|
|||
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */; };
|
||||
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */; };
|
||||
FDC2909A27D71376005DAE71 /* NonceGeneratorSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909927D71376005DAE71 /* NonceGeneratorSpec.swift */; };
|
||||
FDC2909C27D713D2005DAE71 /* SodiumProtocolsSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC2909B27D713D2005DAE71 /* SodiumProtocolsSpec.swift */; };
|
||||
FDC290A627D860CE005DAE71 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; };
|
||||
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; };
|
||||
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */; };
|
||||
FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290A527D860CE005DAE71 /* Mock.swift */; };
|
||||
FDC290B327DFF9F5005DAE71 /* TestOnionRequestAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC290B227DFF9F5005DAE71 /* TestOnionRequestAPI.swift */; };
|
||||
FDC4380927B31D4E00C60D73 /* OpenGroupAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */; };
|
||||
FDC4381527B329CE00C60D73 /* NonceGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381427B329CE00C60D73 /* NonceGenerator.swift */; };
|
||||
FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381627B32EC700C60D73 /* Personalization.swift */; };
|
||||
FDC4382027B36ADC00C60D73 /* SOGSEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */; };
|
||||
FDC4382F27B383AF00C60D73 /* LegacyPushServerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */; };
|
||||
|
@ -805,8 +815,6 @@
|
|||
FDC438A627BB113A00C60D73 /* UserUnbanRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A527BB113A00C60D73 /* UserUnbanRequest.swift */; };
|
||||
FDC438AA27BB12BB00C60D73 /* UserModeratorRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438A927BB12BB00C60D73 /* UserModeratorRequest.swift */; };
|
||||
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438BC27BB2AB400C60D73 /* Mockable.swift */; };
|
||||
FDC438C127BB4E6800C60D73 /* SMKDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C027BB4E6800C60D73 /* SMKDependencies.swift */; };
|
||||
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C227BB512200C60D73 /* SodiumProtocols.swift */; };
|
||||
FDC438C727BB6DF000C60D73 /* DirectMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C627BB6DF000C60D73 /* DirectMessage.swift */; };
|
||||
FDC438C927BB706500C60D73 /* SendDirectMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */; };
|
||||
FDC438CB27BB7DB100C60D73 /* UpdateMessageRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */; };
|
||||
|
@ -828,6 +836,7 @@
|
|||
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 */; };
|
||||
FDDF074429C3E3D000E5E8B5 /* FetchRequest+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */; };
|
||||
FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */; };
|
||||
FDE658A129418C7900A33BC1 /* CryptoKit+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */; };
|
||||
FDE658A329418E2F00A33BC1 /* KeyPair.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE658A229418E2F00A33BC1 /* KeyPair.swift */; };
|
||||
FDE6E99829F8E63A00F93C5D /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDE6E99729F8E63A00F93C5D /* Accessibility.swift */; };
|
||||
|
@ -862,7 +871,6 @@
|
|||
FDF8488629405A61007DCAE5 /* Request.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488529405A60007DCAE5 /* Request.swift */; };
|
||||
FDF8488829405A9A007DCAE5 /* SOGSBatchRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488729405A9A007DCAE5 /* SOGSBatchRequest.swift */; };
|
||||
FDF8488929405B27007DCAE5 /* Data+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */; };
|
||||
FDF8488B29405BF2007DCAE5 /* SSKDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488A29405BF2007DCAE5 /* SSKDependencies.swift */; };
|
||||
FDF8488E29405C04007DCAE5 /* GetSnodePoolJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8488D29405C04007DCAE5 /* GetSnodePoolJob.swift */; };
|
||||
FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */; };
|
||||
FDF8489429405C1B007DCAE5 /* SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */; };
|
||||
|
@ -924,6 +932,7 @@
|
|||
FDFDE128282D05530098B17F /* MediaPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE127282D05530098B17F /* MediaPresentationContext.swift */; };
|
||||
FDFDE12A282D056B0098B17F /* MediaZoomAnimationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */; };
|
||||
FDFF61D729F2600300F95FB0 /* Identity+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */; };
|
||||
FDFF9FDF2A787F57005E0628 /* JSONEncoder+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */; };
|
||||
FE5FDED6D91BB4B3FA5C104D /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A9C113D2086D3C8A68A371C /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
@ -1465,7 +1474,6 @@
|
|||
C33FDBB4255A581600E217F9 /* NSURLSessionDataTask+StatusCode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLSessionDataTask+StatusCode.m"; sourceTree = "<group>"; };
|
||||
C33FDBB6255A581600E217F9 /* DataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DataSource.m; sourceTree = "<group>"; };
|
||||
C33FDBBC255A581600E217F9 /* SSKKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSKKeychainStorage.swift; sourceTree = "<group>"; };
|
||||
C33FDBC2255A581700E217F9 /* SSKAsserts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SSKAsserts.h; sourceTree = "<group>"; };
|
||||
C33FDBD3255A581800E217F9 /* OWSSignalAddress.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSSignalAddress.swift; sourceTree = "<group>"; };
|
||||
C33FDBD8255A581900E217F9 /* SignalIOS.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SignalIOS.pb.swift; sourceTree = "<group>"; };
|
||||
C33FDBDE255A581900E217F9 /* PushNotificationAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotificationAPI.swift; sourceTree = "<group>"; };
|
||||
|
@ -1640,10 +1648,7 @@
|
|||
FCB11D8B1A129A76002F93FB /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
|
||||
FD078E4727E02561000769AF /* CommonMockedExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonMockedExtensions.swift; sourceTree = "<group>"; };
|
||||
FD078E4C27E17156000769AF /* MockOGMCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockOGMCache.swift; sourceTree = "<group>"; };
|
||||
FD078E4E27E175F1000769AF /* DependencyExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependencyExtensions.swift; sourceTree = "<group>"; };
|
||||
FD078E5127E1760A000769AF /* OGMDependencyExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OGMDependencyExtensions.swift; sourceTree = "<group>"; };
|
||||
FD078E5927E29F09000769AF /* MockNonce16Generator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNonce16Generator.swift; sourceTree = "<group>"; };
|
||||
FD078E5B27E29F78000769AF /* MockNonce24Generator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNonce24Generator.swift; sourceTree = "<group>"; };
|
||||
FD0969F82A69FFE700C5C365 /* Mocked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mocked.swift; sourceTree = "<group>"; };
|
||||
FD09796A27F6C67500936362 /* Failable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Failable.swift; sourceTree = "<group>"; };
|
||||
FD09796D27FA6D0000936362 /* Contact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Contact.swift; sourceTree = "<group>"; };
|
||||
FD09796F27FA6FF300936362 /* Profile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profile.swift; sourceTree = "<group>"; };
|
||||
|
@ -1699,6 +1704,15 @@
|
|||
FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PersistableRecord+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD1A94FD2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistableRecordUtilitiesSpec.swift; sourceTree = "<group>"; };
|
||||
FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD23CE1A2A651E6D0000B97C /* NetworkType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkType.swift; sourceTree = "<group>"; };
|
||||
FD23CE1E2A65269C0000B97C /* Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Crypto.swift; sourceTree = "<group>"; };
|
||||
FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OpenGroupAPI+Crypto.swift"; sourceTree = "<group>"; };
|
||||
FD23CE232A675C440000B97C /* Crypto+SessionMessagingKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Crypto+SessionMessagingKit.swift"; sourceTree = "<group>"; };
|
||||
FD23CE252A676B5B0000B97C /* DependenciesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DependenciesSpec.swift; sourceTree = "<group>"; };
|
||||
FD23CE272A67755C0000B97C /* MockCrypto.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCrypto.swift; sourceTree = "<group>"; };
|
||||
FD23CE2B2A678DF80000B97C /* MockCaches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockCaches.swift; sourceTree = "<group>"; };
|
||||
FD23CE2F2A67B8820000B97C /* Caches.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Caches.swift; sourceTree = "<group>"; };
|
||||
FD23CE312A67C38D0000B97C /* MockNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNetwork.swift; sourceTree = "<group>"; };
|
||||
FD23EA6028ED0B260058676E /* CombineExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineExtensions.swift; sourceTree = "<group>"; };
|
||||
FD245C612850664300B966DD /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = "<group>"; };
|
||||
FD28A4F527EAD44C00FF65E7 /* Storage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storage.swift; sourceTree = "<group>"; };
|
||||
|
@ -1750,9 +1764,8 @@
|
|||
FD3C905B27E3FBEF00CD579F /* BatchRequestInfoSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatchRequestInfoSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C905F27E410F700CD579F /* FileUploadResponseSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileUploadResponseSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C906627E416AF00CD579F /* BlindedIdLookupSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlindedIdLookupSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C906927E417CE00CD579F /* SodiumUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SodiumUtilitiesSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C906927E417CE00CD579F /* CryptoSMKSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoSMKSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C906C27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSenderEncryptionSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C906E27E43E8700CD579F /* MockBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockBox.swift; sourceTree = "<group>"; };
|
||||
FD3C907027E445E500CD579F /* MessageReceiverDecryptionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReceiverDecryptionSpec.swift; sourceTree = "<group>"; };
|
||||
FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupServerIdLookup.swift; sourceTree = "<group>"; };
|
||||
FD3E0C83283B5835002A425C /* SessionThreadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModel.swift; sourceTree = "<group>"; };
|
||||
|
@ -1768,6 +1781,7 @@
|
|||
FD52090628B49738006098F6 /* ConfirmationModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationModal.swift; sourceTree = "<group>"; };
|
||||
FD52090828B59411006098F6 /* ScreenLockUI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockUI.swift; sourceTree = "<group>"; };
|
||||
FD52090A28B59BB4006098F6 /* ScreenLockViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenLockViewController.swift; sourceTree = "<group>"; };
|
||||
FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD5C72F6284F0E560029977D /* MessageReceiver+ReadReceipts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+ReadReceipts.swift"; sourceTree = "<group>"; };
|
||||
FD5C72F8284F0E880029977D /* MessageReceiver+TypingIndicators.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+TypingIndicators.swift"; sourceTree = "<group>"; };
|
||||
FD5C72FA284F0EA10029977D /* MessageReceiver+DataExtractionNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageReceiver+DataExtractionNotification.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1826,7 +1840,6 @@
|
|||
FD7692F62A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionThreadViewModelSpec.swift; sourceTree = "<group>"; };
|
||||
FD7728952849E7E90018502F /* String+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD7728972849E8110018502F /* UITableView+ReusableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+ReusableView.swift"; sourceTree = "<group>"; };
|
||||
FD772899284AF1BD0018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD77289D284EF1C50018502F /* Sodium+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sodium+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD778B6329B189FF001BAC6B /* _014_GenerateInitialUserConfigDumps.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _014_GenerateInitialUserConfigDumps.swift; sourceTree = "<group>"; };
|
||||
FD83B9AF27CF200A005E1583 /* SessionUtilitiesKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SessionUtilitiesKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
@ -1837,6 +1850,7 @@
|
|||
FD83B9C827D0487A005E1583 /* SendDirectMessageResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageResponse.swift; sourceTree = "<group>"; };
|
||||
FD83B9CB27D179BC005E1583 /* FSEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FSEndpoint.swift; sourceTree = "<group>"; };
|
||||
FD83B9D127D59495005E1583 /* MockUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockUserDefaults.swift; sourceTree = "<group>"; };
|
||||
FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryWithDependencies.swift; sourceTree = "<group>"; };
|
||||
FD848B86283B844B000E298B /* MessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewModel.swift; sourceTree = "<group>"; };
|
||||
FD848B8A283DC509000E298B /* PagedDatabaseObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedDatabaseObserver.swift; sourceTree = "<group>"; };
|
||||
FD848B8C283E0B26000E298B /* MessageInputTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageInputTypes.swift; sourceTree = "<group>"; };
|
||||
|
@ -1848,11 +1862,6 @@
|
|||
FD859EEF27BF207700510D0C /* SessionProtos.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = SessionProtos.proto; sourceTree = "<group>"; };
|
||||
FD859EF027BF207C00510D0C /* WebSocketResources.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = WebSocketResources.proto; sourceTree = "<group>"; };
|
||||
FD859EF127BF6BA200510D0C /* Data+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FD859EF327C2F49200510D0C /* MockSodium.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSodium.swift; sourceTree = "<group>"; };
|
||||
FD859EF527C2F52C00510D0C /* MockSign.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSign.swift; sourceTree = "<group>"; };
|
||||
FD859EF727C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockAeadXChaCha20Poly1305Ietf.swift; sourceTree = "<group>"; };
|
||||
FD859EF927C2F5C500510D0C /* MockGenericHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockGenericHash.swift; sourceTree = "<group>"; };
|
||||
FD859EFB27C2F60700510D0C /* MockEd25519.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockEd25519.swift; sourceTree = "<group>"; };
|
||||
FD87DCF928B74DB300AF0F98 /* ConversationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
FD87DCFD28B7582C00AF0F98 /* BlockedContactsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactsViewModel.swift; sourceTree = "<group>"; };
|
||||
FD87DCFF28B820F200AF0F98 /* BlockedContactCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedContactCell.swift; sourceTree = "<group>"; };
|
||||
|
@ -1867,10 +1876,13 @@
|
|||
FD8ECF912938552800C0D1BB /* Threading.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Threading.swift; sourceTree = "<group>"; };
|
||||
FD8ECF93293856AF00C0D1BB /* Randomness.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Randomness.swift; sourceTree = "<group>"; };
|
||||
FD9004132818AD0B00ABAAF6 /* _002_SetupStandardJobs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _002_SetupStandardJobs.swift; sourceTree = "<group>"; };
|
||||
FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSendJobSpec.swift; sourceTree = "<group>"; };
|
||||
FD96F3A629DBD43D00401309 /* MockJobRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockJobRunner.swift; sourceTree = "<group>"; };
|
||||
FD97B23F2A3FEB050027DD57 /* ARC4RandomNumberGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ARC4RandomNumberGenerator.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>"; };
|
||||
FD9BDDF82A5D2294005F1EBC /* libSessionUtil.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSessionUtil.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
FD9DD2702A72516D00ECB68E /* TestExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestExtensions.swift; sourceTree = "<group>"; };
|
||||
FDA1E83529A5748F00C5C3BD /* ConfigUserGroupsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigUserGroupsSpec.swift; sourceTree = "<group>"; };
|
||||
FDA1E83829A5771A00C5C3BD /* LibSessionSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibSessionSpec.swift; sourceTree = "<group>"; };
|
||||
FDA1E83A29A5F2D500C5C3BD /* SessionUtil+Shared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionUtil+Shared.swift"; sourceTree = "<group>"; };
|
||||
|
@ -1904,13 +1916,10 @@
|
|||
FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSErrorSpec.swift; sourceTree = "<group>"; };
|
||||
FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersonalizationSpec.swift; sourceTree = "<group>"; };
|
||||
FDC2909927D71376005DAE71 /* NonceGeneratorSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceGeneratorSpec.swift; sourceTree = "<group>"; };
|
||||
FDC2909B27D713D2005DAE71 /* SodiumProtocolsSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SodiumProtocolsSpec.swift; sourceTree = "<group>"; };
|
||||
FDC2909D27D85751005DAE71 /* OpenGroupManagerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupManagerSpec.swift; sourceTree = "<group>"; };
|
||||
FDC290A527D860CE005DAE71 /* Mock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mock.swift; sourceTree = "<group>"; };
|
||||
FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NimbleExtensions.swift; sourceTree = "<group>"; };
|
||||
FDC290B227DFF9F5005DAE71 /* TestOnionRequestAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestOnionRequestAPI.swift; sourceTree = "<group>"; };
|
||||
FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenGroupAPIError.swift; sourceTree = "<group>"; };
|
||||
FDC4381427B329CE00C60D73 /* NonceGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonceGenerator.swift; sourceTree = "<group>"; };
|
||||
FDC4381627B32EC700C60D73 /* Personalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Personalization.swift; sourceTree = "<group>"; };
|
||||
FDC4381F27B36ADC00C60D73 /* SOGSEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SOGSEndpoint.swift; sourceTree = "<group>"; };
|
||||
FDC4382E27B383AF00C60D73 /* LegacyPushServerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyPushServerResponse.swift; sourceTree = "<group>"; };
|
||||
|
@ -1932,8 +1941,6 @@
|
|||
FDC438B027BB159600C60D73 /* RequestInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestInfo.swift; sourceTree = "<group>"; };
|
||||
FDC438B227BB15B400C60D73 /* ResponseInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseInfo.swift; sourceTree = "<group>"; };
|
||||
FDC438BC27BB2AB400C60D73 /* Mockable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mockable.swift; sourceTree = "<group>"; };
|
||||
FDC438C027BB4E6800C60D73 /* SMKDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMKDependencies.swift; sourceTree = "<group>"; };
|
||||
FDC438C227BB512200C60D73 /* SodiumProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SodiumProtocols.swift; sourceTree = "<group>"; };
|
||||
FDC438C627BB6DF000C60D73 /* DirectMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectMessage.swift; sourceTree = "<group>"; };
|
||||
FDC438C827BB706500C60D73 /* SendDirectMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendDirectMessageRequest.swift; sourceTree = "<group>"; };
|
||||
FDC438CA27BB7DB100C60D73 /* UpdateMessageRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateMessageRequest.swift; sourceTree = "<group>"; };
|
||||
|
@ -1954,6 +1961,7 @@
|
|||
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>"; };
|
||||
FDDF074329C3E3D000E5E8B5 /* FetchRequest+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FetchRequest+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JobRunnerSpec.swift; sourceTree = "<group>"; };
|
||||
FDE658A029418C7900A33BC1 /* CryptoKit+Utilities.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CryptoKit+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FDE658A229418E2F00A33BC1 /* KeyPair.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyPair.swift; sourceTree = "<group>"; };
|
||||
FDE6E99729F8E63A00F93C5D /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = "<group>"; };
|
||||
|
@ -1991,7 +1999,6 @@
|
|||
FDF8488229405A12007DCAE5 /* BatchResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BatchResponse.swift; sourceTree = "<group>"; };
|
||||
FDF8488529405A60007DCAE5 /* Request.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Request.swift; sourceTree = "<group>"; };
|
||||
FDF8488729405A9A007DCAE5 /* SOGSBatchRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SOGSBatchRequest.swift; sourceTree = "<group>"; };
|
||||
FDF8488A29405BF2007DCAE5 /* SSKDependencies.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SSKDependencies.swift; sourceTree = "<group>"; };
|
||||
FDF8488D29405C04007DCAE5 /* GetSnodePoolJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetSnodePoolJob.swift; sourceTree = "<group>"; };
|
||||
FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPINamespace.swift; sourceTree = "<group>"; };
|
||||
FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SnodeAPI.swift; sourceTree = "<group>"; };
|
||||
|
@ -2053,6 +2060,7 @@
|
|||
FDFDE127282D05530098B17F /* MediaPresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPresentationContext.swift; sourceTree = "<group>"; };
|
||||
FDFDE129282D056B0098B17F /* MediaZoomAnimationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaZoomAnimationController.swift; sourceTree = "<group>"; };
|
||||
FDFF61D629F2600300F95FB0 /* Identity+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identity+Utilities.swift"; sourceTree = "<group>"; };
|
||||
FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONEncoder+Utilities.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -2583,6 +2591,7 @@
|
|||
FDC438B227BB15B400C60D73 /* ResponseInfo.swift */,
|
||||
C33FDAF2255A580500E217F9 /* ProxiedContentDownloader.swift */,
|
||||
C3C2A5BC255385EE00C340D1 /* HTTP.swift */,
|
||||
FD23CE1A2A651E6D0000B97C /* NetworkType.swift */,
|
||||
FDF848EE294067E4007DCAE5 /* URLResponse+Utilities.swift */,
|
||||
);
|
||||
path = Networking;
|
||||
|
@ -2610,14 +2619,15 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
7BD477A727EC39F5004E2822 /* Atomic.swift */,
|
||||
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */,
|
||||
7BAF54D527ACD0E2003D12F8 /* ReusableView.swift */,
|
||||
C33FDB8A255A581200E217F9 /* AppContext.h */,
|
||||
C33FDB85255A581100E217F9 /* AppContext.m */,
|
||||
C3C2A5D12553860800C340D1 /* Array+Utilities.swift */,
|
||||
FDC4383D27B4708600C60D73 /* Atomic.swift */,
|
||||
FDC438CC27BC641200C60D73 /* Set+Utilities.swift */,
|
||||
C33FDAA8255A57FF00E217F9 /* BuildConfiguration.swift */,
|
||||
B8F5F58225EC94A6003BF8D4 /* Collection+Utilities.swift */,
|
||||
FD23CE2F2A67B8820000B97C /* Caches.swift */,
|
||||
FDFD645A27F26D4600808CA1 /* Data+Utilities.swift */,
|
||||
FDC6D75F2862B3F600B04575 /* Dependencies.swift */,
|
||||
C3C2A5D52553860A00C340D1 /* Dictionary+Utilities.swift */,
|
||||
|
@ -3208,6 +3218,7 @@
|
|||
C3A721332558BDDF0043A11F /* Open Groups */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD23CE202A661CE80000B97C /* Crypto */,
|
||||
FDC4381827B34EAD00C60D73 /* Models */,
|
||||
FDC4380727B31D3A00C60D73 /* Types */,
|
||||
FD3C907427E83AC200CD579F /* OpenGroupServerIdLookup.swift */,
|
||||
|
@ -3232,9 +3243,10 @@
|
|||
children = (
|
||||
C33FDB01255A580700E217F9 /* AppReadiness.h */,
|
||||
C33FDB75255A581000E217F9 /* AppReadiness.m */,
|
||||
FDF0B7542807C4BB004C14C5 /* Environment.swift */,
|
||||
FD23CE232A675C440000B97C /* Crypto+SessionMessagingKit.swift */,
|
||||
FD859EF127BF6BA200510D0C /* Data+Utilities.swift */,
|
||||
C38EF309255B6DBE007E1867 /* DeviceSleepManager.swift */,
|
||||
FDF0B7542807C4BB004C14C5 /* Environment.swift */,
|
||||
C3BBE0C62554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift */,
|
||||
C3A71D0A2558989C0043A11F /* MessageWrapper.swift */,
|
||||
C38EF2F5255B6DBC007E1867 /* OWSAudioPlayer.h */,
|
||||
|
@ -3248,7 +3260,6 @@
|
|||
C38EF2EC255B6DBA007E1867 /* ProximityMonitoringManager.swift */,
|
||||
C38EEF09255B49A8007E1867 /* SNProtoEnvelope+Conversion.swift */,
|
||||
FDC4386827B4E6B700C60D73 /* String+Utlities.swift */,
|
||||
FD772899284AF1BD0018502F /* Sodium+Utilities.swift */,
|
||||
FD16AB602A1DD9B60083D849 /* ProfilePictureView+Convenience.swift */,
|
||||
C3A3A170256E1D25004D228D /* SSKReachabilityManager.swift */,
|
||||
C3ECBF7A257056B700EA7FCE /* Threading.swift */,
|
||||
|
@ -3267,7 +3278,6 @@
|
|||
FDF8488C29405C04007DCAE5 /* Jobs */,
|
||||
FDF8489229405C1B007DCAE5 /* Networking */,
|
||||
C3C2A5CD255385F300C340D1 /* Utilities */,
|
||||
FDF8488A29405BF2007DCAE5 /* SSKDependencies.swift */,
|
||||
C3C2A5B9255385ED00C340D1 /* Configuration.swift */,
|
||||
);
|
||||
path = SessionSnodeKit;
|
||||
|
@ -3334,7 +3344,6 @@
|
|||
FD8ECF7529340F4800C0D1BB /* SessionUtil */,
|
||||
FD3E0C82283B581F002A425C /* Shared Models */,
|
||||
C3BBE0B32554F0D30050F1E3 /* Utilities */,
|
||||
FDC438C027BB4E6800C60D73 /* SMKDependencies.swift */,
|
||||
FD245C612850664300B966DD /* Configuration.swift */,
|
||||
);
|
||||
path = SessionMessagingKit;
|
||||
|
@ -3414,7 +3423,6 @@
|
|||
C33FDB8F255A581200E217F9 /* ParamParser.swift */,
|
||||
C33FDADE255A580400E217F9 /* SwiftSingletons.swift */,
|
||||
C33FDB49255A580C00E217F9 /* WeakTimer.swift */,
|
||||
C33FDBC2255A581700E217F9 /* SSKAsserts.h */,
|
||||
C33FDA9E255A57FF00E217F9 /* ReverseDispatchQueue.swift */,
|
||||
C33FDBF6255A581C00E217F9 /* NSURLSessionDataTask+StatusCode.h */,
|
||||
C33FDBB4255A581600E217F9 /* NSURLSessionDataTask+StatusCode.m */,
|
||||
|
@ -3582,7 +3590,10 @@
|
|||
FDA8EB0F280F8238002B68E5 /* Codable+Utilities.swift */,
|
||||
FD3003692A3ADD6000B5A5FB /* CExceptionHelper.h */,
|
||||
FD30036D2A3AE26000B5A5FB /* CExceptionHelper.mm */,
|
||||
FD23CE1E2A65269C0000B97C /* Crypto.swift */,
|
||||
FD559DF42A7368CB00C7C62A /* DispatchQueue+Utilities.swift */,
|
||||
FD09796A27F6C67500936362 /* Failable.swift */,
|
||||
FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */,
|
||||
FD09797127FAA2F500936362 /* Optional+Utilities.swift */,
|
||||
FD09797C27FBDB2000936362 /* Notification+Utilities.swift */,
|
||||
FDF222082818D2B0000A4995 /* NSAttributedString+Utilities.swift */,
|
||||
|
@ -3766,6 +3777,14 @@
|
|||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD23CE202A661CE80000B97C /* Crypto */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD23CE212A661D000000B97C /* OpenGroupAPI+Crypto.swift */,
|
||||
);
|
||||
path = Crypto;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD29598E2A43BE5400888A17 /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -3868,7 +3887,7 @@
|
|||
FD3C906827E417B100CD579F /* Utilities */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD3C906927E417CE00CD579F /* SodiumUtilitiesSpec.swift */,
|
||||
FD3C906927E417CE00CD579F /* CryptoSMKSpec.swift */,
|
||||
);
|
||||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
|
@ -3906,6 +3925,7 @@
|
|||
children = (
|
||||
FD7115F628C8150D00B47552 /* Disposable Views */,
|
||||
FD7115FD28C8202D00B47552 /* ReplaySubject.swift */,
|
||||
FD83DCDC2A739D350065FFAE /* RetryWithDependencies.swift */,
|
||||
FD7115FB28C8155800B47552 /* Publisher+Utilities.swift */,
|
||||
FD71160128C8255900B47552 /* UIControl+Combine.swift */,
|
||||
FD7115F928C8153400B47552 /* UIBarButtonItem+Combine.swift */,
|
||||
|
@ -4030,6 +4050,7 @@
|
|||
children = (
|
||||
FD37EA1228AB3F60003AE748 /* Database */,
|
||||
FD83B9B927CF20A5005E1583 /* General */,
|
||||
FDDF074829DAB35200E5E8B5 /* JobRunner */,
|
||||
FD9B30F1293EA0AF008DEE3E /* Networking */,
|
||||
FDFBB7522A2023DE00CA7350 /* Utilities */,
|
||||
FD29598E2A43BE5400888A17 /* Utilities */,
|
||||
|
@ -4041,6 +4062,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
FD0B77B129B82B7A009169BA /* ArrayUtilitiesSpec.swift */,
|
||||
FD23CE252A676B5B0000B97C /* DependenciesSpec.swift */,
|
||||
FD83B9BA27CF20AF005E1583 /* SessionIdSpec.swift */,
|
||||
);
|
||||
path = General;
|
||||
|
@ -4050,8 +4072,14 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
FDC290A527D860CE005DAE71 /* Mock.swift */,
|
||||
FD0969F82A69FFE700C5C365 /* Mocked.swift */,
|
||||
FD23CE272A67755C0000B97C /* MockCrypto.swift */,
|
||||
FD23CE2B2A678DF80000B97C /* MockCaches.swift */,
|
||||
FDFD645C27F273F300808CA1 /* MockGeneralCache.swift */,
|
||||
FD23CE312A67C38D0000B97C /* MockNetwork.swift */,
|
||||
FD96F3A629DBD43D00401309 /* MockJobRunner.swift */,
|
||||
FD83B9BD27CF2243005E1583 /* TestConstants.swift */,
|
||||
FD9DD2702A72516D00ECB68E /* TestExtensions.swift */,
|
||||
FDC290A727D9B46D005DAE71 /* NimbleExtensions.swift */,
|
||||
FD078E4727E02561000769AF /* CommonMockedExtensions.swift */,
|
||||
FD23EA6028ED0B260058676E /* CombineExtensions.swift */,
|
||||
|
@ -4128,6 +4156,22 @@
|
|||
path = JobRunner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD96F3A229DBC3BA00401309 /* Jobs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD96F3A329DBC3D000401309 /* Types */,
|
||||
);
|
||||
path = Jobs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD96F3A329DBC3D000401309 /* Types */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FD96F3A429DBC3DC00401309 /* MessageSendJobSpec.swift */,
|
||||
);
|
||||
path = Types;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FD9B30F1293EA0AF008DEE3E /* Networking */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -4164,7 +4208,6 @@
|
|||
FDC2909727D7129B005DAE71 /* PersonalizationSpec.swift */,
|
||||
FDC2909327D710B4005DAE71 /* SOGSEndpointSpec.swift */,
|
||||
FDC2909527D71252005DAE71 /* SOGSErrorSpec.swift */,
|
||||
FDC2909B27D713D2005DAE71 /* SodiumProtocolsSpec.swift */,
|
||||
);
|
||||
path = Types;
|
||||
sourceTree = "<group>";
|
||||
|
@ -4178,8 +4221,6 @@
|
|||
FDC4380827B31D4E00C60D73 /* OpenGroupAPIError.swift */,
|
||||
FDC4381627B32EC700C60D73 /* Personalization.swift */,
|
||||
FD2959912A4417A900888A17 /* PreparedSendData.swift */,
|
||||
FDC4381427B329CE00C60D73 /* NonceGenerator.swift */,
|
||||
FDC438C227BB512200C60D73 /* SodiumProtocols.swift */,
|
||||
);
|
||||
path = Types;
|
||||
sourceTree = "<group>";
|
||||
|
@ -4256,6 +4297,7 @@
|
|||
FDC4389B27BA01E300C60D73 /* _TestUtilities */,
|
||||
FD3C905D27E410DB00CD579F /* Common Networking */,
|
||||
FD3C906527E416A200CD579F /* Contacts */,
|
||||
FD96F3A229DBC3BA00401309 /* Jobs */,
|
||||
FDC4389827BA001800C60D73 /* Open Groups */,
|
||||
FD3C906B27E43C2400CD579F /* Sending & Receiving */,
|
||||
FD7692F52A53A2C7000E4B70 /* Shared Models */,
|
||||
|
@ -4280,19 +4322,8 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
FDC438BC27BB2AB400C60D73 /* Mockable.swift */,
|
||||
FD859EF327C2F49200510D0C /* MockSodium.swift */,
|
||||
FD3C906E27E43E8700CD579F /* MockBox.swift */,
|
||||
FD859EF927C2F5C500510D0C /* MockGenericHash.swift */,
|
||||
FD859EF527C2F52C00510D0C /* MockSign.swift */,
|
||||
FD859EF727C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift */,
|
||||
FD859EFB27C2F60700510D0C /* MockEd25519.swift */,
|
||||
FD078E5927E29F09000769AF /* MockNonce16Generator.swift */,
|
||||
FD078E5B27E29F78000769AF /* MockNonce24Generator.swift */,
|
||||
FD83B9D127D59495005E1583 /* MockUserDefaults.swift */,
|
||||
FD078E4C27E17156000769AF /* MockOGMCache.swift */,
|
||||
FDC290B227DFF9F5005DAE71 /* TestOnionRequestAPI.swift */,
|
||||
FD078E4E27E175F1000769AF /* DependencyExtensions.swift */,
|
||||
FD078E5127E1760A000769AF /* OGMDependencyExtensions.swift */,
|
||||
);
|
||||
path = _TestUtilities;
|
||||
sourceTree = "<group>";
|
||||
|
@ -4305,6 +4336,14 @@
|
|||
path = Utilities;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FDDF074829DAB35200E5E8B5 /* JobRunner */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FDDF074929DAB36900E5E8B5 /* JobRunnerSpec.swift */,
|
||||
);
|
||||
path = JobRunner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FDE7214E287E50D50093DF33 /* Scripts */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -4459,7 +4498,6 @@
|
|||
C33FDDB0255A582000E217F9 /* NSURLSessionDataTask+StatusCode.h in Headers */,
|
||||
C33FDDD0255A582000E217F9 /* FunctionalUtil.h in Headers */,
|
||||
C33FDD5B255A582000E217F9 /* OWSOperation.h in Headers */,
|
||||
C33FDD7C255A582000E217F9 /* SSKAsserts.h in Headers */,
|
||||
C33FDDCC255A582000E217F9 /* TSConstants.h in Headers */,
|
||||
C33FDDBD255A582000E217F9 /* ByteParser.h in Headers */,
|
||||
C33FDDB3255A582000E217F9 /* OWSError.h in Headers */,
|
||||
|
@ -4661,9 +4699,9 @@
|
|||
D221A085169C9E5E00537ABF /* Sources */,
|
||||
D221A086169C9E5E00537ABF /* Frameworks */,
|
||||
D221A087169C9E5E00537ABF /* Resources */,
|
||||
FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */,
|
||||
453518771FC635DD00210559 /* Embed Foundation Extensions */,
|
||||
4535189F1FC63DBF00210559 /* Embed Frameworks */,
|
||||
FDD82C422A2085B900425F05 /* Add Commit Hash To Build Info Plist */,
|
||||
90DF4725BB1271EBA2C66A12 /* [CP] Embed Pods Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
|
@ -5587,7 +5625,6 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FDF8488B29405BF2007DCAE5 /* SSKDependencies.swift in Sources */,
|
||||
FDF8488E29405C04007DCAE5 /* GetSnodePoolJob.swift in Sources */,
|
||||
FDF848C329405C5A007DCAE5 /* DeleteMessagesRequest.swift in Sources */,
|
||||
FDF8489129405C13007DCAE5 /* SnodeAPINamespace.swift in Sources */,
|
||||
|
@ -5659,10 +5696,13 @@
|
|||
FDFD645927F26C6800808CA1 /* Array+Utilities.swift in Sources */,
|
||||
7B1D74B027C365960030B423 /* Timer+MainThread.swift in Sources */,
|
||||
C32C5D83256DD5B6003C73A2 /* SSKKeychainStorage.swift in Sources */,
|
||||
FD559DF52A7368CB00C7C62A /* DispatchQueue+Utilities.swift in Sources */,
|
||||
FDF8488329405A12007DCAE5 /* BatchResponse.swift in Sources */,
|
||||
C3D9E39B256763C20040E4F3 /* AppContext.m in Sources */,
|
||||
FDFF9FDF2A787F57005E0628 /* JSONEncoder+Utilities.swift in Sources */,
|
||||
C3BBE0A82554D4DE0050F1E3 /* JSON.swift in Sources */,
|
||||
FD17D7C127F5200100122BE0 /* TypedTableDefinition.swift in Sources */,
|
||||
FD23CE1B2A651E6D0000B97C /* NetworkType.swift in Sources */,
|
||||
FD17D7EA27F6A1C600122BE0 /* SUKLegacy.swift in Sources */,
|
||||
FDA8EB10280F8238002B68E5 /* Codable+Utilities.swift in Sources */,
|
||||
FDBB25E32988B13800F1508E /* _004_AddJobPriority.swift in Sources */,
|
||||
|
@ -5721,8 +5761,10 @@
|
|||
C3A71F892558BA9F0043A11F /* Mnemonic.swift in Sources */,
|
||||
B8F5F58325EC94A6003BF8D4 /* Collection+Utilities.swift in Sources */,
|
||||
7BD477A827EC39F5004E2822 /* Atomic.swift in Sources */,
|
||||
FD23CE1F2A65269C0000B97C /* Crypto.swift in Sources */,
|
||||
B8BC00C0257D90E30032E807 /* General.swift in Sources */,
|
||||
FDF8488629405A61007DCAE5 /* Request.swift in Sources */,
|
||||
FD23CE302A67B8820000B97C /* Caches.swift in Sources */,
|
||||
FD17D7A127F40D2500122BE0 /* Storage.swift in Sources */,
|
||||
FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */,
|
||||
FD5D201E27B0D87C00FEA984 /* SessionId.swift in Sources */,
|
||||
|
@ -5732,6 +5774,7 @@
|
|||
FDF22211281B5E0B000A4995 /* TableRecord+Utilities.swift in Sources */,
|
||||
B88FA7FB26114EA70049422F /* Hex.swift in Sources */,
|
||||
FD7728962849E7E90018502F /* String+Utilities.swift in Sources */,
|
||||
FD83DCDD2A739D350065FFAE /* RetryWithDependencies.swift in Sources */,
|
||||
C35D0DB525AE5F1200B6BF49 /* UIEdgeInsets.swift in Sources */,
|
||||
C3D9E4F4256778AF0040E4F3 /* NSData+Image.m in Sources */,
|
||||
FDF222092818D2B0000A4995 /* NSAttributedString+Utilities.swift in Sources */,
|
||||
|
@ -5785,7 +5828,6 @@
|
|||
FDF0B74928060D13004C14C5 /* QuotedReplyModel.swift in Sources */,
|
||||
FD3003662A25D5B300B5A5FB /* ConfigMessageReceiveJob.swift in Sources */,
|
||||
7B81682C28B72F480069F315 /* PendingChange.swift in Sources */,
|
||||
FD77289A284AF1BD0018502F /* Sodium+Utilities.swift in Sources */,
|
||||
FD5C7309285007920029977D /* BlindedIdLookup.swift in Sources */,
|
||||
FD71161C28D194FB00B47552 /* MentionInfo.swift in Sources */,
|
||||
7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */,
|
||||
|
@ -5892,9 +5934,9 @@
|
|||
FD432434299C6985008A0213 /* PendingReadReceipt.swift in Sources */,
|
||||
FDC4381727B32EC700C60D73 /* Personalization.swift in Sources */,
|
||||
FD245C51285065CC00B966DD /* MessageReceiver.swift in Sources */,
|
||||
FD23CE222A661D000000B97C /* OpenGroupAPI+Crypto.swift in Sources */,
|
||||
FD245C652850665400B966DD /* ClosedGroupControlMessage.swift in Sources */,
|
||||
FDC4387827B5C35400C60D73 /* SendMessageRequest.swift in Sources */,
|
||||
FDC4381527B329CE00C60D73 /* NonceGenerator.swift in Sources */,
|
||||
7B93D07027CF194000811CB6 /* ConfigurationMessage+Convenience.swift in Sources */,
|
||||
FD5C72FD284F0EC90029977D /* MessageReceiver+ExpirationTimers.swift in Sources */,
|
||||
C32C5A88256DBCF9003C73A2 /* MessageReceiver+ClosedGroups.swift in Sources */,
|
||||
|
@ -5925,7 +5967,7 @@
|
|||
FDA1E83B29A5F2D500C5C3BD /* SessionUtil+Shared.swift in Sources */,
|
||||
C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */,
|
||||
FD16AB612A1DD9B60083D849 /* ProfilePictureView+Convenience.swift in Sources */,
|
||||
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */,
|
||||
FD23CE242A675C440000B97C /* Crypto+SessionMessagingKit.swift in Sources */,
|
||||
B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */,
|
||||
C3DB66C3260ACCE6001EFC55 /* OpenGroupPoller.swift in Sources */,
|
||||
FD716E722850647600C96BF4 /* Data+Utilities.swift in Sources */,
|
||||
|
@ -5958,7 +6000,6 @@
|
|||
FD09798127FCFEE800936362 /* SessionThread.swift in Sources */,
|
||||
FD09C5EA282A1BB2000CE219 /* ThreadTypingIndicator.swift in Sources */,
|
||||
FDF0B75E280AAF35004C14C5 /* Preferences.swift in Sources */,
|
||||
FDC438C127BB4E6800C60D73 /* SMKDependencies.swift in Sources */,
|
||||
FDC4383827B3863200C60D73 /* VersionResponse.swift in Sources */,
|
||||
B806ECA126C4A7E4008BDA44 /* WebRTCSession+UI.swift in Sources */,
|
||||
FD432432299C6933008A0213 /* _011_AddPendingReadReceipts.swift in Sources */,
|
||||
|
@ -6184,13 +6225,18 @@
|
|||
files = (
|
||||
FD71161728D00DA400B47552 /* ThreadSettingsViewModelSpec.swift in Sources */,
|
||||
FD2AAAF028ED57B500A49611 /* SynchronousStorage.swift in Sources */,
|
||||
FD23CE292A6775650000B97C /* MockCrypto.swift in Sources */,
|
||||
FD23CE332A67C4D90000B97C /* MockNetwork.swift in Sources */,
|
||||
FD71161528D00D6700B47552 /* ThreadDisappearingMessagesViewModelSpec.swift in Sources */,
|
||||
FD23CE2D2A678E1E0000B97C /* MockCaches.swift in Sources */,
|
||||
FD23EA5E28ED00FD0058676E /* NimbleExtensions.swift in Sources */,
|
||||
FD23EA5F28ED00FF0058676E /* CommonMockedExtensions.swift in Sources */,
|
||||
FD23EA5D28ED00FA0058676E /* TestConstants.swift in Sources */,
|
||||
FD71161A28D00E1100B47552 /* NotificationContentViewModelSpec.swift in Sources */,
|
||||
FD0969FA2A6A00B000C5C365 /* Mocked.swift in Sources */,
|
||||
FD23EA5C28ED00F80058676E /* Mock.swift in Sources */,
|
||||
FD23EA6128ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||
FD9DD2712A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||
FD2AAAED28ED3E1000A49611 /* MockGeneralCache.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -6203,14 +6249,21 @@
|
|||
FD078E4927E02576000769AF /* CommonMockedExtensions.swift in Sources */,
|
||||
FD83B9BF27CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||
FDFBB7542A2023EB00CA7350 /* BencodeSpec.swift in Sources */,
|
||||
FD9DD2732A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||
FD83B9BB27CF20AF005E1583 /* SessionIdSpec.swift in Sources */,
|
||||
FDC290A927D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||
FD23EA6328ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||
FD23CE352A67C4DA0000B97C /* MockNetwork.swift in Sources */,
|
||||
FD23CE282A67755C0000B97C /* MockCrypto.swift in Sources */,
|
||||
FD2AAAEE28ED3E1100A49611 /* MockGeneralCache.swift in Sources */,
|
||||
FD9B30F3293EA0BF008DEE3E /* BatchResponseSpec.swift in Sources */,
|
||||
FD37EA1528AB42CB003AE748 /* IdentitySpec.swift in Sources */,
|
||||
FD23CE2C2A678DF80000B97C /* MockCaches.swift in Sources */,
|
||||
FD0B77B229B82B7A009169BA /* ArrayUtilitiesSpec.swift in Sources */,
|
||||
FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */,
|
||||
FD23CE262A676B5B0000B97C /* DependenciesSpec.swift in Sources */,
|
||||
FDDF074A29DAB36900E5E8B5 /* JobRunnerSpec.swift in Sources */,
|
||||
FD0969FB2A6A00B100C5C365 /* Mocked.swift in Sources */,
|
||||
FDC290AA27D9B6FD005DAE71 /* Mock.swift in Sources */,
|
||||
FD2959902A43BE5F00888A17 /* VersionSpec.swift in Sources */,
|
||||
);
|
||||
|
@ -6222,54 +6275,49 @@
|
|||
files = (
|
||||
FD3C905C27E3FBEF00CD579F /* BatchRequestInfoSpec.swift in Sources */,
|
||||
FDDC08F229A300E800BF9681 /* LibSessionTypeConversionUtilitiesSpec.swift in Sources */,
|
||||
FD859EFA27C2F5C500510D0C /* MockGenericHash.swift in Sources */,
|
||||
FDC2909427D710B4005DAE71 /* SOGSEndpointSpec.swift in Sources */,
|
||||
FDC290B327DFF9F5005DAE71 /* TestOnionRequestAPI.swift in Sources */,
|
||||
FD96F3A529DBC3DC00401309 /* MessageSendJobSpec.swift in Sources */,
|
||||
FDC2909127D709CA005DAE71 /* SOGSMessageSpec.swift in Sources */,
|
||||
FD3C906A27E417CE00CD579F /* SodiumUtilitiesSpec.swift in Sources */,
|
||||
FD3C906A27E417CE00CD579F /* CryptoSMKSpec.swift in Sources */,
|
||||
FD96F3A729DBD43D00401309 /* MockJobRunner.swift in Sources */,
|
||||
FDBB25E12983909300F1508E /* ConfigConvoInfoVolatileSpec.swift in Sources */,
|
||||
FD3C907127E445E500CD579F /* MessageReceiverDecryptionSpec.swift in Sources */,
|
||||
FDC2909627D71252005DAE71 /* SOGSErrorSpec.swift in Sources */,
|
||||
FDC2908727D7047F005DAE71 /* RoomSpec.swift in Sources */,
|
||||
FD078E4F27E175F1000769AF /* DependencyExtensions.swift in Sources */,
|
||||
FDC2909C27D713D2005DAE71 /* SodiumProtocolsSpec.swift in Sources */,
|
||||
FD3C906027E410F700CD579F /* FileUploadResponseSpec.swift in Sources */,
|
||||
FD83B9C727CF3F10005E1583 /* CapabilitiesSpec.swift in Sources */,
|
||||
FDC2909A27D71376005DAE71 /* NonceGeneratorSpec.swift in Sources */,
|
||||
FD2AAAF128ED57B500A49611 /* SynchronousStorage.swift in Sources */,
|
||||
FD078E4827E02561000769AF /* CommonMockedExtensions.swift in Sources */,
|
||||
FD859EF827C2F58900510D0C /* MockAeadXChaCha20Poly1305Ietf.swift in Sources */,
|
||||
FD23CE2A2A6775660000B97C /* MockCrypto.swift in Sources */,
|
||||
FDC2909827D7129B005DAE71 /* PersonalizationSpec.swift in Sources */,
|
||||
FD859EF427C2F49200510D0C /* MockSodium.swift in Sources */,
|
||||
FD078E4D27E17156000769AF /* MockOGMCache.swift in Sources */,
|
||||
FD078E5227E1760A000769AF /* OGMDependencyExtensions.swift in Sources */,
|
||||
FD9DD2722A72516D00ECB68E /* TestExtensions.swift in Sources */,
|
||||
FDA1E83629A5748F00C5C3BD /* ConfigUserGroupsSpec.swift in Sources */,
|
||||
FD859EFC27C2F60700510D0C /* MockEd25519.swift in Sources */,
|
||||
FDC290A627D860CE005DAE71 /* Mock.swift in Sources */,
|
||||
FD2B4AFB29429D1000AB4848 /* ConfigContactsSpec.swift in Sources */,
|
||||
FDA1E83D29AC71A800C5C3BD /* SessionUtilSpec.swift in Sources */,
|
||||
FD83B9C027CF2294005E1583 /* TestConstants.swift in Sources */,
|
||||
FD3C906F27E43E8700CD579F /* MockBox.swift in Sources */,
|
||||
FDC4389A27BA002500C60D73 /* OpenGroupAPISpec.swift in Sources */,
|
||||
FD23CE2E2A678E1E0000B97C /* MockCaches.swift in Sources */,
|
||||
FD23EA6228ED0B260058676E /* CombineExtensions.swift in Sources */,
|
||||
FD83B9C527CF3E2A005E1583 /* OpenGroupSpec.swift in Sources */,
|
||||
FD23CE342A67C4D90000B97C /* MockNetwork.swift in Sources */,
|
||||
FDC2908B27D707F3005DAE71 /* SendMessageRequestSpec.swift in Sources */,
|
||||
FD8ECF822934387A00C0D1BB /* ConfigUserProfileSpec.swift in Sources */,
|
||||
FDC290A827D9B46D005DAE71 /* NimbleExtensions.swift in Sources */,
|
||||
FD7692F72A53A2ED000E4B70 /* SessionThreadViewModelSpec.swift in Sources */,
|
||||
FD0969F92A69FFE700C5C365 /* Mocked.swift in Sources */,
|
||||
FDC2908F27D70938005DAE71 /* SendDirectMessageRequestSpec.swift in Sources */,
|
||||
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */,
|
||||
FDA1E83929A5771A00C5C3BD /* LibSessionSpec.swift in Sources */,
|
||||
FD859EF627C2F52C00510D0C /* MockSign.swift in Sources */,
|
||||
FDC2908927D70656005DAE71 /* RoomPollInfoSpec.swift in Sources */,
|
||||
FDFD645D27F273F300808CA1 /* MockGeneralCache.swift in Sources */,
|
||||
FD078E5A27E29F09000769AF /* MockNonce16Generator.swift in Sources */,
|
||||
FD3C906D27E43C4B00CD579F /* MessageSenderEncryptionSpec.swift in Sources */,
|
||||
FDC2908D27D70905005DAE71 /* UpdateMessageRequestSpec.swift in Sources */,
|
||||
FD078E5427E197CA000769AF /* OpenGroupManagerSpec.swift in Sources */,
|
||||
FD3C906727E416AF00CD579F /* BlindedIdLookupSpec.swift in Sources */,
|
||||
FD83B9D227D59495005E1583 /* MockUserDefaults.swift in Sources */,
|
||||
FD078E5C27E29F78000769AF /* MockNonce24Generator.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -35,15 +35,15 @@ extension ContextMenuVC {
|
|||
|
||||
// MARK: - Actions
|
||||
|
||||
static func info(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func info(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_info"),
|
||||
title: "context_menu_info".localized(),
|
||||
accessibilityLabel: "Message info"
|
||||
) { delegate?.info(cellViewModel) }
|
||||
) { delegate?.info(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func retry(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func retry(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(systemName: "arrow.triangle.2.circlepath"),
|
||||
title: (cellViewModel.state == .failedToSync ?
|
||||
|
@ -51,23 +51,23 @@ extension ContextMenuVC {
|
|||
"context_menu_resend".localized()
|
||||
),
|
||||
accessibilityLabel: (cellViewModel.state == .failedToSync ? "Resync message" : "Resend message")
|
||||
) { delegate?.retry(cellViewModel) }
|
||||
) { delegate?.retry(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func reply(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func reply(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_reply"),
|
||||
title: "context_menu_reply".localized(),
|
||||
accessibilityLabel: "Reply to message"
|
||||
) { delegate?.reply(cellViewModel) }
|
||||
) { delegate?.reply(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func copy(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func copy(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_copy"),
|
||||
title: "copy".localized(),
|
||||
accessibilityLabel: "Copy text"
|
||||
) { delegate?.copy(cellViewModel) }
|
||||
) { delegate?.copy(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func copySessionID(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
|
@ -79,50 +79,50 @@ extension ContextMenuVC {
|
|||
) { delegate?.copySessionID(cellViewModel) }
|
||||
}
|
||||
|
||||
static func delete(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func delete(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_trash"),
|
||||
title: "TXT_DELETE_TITLE".localized(),
|
||||
accessibilityLabel: "Delete message"
|
||||
) { delegate?.delete(cellViewModel) }
|
||||
) { delegate?.delete(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func save(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func save(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_download"),
|
||||
title: "context_menu_save".localized(),
|
||||
accessibilityLabel: "Save attachment"
|
||||
) { delegate?.save(cellViewModel) }
|
||||
) { delegate?.save(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func ban(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func ban(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_block"),
|
||||
title: "context_menu_ban_user".localized(),
|
||||
accessibilityLabel: "Ban user"
|
||||
) { delegate?.ban(cellViewModel) }
|
||||
) { delegate?.ban(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
icon: UIImage(named: "ic_block"),
|
||||
title: "context_menu_ban_and_delete_all".localized(),
|
||||
accessibilityLabel: "Ban user and delete"
|
||||
) { delegate?.banAndDeleteAllMessages(cellViewModel) }
|
||||
) { delegate?.banAndDeleteAllMessages(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func react(_ cellViewModel: MessageViewModel, _ emoji: EmojiWithSkinTones, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func react(_ cellViewModel: MessageViewModel, _ emoji: EmojiWithSkinTones, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
title: emoji.rawValue,
|
||||
isEmojiAction: true
|
||||
) { delegate?.react(cellViewModel, with: emoji) }
|
||||
) { delegate?.react(cellViewModel, with: emoji, using: dependencies) }
|
||||
}
|
||||
|
||||
static func emojiPlusButton(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
static func emojiPlusButton(_ cellViewModel: MessageViewModel, _ delegate: ContextMenuActionDelegate?, using dependencies: Dependencies) -> Action {
|
||||
return Action(
|
||||
isEmojiPlus: true,
|
||||
accessibilityLabel: "Add emoji"
|
||||
) { delegate?.showFullEmojiKeyboard(cellViewModel) }
|
||||
) { delegate?.showFullEmojiKeyboard(cellViewModel, using: dependencies) }
|
||||
}
|
||||
|
||||
static func dismiss(_ delegate: ContextMenuActionDelegate?) -> Action {
|
||||
|
@ -150,7 +150,8 @@ extension ContextMenuVC {
|
|||
currentUserBlinded25PublicKey: String?,
|
||||
currentUserIsOpenGroupModerator: Bool,
|
||||
currentThreadIsMessageRequest: Bool,
|
||||
delegate: ContextMenuActionDelegate?
|
||||
delegate: ContextMenuActionDelegate?,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> [Action]? {
|
||||
switch cellViewModel.variant {
|
||||
case .standardIncomingDeleted, .infoCall,
|
||||
|
@ -159,7 +160,7 @@ extension ContextMenuVC {
|
|||
.infoClosedGroupCurrentUserLeft, .infoClosedGroupCurrentUserLeaving, .infoClosedGroupCurrentUserErrorLeaving,
|
||||
.infoMessageRequestAccepted, .infoDisappearingMessagesUpdate:
|
||||
// Let the user delete info messages and unsent messages
|
||||
return [ Action.delete(cellViewModel, delegate) ]
|
||||
return [ Action.delete(cellViewModel, delegate, using: dependencies) ]
|
||||
|
||||
case .standardOutgoing, .standardIncoming: break
|
||||
}
|
||||
|
@ -227,18 +228,21 @@ extension ContextMenuVC {
|
|||
let shouldShowInfo: Bool = (cellViewModel.attachments?.isEmpty == false)
|
||||
|
||||
let generatedActions: [Action] = [
|
||||
(canRetry ? Action.retry(cellViewModel, delegate) : nil),
|
||||
(viewModelCanReply(cellViewModel) ? Action.reply(cellViewModel, delegate) : nil),
|
||||
(canCopy ? Action.copy(cellViewModel, delegate) : nil),
|
||||
(canSave ? Action.save(cellViewModel, delegate) : nil),
|
||||
(canRetry ? Action.retry(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(viewModelCanReply(cellViewModel) ? Action.reply(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(canCopy ? Action.copy(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(canSave ? Action.save(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(canCopySessionId ? Action.copySessionID(cellViewModel, delegate) : nil),
|
||||
(canDelete ? Action.delete(cellViewModel, delegate) : nil),
|
||||
(canBan ? Action.ban(cellViewModel, delegate) : nil),
|
||||
(canBan ? Action.banAndDeleteAllMessages(cellViewModel, delegate) : nil),
|
||||
(shouldShowInfo ? Action.info(cellViewModel, delegate) : nil),
|
||||
(canDelete ? Action.delete(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(canBan ? Action.ban(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(canBan ? Action.banAndDeleteAllMessages(cellViewModel, delegate, using: dependencies) : nil),
|
||||
(shouldShowInfo ? Action.info(cellViewModel, delegate, using: dependencies) : nil),
|
||||
]
|
||||
.appending(contentsOf: (shouldShowEmojiActions ? recentEmojis : []).map { Action.react(cellViewModel, $0, delegate) })
|
||||
.appending(Action.emojiPlusButton(cellViewModel, delegate))
|
||||
.appending(
|
||||
contentsOf: (shouldShowEmojiActions ? recentEmojis : [])
|
||||
.map { Action.react(cellViewModel, $0, delegate, using: dependencies) }
|
||||
)
|
||||
.appending(Action.emojiPlusButton(cellViewModel, delegate, using: dependencies))
|
||||
.compactMap { $0 }
|
||||
|
||||
guard !generatedActions.isEmpty else { return [] }
|
||||
|
@ -250,16 +254,16 @@ extension ContextMenuVC {
|
|||
// MARK: - Delegate
|
||||
|
||||
protocol ContextMenuActionDelegate {
|
||||
func info(_ cellViewModel: MessageViewModel)
|
||||
func retry(_ cellViewModel: MessageViewModel)
|
||||
func reply(_ cellViewModel: MessageViewModel)
|
||||
func copy(_ cellViewModel: MessageViewModel)
|
||||
func info(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func retry(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func reply(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func copy(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func copySessionID(_ cellViewModel: MessageViewModel)
|
||||
func delete(_ cellViewModel: MessageViewModel)
|
||||
func save(_ cellViewModel: MessageViewModel)
|
||||
func ban(_ cellViewModel: MessageViewModel)
|
||||
func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel)
|
||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones)
|
||||
func showFullEmojiKeyboard(_ cellViewModel: MessageViewModel)
|
||||
func delete(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func save(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func ban(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones, using dependencies: Dependencies)
|
||||
func showFullEmojiKeyboard(_ cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func contextMenuDismissed()
|
||||
}
|
||||
|
|
|
@ -149,8 +149,14 @@ extension ConversationVC:
|
|||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?) {
|
||||
sendMessage(text: (messageText ?? ""), attachments: attachments)
|
||||
func sendMediaNav(
|
||||
_ sendMediaNavigationController: SendMediaNavigationController,
|
||||
didApproveAttachments attachments: [SignalAttachment],
|
||||
forThreadId threadId: String,
|
||||
messageText: String?,
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
sendMessage(text: (messageText ?? ""), attachments: attachments, using: dependencies)
|
||||
resetMentions()
|
||||
|
||||
dismiss(animated: true) { [weak self] in
|
||||
|
@ -173,8 +179,8 @@ extension ConversationVC:
|
|||
|
||||
// MARK: - AttachmentApprovalViewControllerDelegate
|
||||
|
||||
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?) {
|
||||
sendMessage(text: (messageText ?? ""), attachments: attachments)
|
||||
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?, using dependencies: Dependencies) {
|
||||
sendMessage(text: (messageText ?? ""), attachments: attachments, using: dependencies)
|
||||
resetMentions()
|
||||
|
||||
dismiss(animated: true) { [weak self] in
|
||||
|
@ -409,7 +415,8 @@ extension ConversationVC:
|
|||
attachments: [SignalAttachment] = [],
|
||||
linkPreviewDraft: LinkPreviewDraft? = nil,
|
||||
quoteModel: QuotedReplyModel? = nil,
|
||||
hasPermissionToSendSeed: Bool = false
|
||||
hasPermissionToSendSeed: Bool = false,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
guard !showBlockedModalIfNeeded() else { return }
|
||||
|
||||
|
@ -480,20 +487,23 @@ extension ConversationVC:
|
|||
quoteModel: quoteModel
|
||||
)
|
||||
|
||||
sendMessage(optimisticData: optimisticData)
|
||||
sendMessage(optimisticData: optimisticData, using: dependencies)
|
||||
}
|
||||
|
||||
private func sendMessage(optimisticData: ConversationViewModel.OptimisticMessageData) {
|
||||
private func sendMessage(
|
||||
optimisticData: ConversationViewModel.OptimisticMessageData,
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
let threadId: String = self.viewModel.threadData.threadId
|
||||
let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant
|
||||
|
||||
DispatchQueue.global(qos:.userInitiated).async {
|
||||
DispatchQueue.global(qos:.userInitiated).async(using: dependencies) {
|
||||
// Generate the quote thumbnail if needed (want this to happen outside of the DBWrite thread as
|
||||
// this can take up to 0.5s
|
||||
let quoteThumbnailAttachment: Attachment? = optimisticData.quoteModel?.attachment?.cloneAsQuoteThumbnail()
|
||||
|
||||
// Actually send the message
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { [weak self] db in
|
||||
// Update the thread to be visible (if it isn't already)
|
||||
if self?.viewModel.threadData.threadShouldBeVisible == false {
|
||||
|
@ -541,7 +551,8 @@ extension ConversationVC:
|
|||
db,
|
||||
interaction: insertedInteraction,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
|
@ -798,10 +809,14 @@ extension ConversationVC:
|
|||
self.contextMenuWindow?.makeKeyAndVisible()
|
||||
}
|
||||
|
||||
func handleItemTapped(_ cellViewModel: MessageViewModel, gestureRecognizer: UITapGestureRecognizer) {
|
||||
func handleItemTapped(
|
||||
_ cellViewModel: MessageViewModel,
|
||||
gestureRecognizer: UITapGestureRecognizer,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
guard cellViewModel.variant != .standardOutgoing || (cellViewModel.state != .failed && cellViewModel.state != .failedToSync) else {
|
||||
// Show the failed message sheet
|
||||
showFailedMessageSheet(for: cellViewModel)
|
||||
showFailedMessageSheet(for: cellViewModel, using: dependencies)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -875,8 +890,8 @@ extension ConversationVC:
|
|||
let threadId: String = self.viewModel.threadData.threadId
|
||||
|
||||
// Retry downloading the failed attachment
|
||||
Storage.shared.writeAsync { db in
|
||||
JobRunner.add(
|
||||
dependencies.storage.writeAsync { db in
|
||||
dependencies.jobRunner.add(
|
||||
db,
|
||||
job: Job(
|
||||
variant: .attachmentDownload,
|
||||
|
@ -885,7 +900,9 @@ extension ConversationVC:
|
|||
details: AttachmentDownloadJob.Details(
|
||||
attachmentId: mediaView.attachment.id
|
||||
)
|
||||
)
|
||||
),
|
||||
canStartJob: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
break
|
||||
|
@ -1024,8 +1041,8 @@ extension ConversationVC:
|
|||
self.present(actionSheet, animated: true)
|
||||
}
|
||||
|
||||
func handleReplyButtonTapped(for cellViewModel: MessageViewModel) {
|
||||
reply(cellViewModel)
|
||||
func handleReplyButtonTapped(for cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
reply(cellViewModel, using: dependencies)
|
||||
}
|
||||
|
||||
func startThread(with sessionId: String, openGroupServer: String?, openGroupPublicKey: String?) {
|
||||
|
@ -1134,15 +1151,15 @@ extension ConversationVC:
|
|||
UIView.setAnimationsEnabled(true)
|
||||
}
|
||||
|
||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones) {
|
||||
react(cellViewModel, with: emoji.rawValue, remove: false)
|
||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones, using dependencies: Dependencies) {
|
||||
react(cellViewModel, with: emoji.rawValue, remove: false, using: dependencies)
|
||||
}
|
||||
|
||||
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones) {
|
||||
react(cellViewModel, with: emoji.rawValue, remove: true)
|
||||
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones, using dependencies: Dependencies) {
|
||||
react(cellViewModel, with: emoji.rawValue, remove: true, using: dependencies)
|
||||
}
|
||||
|
||||
func removeAllReactions(_ cellViewModel: MessageViewModel, for emoji: String) {
|
||||
func removeAllReactions(_ cellViewModel: MessageViewModel, for emoji: String, using dependencies: Dependencies) {
|
||||
guard cellViewModel.threadVariant == .community else { return }
|
||||
|
||||
Storage.shared
|
||||
|
@ -1219,7 +1236,7 @@ extension ConversationVC:
|
|||
let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant
|
||||
let openGroupRoom: String? = self.viewModel.threadData.openGroupRoomToken
|
||||
let sentTimestamp: Int64 = SnodeAPI.currentOffsetTimestampMs()
|
||||
let recentReactionTimestamps: [Int64] = dependencies.generalCache.recentReactionTimestamps
|
||||
let recentReactionTimestamps: [Int64] = dependencies.caches[.general].recentReactionTimestamps
|
||||
|
||||
guard
|
||||
recentReactionTimestamps.count < 20 ||
|
||||
|
@ -1237,7 +1254,7 @@ extension ConversationVC:
|
|||
return
|
||||
}
|
||||
|
||||
dependencies.mutableGeneralCache.mutate {
|
||||
dependencies.caches.mutate(cache: .general) {
|
||||
$0.recentReactionTimestamps = Array($0.recentReactionTimestamps
|
||||
.suffix(19))
|
||||
.appending(sentTimestamp)
|
||||
|
@ -1272,9 +1289,9 @@ extension ConversationVC:
|
|||
))
|
||||
}
|
||||
}
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies)
|
||||
.flatMap { pendingChange -> AnyPublisher<(MessageSender.PreparedSendData?, OpenGroupInfo?), Error> in
|
||||
Storage.shared.writePublisher { [weak self] db -> (MessageSender.PreparedSendData?, OpenGroupInfo?) in
|
||||
dependencies.storage.writePublisher { [weak self] db -> (MessageSender.PreparedSendData?, OpenGroupInfo?) in
|
||||
// Update the thread to be visible (if it isn't already)
|
||||
if self?.viewModel.threadData.threadShouldBeVisible == false {
|
||||
_ = try SessionThread
|
||||
|
@ -1383,7 +1400,8 @@ extension ConversationVC:
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: cellViewModel.threadId, threadVariant: cellViewModel.threadVariant)
|
||||
.defaultNamespace,
|
||||
interactionId: cellViewModel.id
|
||||
interactionId: cellViewModel.id,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
return (sendData, nil)
|
||||
|
@ -1393,7 +1411,7 @@ extension ConversationVC:
|
|||
.tryFlatMap { messageSendData, openGroupInfo -> AnyPublisher<Void, Error> in
|
||||
switch (messageSendData, openGroupInfo) {
|
||||
case (.some(let sendData), _):
|
||||
return MessageSender.sendImmediate(preparedSendData: sendData)
|
||||
return MessageSender.sendImmediate(data: sendData, using: dependencies)
|
||||
|
||||
case (_, .some(let info)):
|
||||
return OpenGroupAPI.send(data: info.sendData)
|
||||
|
@ -1444,14 +1462,14 @@ extension ConversationVC:
|
|||
}
|
||||
}
|
||||
|
||||
func showFullEmojiKeyboard(_ cellViewModel: MessageViewModel) {
|
||||
func showFullEmojiKeyboard(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
hideInputAccessoryView()
|
||||
|
||||
let emojiPicker = EmojiPickerSheet(
|
||||
completionHandler: { [weak self] emoji in
|
||||
guard let emoji: EmojiWithSkinTones = emoji else { return }
|
||||
|
||||
self?.react(cellViewModel, with: emoji)
|
||||
self?.react(cellViewModel, with: emoji, using: dependencies)
|
||||
},
|
||||
dismissHandler: { [weak self] in
|
||||
self?.showInputAccessoryView()
|
||||
|
@ -1467,7 +1485,7 @@ extension ConversationVC:
|
|||
|
||||
// MARK: --action handling
|
||||
|
||||
func showFailedMessageSheet(for cellViewModel: MessageViewModel) {
|
||||
private func showFailedMessageSheet(for cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
let sheet = UIAlertController(
|
||||
title: (cellViewModel.state == .failedToSync ?
|
||||
"MESSAGE_DELIVERY_FAILED_SYNC_TITLE".localized() :
|
||||
|
@ -1494,7 +1512,7 @@ extension ConversationVC:
|
|||
"context_menu_resend".localized()
|
||||
),
|
||||
style: .default,
|
||||
handler: { [weak self] _ in self?.retry(cellViewModel) }
|
||||
handler: { [weak self] _ in self?.retry(cellViewModel, using: dependencies) }
|
||||
))
|
||||
|
||||
// HACK: Extracting this info from the error string is pretty dodgy
|
||||
|
@ -1607,7 +1625,7 @@ extension ConversationVC:
|
|||
|
||||
// MARK: - ContextMenuActionDelegate
|
||||
|
||||
func info(_ cellViewModel: MessageViewModel) {
|
||||
func info(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
let mediaInfoVC = MediaInfoVC(
|
||||
attachments: (cellViewModel.attachments ?? []),
|
||||
isOutgoing: (cellViewModel.variant == .standardOutgoing),
|
||||
|
@ -1618,8 +1636,7 @@ extension ConversationVC:
|
|||
navigationController?.pushViewController(mediaInfoVC, animated: true)
|
||||
}
|
||||
|
||||
func retry(_ cellViewModel: MessageViewModel) {
|
||||
// If the failed message is an optimistic update then we need to do things differently
|
||||
func retry(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
guard cellViewModel.id != MessageViewModel.optimisticUpdateId else {
|
||||
guard
|
||||
let optimisticMessageId: UUID = cellViewModel.optimisticMessageId,
|
||||
|
@ -1640,11 +1657,11 @@ extension ConversationVC:
|
|||
}
|
||||
|
||||
// Try to send the optimistic message again
|
||||
self.sendMessage(optimisticData: optimisticMessageData)
|
||||
sendMessage(optimisticData: optimisticMessageData, using: dependencies)
|
||||
return
|
||||
}
|
||||
|
||||
Storage.shared.writeAsync { [weak self] db in
|
||||
dependencies.storage.writeAsync { [weak self] db in
|
||||
guard
|
||||
let threadId: String = self?.viewModel.threadData.threadId,
|
||||
let threadVariant: SessionThread.Variant = self?.viewModel.threadData.threadVariant,
|
||||
|
@ -1685,12 +1702,13 @@ extension ConversationVC:
|
|||
interaction: interaction,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant,
|
||||
isSyncMessage: (cellViewModel.state == .failedToSync)
|
||||
isSyncMessage: (cellViewModel.state == .failedToSync),
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func reply(_ cellViewModel: MessageViewModel) {
|
||||
func reply(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
let maybeQuoteDraft: QuotedReplyModel? = QuotedReplyModel.quotedReplyForSending(
|
||||
threadId: self.viewModel.threadData.threadId,
|
||||
authorId: cellViewModel.authorId,
|
||||
|
@ -1713,7 +1731,7 @@ extension ConversationVC:
|
|||
snInputView.becomeFirstResponder()
|
||||
}
|
||||
|
||||
func copy(_ cellViewModel: MessageViewModel) {
|
||||
func copy(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
switch cellViewModel.cellType {
|
||||
case .typingIndicator, .dateHeader, .unreadMarker: break
|
||||
|
||||
|
@ -1751,7 +1769,7 @@ extension ConversationVC:
|
|||
UIPasteboard.general.string = cellViewModel.authorId
|
||||
}
|
||||
|
||||
func delete(_ cellViewModel: MessageViewModel) {
|
||||
func delete(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
switch cellViewModel.variant {
|
||||
case .standardIncomingDeleted, .infoCall,
|
||||
.infoScreenshotNotification, .infoMediaSavedNotification,
|
||||
|
@ -1947,7 +1965,8 @@ extension ConversationVC:
|
|||
message: unsendRequest,
|
||||
threadId: cellViewModel.threadId,
|
||||
interactionId: nil,
|
||||
to: .contact(publicKey: userPublicKey)
|
||||
to: .contact(publicKey: userPublicKey),
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
return
|
||||
|
@ -1970,7 +1989,8 @@ extension ConversationVC:
|
|||
message: unsendRequest,
|
||||
threadId: cellViewModel.threadId,
|
||||
interactionId: nil,
|
||||
to: .contact(publicKey: userPublicKey)
|
||||
to: .contact(publicKey: userPublicKey),
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
self?.showInputAccessoryView()
|
||||
|
@ -1998,7 +2018,8 @@ extension ConversationVC:
|
|||
message: unsendRequest,
|
||||
interactionId: nil,
|
||||
threadId: cellViewModel.threadId,
|
||||
threadVariant: cellViewModel.threadVariant
|
||||
threadVariant: cellViewModel.threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2032,7 +2053,7 @@ extension ConversationVC:
|
|||
}
|
||||
}
|
||||
|
||||
func save(_ cellViewModel: MessageViewModel) {
|
||||
func save(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
guard cellViewModel.cellType == .mediaMessage else { return }
|
||||
|
||||
let mediaAttachments: [(Attachment, String)] = (cellViewModel.attachments ?? [])
|
||||
|
@ -2074,24 +2095,10 @@ extension ConversationVC:
|
|||
return
|
||||
}
|
||||
|
||||
let threadId: String = self.viewModel.threadData.threadId
|
||||
let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant
|
||||
|
||||
Storage.shared.writeAsync { db in
|
||||
try MessageSender.send(
|
||||
db,
|
||||
message: DataExtractionNotification(
|
||||
kind: .mediaSaved(timestamp: UInt64(cellViewModel.timestampMs)),
|
||||
sentTimestamp: UInt64(SnodeAPI.currentOffsetTimestampMs())
|
||||
),
|
||||
interactionId: nil,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
)
|
||||
}
|
||||
sendDataExtraction(kind: .mediaSaved(timestamp: UInt64(cellViewModel.timestampMs)))
|
||||
}
|
||||
|
||||
func ban(_ cellViewModel: MessageViewModel) {
|
||||
func ban(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
guard cellViewModel.threadVariant == .community else { return }
|
||||
|
||||
let threadId: String = self.viewModel.threadData.threadId
|
||||
|
@ -2147,7 +2154,7 @@ extension ConversationVC:
|
|||
self.present(modal, animated: true)
|
||||
}
|
||||
|
||||
func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel) {
|
||||
func banAndDeleteAllMessages(_ cellViewModel: MessageViewModel, using dependencies: Dependencies) {
|
||||
guard cellViewModel.threadVariant == .community else { return }
|
||||
|
||||
let threadId: String = self.viewModel.threadData.threadId
|
||||
|
@ -2205,7 +2212,7 @@ extension ConversationVC:
|
|||
|
||||
// MARK: - VoiceMessageRecordingViewDelegate
|
||||
|
||||
func startVoiceMessageRecording() {
|
||||
func startVoiceMessageRecording(using dependencies: Dependencies) {
|
||||
// Request permission if needed
|
||||
Permissions.requestMicrophonePermissionIfNeeded() { [weak self] in
|
||||
DispatchQueue.main.async {
|
||||
|
@ -2254,7 +2261,7 @@ extension ConversationVC:
|
|||
// Limit voice messages to a minute
|
||||
audioTimer = Timer.scheduledTimer(withTimeInterval: 180, repeats: false, block: { [weak self] _ in
|
||||
self?.snInputView.hideVoiceMessageUI()
|
||||
self?.endVoiceMessageRecording()
|
||||
self?.endVoiceMessageRecording(using: dependencies)
|
||||
})
|
||||
|
||||
// Prepare audio recorder
|
||||
|
@ -2270,7 +2277,7 @@ extension ConversationVC:
|
|||
}
|
||||
}
|
||||
|
||||
func endVoiceMessageRecording() {
|
||||
func endVoiceMessageRecording(using dependencies: Dependencies) {
|
||||
UIApplication.shared.isIdleTimerDisabled = true
|
||||
|
||||
// Hide the UI
|
||||
|
@ -2322,7 +2329,7 @@ extension ConversationVC:
|
|||
}
|
||||
|
||||
// Send attachment
|
||||
sendMessage(text: "", attachments: [attachment])
|
||||
sendMessage(text: "", attachments: [attachment], using: dependencies)
|
||||
}
|
||||
|
||||
func cancelVoiceMessageRecording() {
|
||||
|
@ -2339,23 +2346,29 @@ extension ConversationVC:
|
|||
|
||||
// MARK: - Data Extraction Notifications
|
||||
|
||||
@objc func sendScreenshotNotification() {
|
||||
@objc func sendScreenshotNotification() { sendDataExtraction(kind: .screenshot) }
|
||||
|
||||
func sendDataExtraction(
|
||||
kind: DataExtractionNotification.Kind,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
// Only send screenshot notifications to one-to-one conversations
|
||||
guard self.viewModel.threadData.threadVariant == .contact else { return }
|
||||
|
||||
let threadId: String = self.viewModel.threadData.threadId
|
||||
let threadVariant: SessionThread.Variant = self.viewModel.threadData.threadVariant
|
||||
|
||||
Storage.shared.writeAsync { db in
|
||||
dependencies.storage.writeAsync { db in
|
||||
try MessageSender.send(
|
||||
db,
|
||||
message: DataExtractionNotification(
|
||||
kind: .screenshot,
|
||||
kind: kind,
|
||||
sentTimestamp: UInt64(SnodeAPI.currentOffsetTimestampMs())
|
||||
),
|
||||
interactionId: nil,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2391,7 +2404,8 @@ extension ConversationVC {
|
|||
for threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
isNewThread: Bool,
|
||||
timestampMs: Int64
|
||||
timestampMs: Int64,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
guard threadVariant == .contact else { return }
|
||||
|
||||
|
@ -2432,7 +2446,8 @@ extension ConversationVC {
|
|||
),
|
||||
interactionId: nil,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -416,14 +416,14 @@ final class InputView: UIView, InputViewButtonDelegate, InputTextViewDelegate, M
|
|||
if inputViewButton == sendButton { delegate?.handleSendButtonTapped() }
|
||||
}
|
||||
|
||||
func handleInputViewButtonLongPressBegan(_ inputViewButton: InputViewButton?) {
|
||||
func handleInputViewButtonLongPressBegan(_ inputViewButton: InputViewButton?, using dependencies: Dependencies) {
|
||||
guard inputViewButton == voiceMessageButton else { return }
|
||||
|
||||
// Note: The 'showVoiceMessageUI' call MUST come before triggering 'startVoiceMessageRecording'
|
||||
// because if something goes wrong it'll trigger `hideVoiceMessageUI` and we don't want it to
|
||||
// end up in a state with the input content hidden
|
||||
showVoiceMessageUI()
|
||||
delegate?.startVoiceMessageRecording()
|
||||
delegate?.startVoiceMessageRecording(using: dependencies)
|
||||
}
|
||||
|
||||
func handleInputViewButtonLongPressMoved(_ inputViewButton: InputViewButton, with touch: UITouch?) {
|
||||
|
|
|
@ -137,7 +137,9 @@ final class InputViewButton: UIView {
|
|||
|
||||
// We want to detect both taps and long presses
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { onTouchesBegan() }
|
||||
|
||||
private func onTouchesBegan(using dependencies: Dependencies = Dependencies()) {
|
||||
guard isUserInteractionEnabled else { return }
|
||||
|
||||
UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
|
||||
|
@ -145,7 +147,7 @@ final class InputViewButton: UIView {
|
|||
invalidateLongPressIfNeeded()
|
||||
longPressTimer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { [weak self] _ in
|
||||
self?.isLongPress = true
|
||||
self?.delegate?.handleInputViewButtonLongPressBegan(self)
|
||||
self?.delegate?.handleInputViewButtonLongPressBegan(self, using: dependencies)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -185,13 +187,13 @@ final class InputViewButton: UIView {
|
|||
|
||||
protocol InputViewButtonDelegate: AnyObject {
|
||||
func handleInputViewButtonTapped(_ inputViewButton: InputViewButton)
|
||||
func handleInputViewButtonLongPressBegan(_ inputViewButton: InputViewButton?)
|
||||
func handleInputViewButtonLongPressBegan(_ inputViewButton: InputViewButton?, using dependencies: Dependencies)
|
||||
func handleInputViewButtonLongPressMoved(_ inputViewButton: InputViewButton, with touch: UITouch?)
|
||||
func handleInputViewButtonLongPressEnded(_ inputViewButton: InputViewButton, with touch: UITouch?)
|
||||
}
|
||||
|
||||
extension InputViewButtonDelegate {
|
||||
func handleInputViewButtonLongPressBegan(_ inputViewButton: InputViewButton?) { }
|
||||
func handleInputViewButtonLongPressBegan(_ inputViewButton: InputViewButton?, using dependencies: Dependencies) { }
|
||||
func handleInputViewButtonLongPressMoved(_ inputViewButton: InputViewButton, with touch: UITouch?) { }
|
||||
func handleInputViewButtonLongPressEnded(_ inputViewButton: InputViewButton, with touch: UITouch?) { }
|
||||
}
|
||||
|
|
|
@ -310,12 +310,12 @@ final class VoiceMessageRecordingView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
func handleLongPressEnded(at location: CGPoint) {
|
||||
func handleLongPressEnded(at location: CGPoint, using dependencies: Dependencies = Dependencies()) {
|
||||
if pulseView.frame.contains(location) {
|
||||
delegate?.endVoiceMessageRecording()
|
||||
delegate?.endVoiceMessageRecording(using: dependencies)
|
||||
}
|
||||
else if isValidLockViewLocation(location) {
|
||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleCircleViewTap))
|
||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(onCircleViewTap))
|
||||
circleView.addGestureRecognizer(tapGestureRecognizer)
|
||||
|
||||
UIView.animate(withDuration: 0.25, delay: 0, options: .transitionCrossDissolve, animations: {
|
||||
|
@ -332,8 +332,10 @@ final class VoiceMessageRecordingView: UIView {
|
|||
}
|
||||
}
|
||||
|
||||
@objc private func handleCircleViewTap() {
|
||||
delegate?.endVoiceMessageRecording()
|
||||
@objc private func onCircleViewTap() { handleCircleViewTap() }
|
||||
|
||||
private func handleCircleViewTap(using dependencies: Dependencies = Dependencies()) {
|
||||
delegate?.endVoiceMessageRecording(using: dependencies)
|
||||
}
|
||||
|
||||
@objc private func handleCancelButtonTapped() {
|
||||
|
@ -474,7 +476,7 @@ extension VoiceMessageRecordingView {
|
|||
// MARK: - Delegate
|
||||
|
||||
protocol VoiceMessageRecordingViewDelegate: AnyObject {
|
||||
func startVoiceMessageRecording()
|
||||
func endVoiceMessageRecording()
|
||||
func startVoiceMessageRecording(using dependencies: Dependencies)
|
||||
func endVoiceMessageRecording(using dependencies: Dependencies)
|
||||
func cancelVoiceMessageRecording()
|
||||
}
|
||||
|
|
|
@ -87,12 +87,18 @@ public class MessageCell: UITableViewCell {
|
|||
|
||||
protocol MessageCellDelegate: ReactionDelegate {
|
||||
func handleItemLongPressed(_ cellViewModel: MessageViewModel)
|
||||
func handleItemTapped(_ cellViewModel: MessageViewModel, gestureRecognizer: UITapGestureRecognizer)
|
||||
func handleItemTapped(_ cellViewModel: MessageViewModel, gestureRecognizer: UITapGestureRecognizer, using dependencies: Dependencies)
|
||||
func handleItemDoubleTapped(_ cellViewModel: MessageViewModel)
|
||||
func handleItemSwiped(_ cellViewModel: MessageViewModel, state: SwipeState)
|
||||
func openUrl(_ urlString: String)
|
||||
func handleReplyButtonTapped(for cellViewModel: MessageViewModel)
|
||||
func handleReplyButtonTapped(for cellViewModel: MessageViewModel, using dependencies: Dependencies)
|
||||
func startThread(with sessionId: String, openGroupServer: String?, openGroupPublicKey: String?)
|
||||
func showReactionList(_ cellViewModel: MessageViewModel, selectedReaction: EmojiWithSkinTones?)
|
||||
func needsLayout(for cellViewModel: MessageViewModel, expandingReactions: Bool)
|
||||
}
|
||||
|
||||
extension MessageCellDelegate {
|
||||
func handleItemTapped(_ cellViewModel: MessageViewModel, gestureRecognizer: UITapGestureRecognizer) {
|
||||
handleItemTapped(cellViewModel, gestureRecognizer: gestureRecognizer, using: Dependencies())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -861,7 +861,9 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
|||
isHandlingLongPress = true
|
||||
}
|
||||
|
||||
@objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {
|
||||
@objc private func handleTap(_ gestureRecognizer: UITapGestureRecognizer) { onTap(gestureRecognizer) }
|
||||
|
||||
private func onTap(_ gestureRecognizer: UITapGestureRecognizer, using dependencies: Dependencies = Dependencies()) {
|
||||
guard let cellViewModel: MessageViewModel = self.viewModel else { return }
|
||||
|
||||
let location = gestureRecognizer.location(in: self)
|
||||
|
@ -897,10 +899,10 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
|||
if reactionContainerView.convert(reactionView.frame, from: reactionView.superview).contains(convertedLocation) {
|
||||
|
||||
if reactionView.viewModel.showBorder {
|
||||
delegate?.removeReact(cellViewModel, for: reactionView.viewModel.emoji)
|
||||
delegate?.removeReact(cellViewModel, for: reactionView.viewModel.emoji, using: dependencies)
|
||||
}
|
||||
else {
|
||||
delegate?.react(cellViewModel, with: reactionView.viewModel.emoji)
|
||||
delegate?.react(cellViewModel, with: reactionView.viewModel.emoji, using: dependencies)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -917,7 +919,7 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
|||
}
|
||||
}
|
||||
else if snContentView.bounds.contains(snContentView.convert(location, from: self)) {
|
||||
delegate?.handleItemTapped(cellViewModel, gestureRecognizer: gestureRecognizer)
|
||||
delegate?.handleItemTapped(cellViewModel, gestureRecognizer: gestureRecognizer, using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -985,11 +987,11 @@ final class VisibleMessageCell: MessageCell, TappableLabelDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
private func reply() {
|
||||
private func reply(using dependencies: Dependencies = Dependencies()) {
|
||||
guard let cellViewModel: MessageViewModel = self.viewModel else { return }
|
||||
|
||||
resetReply()
|
||||
delegate?.handleReplyButtonTapped(for: cellViewModel)
|
||||
delegate?.handleReplyButtonTapped(for: cellViewModel, using: dependencies)
|
||||
}
|
||||
|
||||
// MARK: - Convenience
|
||||
|
|
|
@ -39,10 +39,10 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
|||
// MARK: - Initialization
|
||||
|
||||
init(
|
||||
dependencies: Dependencies = Dependencies(),
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
config: DisappearingMessagesConfiguration
|
||||
config: DisappearingMessagesConfiguration,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
self.dependencies = dependencies
|
||||
self.threadId = threadId
|
||||
|
@ -68,7 +68,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
|||
currentSelection
|
||||
.removeDuplicates()
|
||||
.map { [weak self] currentSelection in (self?.storedSelection != currentSelection) }
|
||||
.map { isChanged in
|
||||
.map { [weak self, dependencies] isChanged in
|
||||
guard isChanged else { return [] }
|
||||
|
||||
return [
|
||||
|
@ -76,8 +76,8 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
|||
id: .save,
|
||||
systemItem: .save,
|
||||
accessibilityIdentifier: "Save button"
|
||||
) { [weak self] in
|
||||
self?.saveChanges()
|
||||
) {
|
||||
self?.saveChanges(using: dependencies)
|
||||
self?.dismissScreen()
|
||||
}
|
||||
]
|
||||
|
@ -100,7 +100,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
|||
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
|
||||
private lazy var _observableTableData: ObservableData = ValueObservation
|
||||
.trackingConstantRegion { [weak self, config, dependencies, threadId = self.threadId] db -> [SectionModel] in
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let maybeThreadViewModel: SessionThreadViewModel? = try SessionThreadViewModel
|
||||
.conversationSettingsQuery(threadId: threadId, userPublicKey: userPublicKey)
|
||||
.fetchOne(db)
|
||||
|
@ -156,7 +156,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
|||
|
||||
// MARK: - Functions
|
||||
|
||||
private func saveChanges() {
|
||||
private func saveChanges(using dependencies: Dependencies = Dependencies()) {
|
||||
let threadId: String = self.threadId
|
||||
let threadVariant: SessionThread.Variant = self.threadVariant
|
||||
let currentSelection: TimeInterval = self.currentSelection.value
|
||||
|
@ -195,7 +195,8 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel<ThreadD
|
|||
),
|
||||
interactionId: interaction.id,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Legacy closed groups
|
||||
|
|
|
@ -60,10 +60,10 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
// MARK: - Initialization
|
||||
|
||||
init(
|
||||
dependencies: Dependencies = Dependencies(),
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
didTriggerSearch: @escaping () -> ()
|
||||
didTriggerSearch: @escaping () -> (),
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
self.dependencies = dependencies
|
||||
self.threadId = threadId
|
||||
|
@ -196,7 +196,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
/// just in case the database has changed between the two reads - unfortunately it doesn't look like there is a way to prevent this
|
||||
private lazy var _observableTableData: ObservableData = ValueObservation
|
||||
.trackingConstantRegion { [weak self, dependencies, threadId = self.threadId, threadVariant = self.threadVariant] db -> [SectionModel] in
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let maybeThreadViewModel: SessionThreadViewModel? = try SessionThreadViewModel
|
||||
.conversationSettingsQuery(threadId: threadId, userPublicKey: userPublicKey)
|
||||
.fetchOne(db)
|
||||
|
@ -755,7 +755,7 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
publicKey: publicKey
|
||||
)
|
||||
|
||||
dependencies.storage.writeAsync { db in
|
||||
dependencies.storage.writeAsync { [dependencies] db in
|
||||
try selectedUsers.forEach { userId in
|
||||
let thread: SessionThread = try SessionThread
|
||||
.fetchOrCreate(db, id: userId, variant: .contact, shouldBeVisible: nil)
|
||||
|
@ -786,7 +786,8 @@ class ThreadSettingsViewModel: SessionTableViewModel<ThreadSettingsViewModel.Nav
|
|||
db,
|
||||
interaction: interaction,
|
||||
threadId: thread.id,
|
||||
threadVariant: thread.variant
|
||||
threadVariant: thread.variant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -368,10 +368,12 @@ final class ReactionListSheet: BaseVC {
|
|||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc private func clearAllTapped() {
|
||||
@objc private func clearAllTapped() { clearAll() }
|
||||
|
||||
private func clearAll(using dependencies: Dependencies = Dependencies()) {
|
||||
guard let selectedReaction: EmojiWithSkinTones = self.reactionSummaries.first(where: { $0.isSelected })?.emoji else { return }
|
||||
|
||||
delegate?.removeAllReactions(messageViewModel, for: selectedReaction.rawValue)
|
||||
delegate?.removeAllReactions(messageViewModel, for: selectedReaction.rawValue, using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -599,7 +601,13 @@ extension ReactionListSheet {
|
|||
// MARK: - Delegate
|
||||
|
||||
protocol ReactionDelegate: AnyObject {
|
||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones)
|
||||
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones)
|
||||
func removeAllReactions(_ cellViewModel: MessageViewModel, for emoji: String)
|
||||
func react(_ cellViewModel: MessageViewModel, with emoji: EmojiWithSkinTones, using dependencies: Dependencies)
|
||||
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones, using dependencies: Dependencies)
|
||||
func removeAllReactions(_ cellViewModel: MessageViewModel, for emoji: String, using dependencies: Dependencies)
|
||||
}
|
||||
|
||||
extension ReactionDelegate {
|
||||
func removeReact(_ cellViewModel: MessageViewModel, for emoji: EmojiWithSkinTones) {
|
||||
removeReact(cellViewModel, for: emoji, using: Dependencies())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -505,8 +505,9 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
dismissSelf(animated: true)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func didPressShare(_ sender: Any) {
|
||||
@objc public func didPressShare(_ sender: Any) { share() }
|
||||
|
||||
public func share(using dependencies: Dependencies = Dependencies()) {
|
||||
guard let currentViewController = self.viewControllers?[0] as? MediaDetailViewController else {
|
||||
owsFailDebug("currentViewController was unexpectedly nil")
|
||||
return
|
||||
|
@ -553,7 +554,8 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou
|
|||
),
|
||||
interactionId: nil, // Show no interaction for the current user
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -430,8 +430,8 @@ extension SendMediaNavigationController: AttachmentApprovalViewControllerDelegat
|
|||
attachmentDraftCollection.remove(attachment: attachment)
|
||||
}
|
||||
|
||||
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?) {
|
||||
sendMediaNavDelegate?.sendMediaNav(self, didApproveAttachments: attachments, forThreadId: threadId, messageText: messageText)
|
||||
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?, using dependencies: Dependencies) {
|
||||
sendMediaNavDelegate?.sendMediaNav(self, didApproveAttachments: attachments, forThreadId: threadId, messageText: messageText, using: dependencies)
|
||||
}
|
||||
|
||||
func attachmentApprovalDidCancel(_ attachmentApproval: AttachmentApprovalViewController) {
|
||||
|
@ -764,7 +764,7 @@ private class DoneButton: UIView {
|
|||
|
||||
protocol SendMediaNavDelegate: AnyObject {
|
||||
func sendMediaNavDidCancel(_ sendMediaNavigationController: SendMediaNavigationController?)
|
||||
func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?)
|
||||
func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didApproveAttachments attachments: [SignalAttachment], forThreadId threadId: String, messageText: String?, using dependencies: Dependencies)
|
||||
|
||||
func sendMediaNavInitialMessageText(_ sendMediaNavigationController: SendMediaNavigationController) -> String?
|
||||
func sendMediaNav(_ sendMediaNavigationController: SendMediaNavigationController, didChangeMessageText newMessageText: String?)
|
||||
|
|
|
@ -92,7 +92,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||
}
|
||||
|
||||
// No point continuing if we are running tests
|
||||
guard !CurrentAppContext().isRunningTests else { return true }
|
||||
guard !SNUtilitiesKit.isRunningTests else { return true }
|
||||
|
||||
self.window = mainWindow
|
||||
CurrentAppContext().mainWindow = mainWindow
|
||||
|
@ -212,7 +212,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||
}
|
||||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
guard !CurrentAppContext().isRunningTests else { return }
|
||||
guard !SNUtilitiesKit.isRunningTests else { return }
|
||||
|
||||
UserDefaults.sharedLokiProject?[.isMainAppActive] = true
|
||||
|
||||
|
@ -314,7 +314,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
|
|||
private func completePostMigrationSetup(calledFrom lifecycleMethod: LifecycleMethod, needsConfigSync: Bool) {
|
||||
SNLog("Migrations completed, performing setup and ensuring rootViewController")
|
||||
Configuration.performMainSetup()
|
||||
JobRunner.add(executor: SyncPushTokensJob.self, for: .syncPushTokens)
|
||||
JobRunner.setExecutor(SyncPushTokensJob.self, for: .syncPushTokens)
|
||||
|
||||
// Setup the UI if needed, then trigger any post-UI setup actions
|
||||
self.ensureRootViewController(calledFrom: lifecycleMethod) { [weak self] success in
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import SessionUtilitiesKit
|
||||
import SignalUtilitiesKit
|
||||
import SignalCoreKit
|
||||
|
||||
|
@ -11,7 +12,7 @@ public class AppEnvironment {
|
|||
public class var shared: AppEnvironment {
|
||||
get { return _shared }
|
||||
set {
|
||||
guard CurrentAppContext().isRunningTests else {
|
||||
guard SNUtilitiesKit.isRunningTests else {
|
||||
owsFailDebug("Can only switch environments in tests.")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -61,8 +61,6 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic
|
|||
name:UIApplicationWillTerminateNotification
|
||||
object:nil];
|
||||
|
||||
// We can't use OWSSingletonAssert() since it uses the app context.
|
||||
|
||||
self.appActiveBlocks = [NSMutableArray new];
|
||||
|
||||
return self;
|
||||
|
@ -243,11 +241,6 @@ NSString *const ReportedApplicationStateDidChangeNotification = @"ReportedApplic
|
|||
}];
|
||||
}
|
||||
|
||||
- (BOOL)isRunningTests
|
||||
{
|
||||
return (NSProcessInfo.processInfo.environment[@"XCTestConfigurationFilePath"] != nil);
|
||||
}
|
||||
|
||||
- (void)setNetworkActivityIndicatorVisible:(BOOL)value
|
||||
{
|
||||
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:value];
|
||||
|
|
|
@ -553,7 +553,8 @@ class NotificationActionHandler {
|
|||
func reply(
|
||||
userInfo: [AnyHashable: Any],
|
||||
replyText: String,
|
||||
applicationState: UIApplication.State
|
||||
applicationState: UIApplication.State,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard let threadId = userInfo[AppNotificationUserInfoKey.threadId] as? String else {
|
||||
return Fail<Void, Error>(error: NotificationError.failDebug("threadId was unexpectedly nil"))
|
||||
|
@ -599,10 +600,11 @@ class NotificationActionHandler {
|
|||
db,
|
||||
interaction: interaction,
|
||||
threadId: threadId,
|
||||
threadVariant: thread.variant
|
||||
threadVariant: thread.variant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.handleEvents(
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
|
|
|
@ -17,17 +17,18 @@ public enum SyncPushTokensJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
// Don't run when inactive or not in main app or if the user doesn't exist yet
|
||||
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
||||
return deferred(job) // Don't need to do anything if it's not the main app
|
||||
return deferred(job, dependencies) // Don't need to do anything if it's not the main app
|
||||
}
|
||||
guard Identity.userCompletedRequiredOnboarding() else {
|
||||
SNLog("[SyncPushTokensJob] Deferred due to incomplete registration")
|
||||
return deferred(job)
|
||||
return deferred(job, dependencies)
|
||||
}
|
||||
|
||||
// We need to check a UIApplication setting which needs to run on the main thread so synchronously
|
||||
|
@ -46,7 +47,7 @@ public enum SyncPushTokensJob: JobExecutor {
|
|||
// https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/HandlingRemoteNotifications.html#//apple_ref/doc/uid/TP40008194-CH6-SW1
|
||||
guard job.behaviour == .runOnce || !isRegisteredForRemoteNotifications else {
|
||||
SNLog("[SyncPushTokensJob] Deferred due to Fast Mode disabled")
|
||||
deferred(job) // Don't need to do anything if push notifications are already registered
|
||||
deferred(job, dependencies) // Don't need to do anything if push notifications are already registered
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -56,7 +57,7 @@ public enum SyncPushTokensJob: JobExecutor {
|
|||
// If the job is running and 'Fast Mode' is disabled then we should try to unregister the existing
|
||||
// token
|
||||
guard isUsingFullAPNs else {
|
||||
Just(Storage.shared[.lastRecordedPushToken])
|
||||
Just(dependencies.storage[.lastRecordedPushToken])
|
||||
.setFailureType(to: Error.self)
|
||||
.flatMap { lastRecordedPushToken -> AnyPublisher<String, Error> in
|
||||
if let existingToken: String = lastRecordedPushToken {
|
||||
|
@ -77,12 +78,12 @@ public enum SyncPushTokensJob: JobExecutor {
|
|||
// the token if needed
|
||||
DispatchQueue.main.sync { UIApplication.shared.unregisterForRemoteNotifications() }
|
||||
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write(using: dependencies) { db in
|
||||
db[.lastRecordedPushToken] = nil
|
||||
}
|
||||
return ()
|
||||
}
|
||||
.subscribe(on: queue)
|
||||
.subscribe(on: queue, using: dependencies)
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
|
@ -91,7 +92,7 @@ public enum SyncPushTokensJob: JobExecutor {
|
|||
}
|
||||
|
||||
// We want to complete this job regardless of success or failure
|
||||
success(job, false)
|
||||
success(job, false, dependencies)
|
||||
}
|
||||
)
|
||||
return
|
||||
|
@ -104,9 +105,10 @@ public enum SyncPushTokensJob: JobExecutor {
|
|||
PushNotificationAPI
|
||||
.subscribe(
|
||||
token: Data(hex: pushToken),
|
||||
isForcedUpdate: true
|
||||
isForcedUpdate: true,
|
||||
using: dependencies
|
||||
)
|
||||
.retry(3)
|
||||
.retry(3, using: dependencies)
|
||||
.handleEvents(
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
|
@ -116,9 +118,9 @@ public enum SyncPushTokensJob: JobExecutor {
|
|||
case .finished:
|
||||
Logger.warn("Recording push tokens locally. pushToken: \(redact(pushToken)), voipToken: \(redact(voipToken))")
|
||||
SNLog("[SyncPushTokensJob] Completed")
|
||||
UserDefaults.standard[.lastPushNotificationSync] = Date()
|
||||
dependencies.standardUserDefaults[.lastPushNotificationSync] = dependencies.dateNow
|
||||
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write(using: dependencies) { db in
|
||||
db[.lastRecordedPushToken] = pushToken
|
||||
db[.lastRecordedVoipToken] = voipToken
|
||||
}
|
||||
|
@ -128,10 +130,10 @@ public enum SyncPushTokensJob: JobExecutor {
|
|||
.map { _ in () }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.subscribe(on: queue)
|
||||
.subscribe(on: queue, using: dependencies)
|
||||
.sinkUntilComplete(
|
||||
// We want to complete this job regardless of success or failure
|
||||
receiveCompletion: { _ in success(job, false) }
|
||||
receiveCompletion: { _ in success(job, false, dependencies) }
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -148,9 +150,9 @@ public enum SyncPushTokensJob: JobExecutor {
|
|||
SyncPushTokensJob.run(
|
||||
job,
|
||||
queue: DispatchQueue.global(qos: .default),
|
||||
success: { _, _ in },
|
||||
failure: { _, _, _ in },
|
||||
deferred: { _ in }
|
||||
success: { _, _, _ in },
|
||||
failure: { _, _, _, _ in },
|
||||
deferred: { _, _ in }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,10 @@ enum Onboarding {
|
|||
return existingPublisher
|
||||
}
|
||||
|
||||
private static func createProfileNameRetrievalPublisher(_ requestId: UUID) -> AnyPublisher<String?, Error> {
|
||||
private static func createProfileNameRetrievalPublisher(
|
||||
_ requestId: UUID,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<String?, Error> {
|
||||
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
|
||||
guard SessionUtil.userConfigsEnabled else {
|
||||
return Just(nil)
|
||||
|
@ -99,7 +102,8 @@ enum Onboarding {
|
|||
)
|
||||
}(),
|
||||
sentTimestamp: TimeInterval((message.sentTimestamp ?? 0) / 1000),
|
||||
calledFromConfigHandling: false
|
||||
calledFromConfigHandling: false,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
return ()
|
||||
|
|
|
@ -245,7 +245,7 @@ final class NukeDataModal: Modal {
|
|||
UserDefaults.removeAll()
|
||||
|
||||
// Remove the cached key so it gets re-cached on next access
|
||||
dependencies.mutableGeneralCache.mutate {
|
||||
dependencies.caches.mutate(cache: .general) {
|
||||
$0.encodedPublicKey = nil
|
||||
$0.recentReactionTimestamps = []
|
||||
}
|
||||
|
|
|
@ -13,10 +13,7 @@ public final class BackgroundPoller {
|
|||
|
||||
public static func poll(
|
||||
completionHandler: @escaping (UIBackgroundFetchResult) -> Void,
|
||||
dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies(
|
||||
subscribeQueue: .global(qos: .background),
|
||||
receiveQueue: .main
|
||||
)
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
Publishers
|
||||
.MergeMany(
|
||||
|
@ -55,8 +52,8 @@ public final class BackgroundPoller {
|
|||
}
|
||||
)
|
||||
)
|
||||
.subscribe(on: dependencies.subscribeQueue)
|
||||
.receive(on: dependencies.receiveQueue)
|
||||
.subscribe(on: DispatchQueue.global(qos: .background), using: dependencies)
|
||||
.receive(on: DispatchQueue.main, using: dependencies)
|
||||
.collect()
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
|
@ -74,7 +71,7 @@ public final class BackgroundPoller {
|
|||
}
|
||||
|
||||
private static func pollForMessages(
|
||||
using dependencies: OpenGroupManager.OGMDependencies
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey()
|
||||
|
||||
|
@ -94,7 +91,7 @@ public final class BackgroundPoller {
|
|||
}
|
||||
|
||||
private static func pollForClosedGroupMessages(
|
||||
using dependencies: OpenGroupManager.OGMDependencies
|
||||
using dependencies: Dependencies
|
||||
) -> [AnyPublisher<Void, Error>] {
|
||||
// Fetch all closed groups (excluding any don't contain the current user as a
|
||||
// GroupMemeber as the user is no longer a member of those)
|
||||
|
|
|
@ -124,13 +124,14 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
_ db: Database,
|
||||
message: CallMessage,
|
||||
interactionId: Int64?,
|
||||
in thread: SessionThread
|
||||
in thread: SessionThread,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> AnyPublisher<Void, Error> {
|
||||
SNLog("[Calls] Sending pre-offer message.")
|
||||
|
||||
return MessageSender
|
||||
.sendImmediate(
|
||||
preparedSendData: try MessageSender
|
||||
data: try MessageSender
|
||||
.preparedSendData(
|
||||
db,
|
||||
message: message,
|
||||
|
@ -138,8 +139,10 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||
.defaultNamespace,
|
||||
interactionId: interactionId
|
||||
)
|
||||
interactionId: interactionId,
|
||||
using: dependencies
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
.handleEvents(receiveOutput: { _ in SNLog("[Calls] Pre-offer message has been sent.") })
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -147,7 +150,8 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
|
||||
public func sendOffer(
|
||||
to thread: SessionThread,
|
||||
isRestartingICEConnection: Bool = false
|
||||
isRestartingICEConnection: Bool = false,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
SNLog("[Calls] Sending offer message.")
|
||||
let uuid: String = self.uuid
|
||||
|
@ -172,7 +176,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { db in
|
||||
try MessageSender
|
||||
.preparedSendData(
|
||||
|
@ -188,10 +192,11 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||
.defaultNamespace,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
|
@ -207,12 +212,15 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
public func sendAnswer(to sessionId: String) -> AnyPublisher<Void, Error> {
|
||||
public func sendAnswer(
|
||||
to sessionId: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
SNLog("[Calls] Sending answer message.")
|
||||
let uuid: String = self.uuid
|
||||
let mediaConstraints: RTCMediaConstraints = mediaConstraints(false)
|
||||
|
||||
return Storage.shared
|
||||
return dependencies.storage
|
||||
.readPublisher { db -> SessionThread in
|
||||
guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: sessionId) else {
|
||||
throw WebRTCSessionError.noThread
|
||||
|
@ -239,7 +247,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { db in
|
||||
try MessageSender
|
||||
.preparedSendData(
|
||||
|
@ -254,10 +262,11 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||
.defaultNamespace,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
|
@ -283,7 +292,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
private func sendICECandidates() {
|
||||
private func sendICECandidates(using dependencies: Dependencies = Dependencies()) {
|
||||
let candidates: [RTCIceCandidate] = self.queuedICECandidates
|
||||
let uuid: String = self.uuid
|
||||
let contactSessionId: String = self.contactSessionId
|
||||
|
@ -291,7 +300,7 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
// Empty the queue
|
||||
self.queuedICECandidates.removeAll()
|
||||
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { db in
|
||||
guard let thread: SessionThread = try SessionThread.fetchOne(db, id: contactSessionId) else {
|
||||
throw WebRTCSessionError.noThread
|
||||
|
@ -315,15 +324,20 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||
.defaultNamespace,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.sinkUntilComplete()
|
||||
}
|
||||
|
||||
public func endCall(_ db: Database, with sessionId: String) throws {
|
||||
public func endCall(
|
||||
_ db: Database,
|
||||
with sessionId: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
guard let thread: SessionThread = try SessionThread.fetchOne(db, id: sessionId) else { return }
|
||||
|
||||
SNLog("[Calls] Sending end call message.")
|
||||
|
@ -340,11 +354,12 @@ public final class WebRTCSession : NSObject, RTCPeerConnectionDelegate {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||
.defaultNamespace,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
MessageSender
|
||||
.sendImmediate(preparedSendData: preparedSendData)
|
||||
.sendImmediate(data: preparedSendData, using: dependencies)
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.sinkUntilComplete()
|
||||
}
|
||||
|
|
|
@ -45,20 +45,20 @@ public enum SNMessagingKit: MigratableTarget { // Just to make the external API
|
|||
|
||||
public static func configure() {
|
||||
// Configure the job executors
|
||||
JobRunner.add(executor: DisappearingMessagesJob.self, for: .disappearingMessages)
|
||||
JobRunner.add(executor: FailedMessageSendsJob.self, for: .failedMessageSends)
|
||||
JobRunner.add(executor: FailedAttachmentDownloadsJob.self, for: .failedAttachmentDownloads)
|
||||
JobRunner.add(executor: UpdateProfilePictureJob.self, for: .updateProfilePicture)
|
||||
JobRunner.add(executor: RetrieveDefaultOpenGroupRoomsJob.self, for: .retrieveDefaultOpenGroupRooms)
|
||||
JobRunner.add(executor: GarbageCollectionJob.self, for: .garbageCollection)
|
||||
JobRunner.add(executor: MessageSendJob.self, for: .messageSend)
|
||||
JobRunner.add(executor: MessageReceiveJob.self, for: .messageReceive)
|
||||
JobRunner.add(executor: NotifyPushServerJob.self, for: .notifyPushServer)
|
||||
JobRunner.add(executor: SendReadReceiptsJob.self, for: .sendReadReceipts)
|
||||
JobRunner.add(executor: AttachmentUploadJob.self, for: .attachmentUpload)
|
||||
JobRunner.add(executor: GroupLeavingJob.self, for: .groupLeaving)
|
||||
JobRunner.add(executor: AttachmentDownloadJob.self, for: .attachmentDownload)
|
||||
JobRunner.add(executor: ConfigurationSyncJob.self, for: .configurationSync)
|
||||
JobRunner.add(executor: ConfigMessageReceiveJob.self, for: .configMessageReceive)
|
||||
JobRunner.setExecutor(DisappearingMessagesJob.self, for: .disappearingMessages)
|
||||
JobRunner.setExecutor(FailedMessageSendsJob.self, for: .failedMessageSends)
|
||||
JobRunner.setExecutor(FailedAttachmentDownloadsJob.self, for: .failedAttachmentDownloads)
|
||||
JobRunner.setExecutor(UpdateProfilePictureJob.self, for: .updateProfilePicture)
|
||||
JobRunner.setExecutor(RetrieveDefaultOpenGroupRoomsJob.self, for: .retrieveDefaultOpenGroupRooms)
|
||||
JobRunner.setExecutor(GarbageCollectionJob.self, for: .garbageCollection)
|
||||
JobRunner.setExecutor(MessageSendJob.self, for: .messageSend)
|
||||
JobRunner.setExecutor(MessageReceiveJob.self, for: .messageReceive)
|
||||
JobRunner.setExecutor(NotifyPushServerJob.self, for: .notifyPushServer)
|
||||
JobRunner.setExecutor(SendReadReceiptsJob.self, for: .sendReadReceipts)
|
||||
JobRunner.setExecutor(AttachmentUploadJob.self, for: .attachmentUpload)
|
||||
JobRunner.setExecutor(GroupLeavingJob.self, for: .groupLeaving)
|
||||
JobRunner.setExecutor(AttachmentDownloadJob.self, for: .attachmentDownload)
|
||||
JobRunner.setExecutor(ConfigurationSyncJob.self, for: .configurationSync)
|
||||
JobRunner.setExecutor(ConfigMessageReceiveJob.self, for: .configMessageReceive)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1039,7 +1039,10 @@ extension Attachment {
|
|||
}
|
||||
}
|
||||
|
||||
internal func upload(to destination: Attachment.Destination) -> AnyPublisher<String?, Error> {
|
||||
internal func upload(
|
||||
to destination: Attachment.Destination,
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<String?, Error> {
|
||||
// This can occur if an AttachmnetUploadJob was explicitly created for a message
|
||||
// dependant on the attachment being uploaded (in this case the attachment has
|
||||
// already been uploaded so just succeed)
|
||||
|
|
|
@ -76,7 +76,7 @@ public extension BlindedIdLookup {
|
|||
openGroupServer: String,
|
||||
openGroupPublicKey: String,
|
||||
isCheckingForOutbox: Bool,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> BlindedIdLookup {
|
||||
var lookup: BlindedIdLookup = (try? BlindedIdLookup
|
||||
.fetchOne(db, id: blindedId))
|
||||
|
@ -94,11 +94,13 @@ public extension BlindedIdLookup {
|
|||
// If we we given a sessionId then validate it is correct and if so save it
|
||||
if
|
||||
let sessionId: String = sessionId,
|
||||
dependencies.sodium.sessionId(
|
||||
sessionId,
|
||||
matchesBlindedId: blindedId,
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
genericHash: dependencies.genericHash
|
||||
dependencies.crypto.verify(
|
||||
.sessionId(
|
||||
sessionId,
|
||||
matchesBlindedId: blindedId,
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
{
|
||||
lookup = try lookup
|
||||
|
@ -115,9 +117,16 @@ public extension BlindedIdLookup {
|
|||
.fetchCursor(db)
|
||||
|
||||
while let contact: Contact = try contactsThatApprovedMeCursor.next() {
|
||||
guard dependencies.sodium.sessionId(contact.id, matchesBlindedId: blindedId, serverPublicKey: openGroupPublicKey, genericHash: dependencies.genericHash) else {
|
||||
continue
|
||||
}
|
||||
guard
|
||||
dependencies.crypto.verify(
|
||||
.sessionId(
|
||||
contact.id,
|
||||
matchesBlindedId: blindedId,
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
else { continue }
|
||||
|
||||
// We found a match so update the lookup and leave the loop
|
||||
lookup = try lookup
|
||||
|
@ -151,11 +160,13 @@ public extension BlindedIdLookup {
|
|||
while let otherLookup: BlindedIdLookup = try blindedIdLookupCursor.next() {
|
||||
guard
|
||||
let sessionId: String = otherLookup.sessionId,
|
||||
dependencies.sodium.sessionId(
|
||||
sessionId,
|
||||
matchesBlindedId: blindedId,
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
genericHash: dependencies.genericHash
|
||||
dependencies.crypto.verify(
|
||||
.sessionId(
|
||||
sessionId,
|
||||
matchesBlindedId: blindedId,
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
else { continue }
|
||||
|
||||
|
|
|
@ -55,12 +55,12 @@ public struct Contact: Codable, Identifiable, Equatable, FetchableRecord, Persis
|
|||
isBlocked: Bool = false,
|
||||
didApproveMe: Bool = false,
|
||||
hasBeenBlocked: Bool = false,
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
self.id = id
|
||||
self.isTrusted = (
|
||||
isTrusted ||
|
||||
id == getUserHexEncodedPublicKey(dependencies: dependencies) // Always trust ourselves
|
||||
id == getUserHexEncodedPublicKey(using: dependencies) // Always trust ourselves
|
||||
)
|
||||
self.isApproved = isApproved
|
||||
self.isBlocked = isBlocked
|
||||
|
|
|
@ -163,7 +163,7 @@ internal extension ControlMessageProcessRecord {
|
|||
.infoClosedGroupCreated:
|
||||
return nil
|
||||
|
||||
case .infoClosedGroupUpdated, .infoClosedGroupCurrentUserLeft, .infoClosedGroupCurrentUserLeaving, .infoClosedGroupCurrentUserErrorLeaving:
|
||||
case .infoClosedGroupUpdated, .infoClosedGroupCurrentUserLeft, .infoClosedGroupCurrentUserLeaving, .infoClosedGroupCurrentUserErrorLeaving:
|
||||
self.variant = .closedGroupControlMessage
|
||||
|
||||
case .infoDisappearingMessagesUpdate:
|
||||
|
|
|
@ -797,22 +797,19 @@ public extension Interaction {
|
|||
_ db: Database,
|
||||
threadId: String,
|
||||
body: String?,
|
||||
quoteAuthorId: String? = nil
|
||||
quoteAuthorId: String? = nil,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Bool {
|
||||
var publicKeysToCheck: [String] = [
|
||||
getUserHexEncodedPublicKey(db)
|
||||
getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
]
|
||||
|
||||
// If the thread is an open group then add the blinded id as a key to check
|
||||
if let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: threadId) {
|
||||
let sodium: Sodium = Sodium()
|
||||
|
||||
if
|
||||
let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
||||
let blindedKeyPair: KeyPair = sodium.blindedKeyPair(
|
||||
serverPublicKey: openGroup.publicKey,
|
||||
edKeyPair: userEd25519KeyPair,
|
||||
genericHash: sodium.genericHash
|
||||
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
|
||||
.blindedKeyPair(serverPublicKey: openGroup.publicKey, edKeyPair: userEd25519KeyPair, using: dependencies)
|
||||
)
|
||||
{
|
||||
publicKeysToCheck.append(SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString)
|
||||
|
|
|
@ -250,11 +250,11 @@ public extension Profile {
|
|||
///
|
||||
/// **Note:** This method intentionally does **not** save the newly created Profile,
|
||||
/// it will need to be explicitly saved after calling
|
||||
static func fetchOrCreateCurrentUser(_ db: Database? = nil) -> Profile {
|
||||
static func fetchOrCreateCurrentUser(_ db: Database? = nil, using dependencies: Dependencies = Dependencies()) -> Profile {
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
|
||||
guard let db: Database = db else {
|
||||
return Storage.shared
|
||||
return dependencies.storage
|
||||
.read { db in fetchOrCreateCurrentUser(db) }
|
||||
.defaulting(to: defaultFor(userPublicKey))
|
||||
}
|
||||
|
|
|
@ -525,12 +525,19 @@ public extension SessionThread {
|
|||
_ db: Database? = nil,
|
||||
threadId: String,
|
||||
threadVariant: Variant,
|
||||
blindingPrefix: SessionId.Prefix
|
||||
blindingPrefix: SessionId.Prefix,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> String? {
|
||||
guard threadVariant == .community else { return nil }
|
||||
guard let db: Database = db else {
|
||||
return Storage.shared.read { db in
|
||||
getUserHexEncodedBlindedKey(db, threadId: threadId, threadVariant: threadVariant, blindingPrefix: blindingPrefix)
|
||||
return dependencies.storage.read { db in
|
||||
getUserHexEncodedBlindedKey(
|
||||
db,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant,
|
||||
blindingPrefix: blindingPrefix,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -559,12 +566,8 @@ public extension SessionThread {
|
|||
|
||||
guard capabilities.isEmpty || capabilities.contains(.blind) else { return nil }
|
||||
|
||||
let sodium: Sodium = Sodium()
|
||||
|
||||
let blindedKeyPair: KeyPair? = sodium.blindedKeyPair(
|
||||
serverPublicKey: openGroupInfo.publicKey,
|
||||
edKeyPair: userEdKeyPair,
|
||||
genericHash: sodium.getGenericHash()
|
||||
let blindedKeyPair: KeyPair? = dependencies.crypto.generate(
|
||||
.blindedKeyPair(serverPublicKey: openGroupInfo.publicKey, edKeyPair: userEdKeyPair, using: dependencies)
|
||||
)
|
||||
|
||||
return blindedKeyPair.map { keyPair -> String in
|
||||
|
|
|
@ -14,9 +14,10 @@ public enum AttachmentDownloadJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
let threadId: String = job.threadId,
|
||||
|
@ -25,7 +26,7 @@ public enum AttachmentDownloadJob: JobExecutor {
|
|||
let attachment: Attachment = Storage.shared
|
||||
.read({ db in try Attachment.fetchOne(db, id: details.attachmentId) })
|
||||
else {
|
||||
failure(job, JobRunnerError.missingRequiredDetails, true)
|
||||
failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -33,7 +34,7 @@ public enum AttachmentDownloadJob: JobExecutor {
|
|||
// an AttachmentDownloadJob to get created for an attachment which has already been
|
||||
// downloaded/uploaded so in those cases just succeed immediately
|
||||
guard attachment.state != .downloaded && attachment.state != .uploaded else {
|
||||
success(job, false)
|
||||
success(job, false, dependencies)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -41,8 +42,8 @@ public enum AttachmentDownloadJob: JobExecutor {
|
|||
// the same attachment multiple times at the same time (it also adds a "clean up" mechanism
|
||||
// if an attachment ends up stuck in a "downloading" state incorrectly
|
||||
guard attachment.state != .downloading else {
|
||||
let otherCurrentJobAttachmentIds: Set<String> = JobRunner
|
||||
.infoForCurrentlyRunningJobs(of: .attachmentDownload)
|
||||
let otherCurrentJobAttachmentIds: Set<String> = dependencies.jobRunner
|
||||
.jobInfoFor(state: .running, variant: .attachmentDownload)
|
||||
.filter { key, _ in key != job.id }
|
||||
.values
|
||||
.compactMap { info -> String? in
|
||||
|
@ -57,7 +58,7 @@ public enum AttachmentDownloadJob: JobExecutor {
|
|||
// then we should update the state of the attachment to be failed to avoid having attachments
|
||||
// appear in an endlessly downloading state
|
||||
if !otherCurrentJobAttachmentIds.contains(attachment.id) {
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
_ = try Attachment
|
||||
.filter(id: attachment.id)
|
||||
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedDownload))
|
||||
|
@ -70,12 +71,12 @@ public enum AttachmentDownloadJob: JobExecutor {
|
|||
// If there is another current job then just fail this one permanently, otherwise let it
|
||||
// retry (if there are more retry attempts available) and in the next retry it's state should
|
||||
// be 'failedDownload' so we won't get stuck in a loop
|
||||
failure(job, nil, otherCurrentJobAttachmentIds.contains(attachment.id))
|
||||
failure(job, nil, otherCurrentJobAttachmentIds.contains(attachment.id), dependencies)
|
||||
return
|
||||
}
|
||||
|
||||
// Update to the 'downloading' state (no need to update the 'attachment' instance)
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
try Attachment
|
||||
.filter(id: attachment.id)
|
||||
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.downloading))
|
||||
|
@ -155,16 +156,16 @@ public enum AttachmentDownloadJob: JobExecutor {
|
|||
}
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
// Remove the temporary file
|
||||
OWSFileSystem.deleteFile(temporaryFileUrl.path)
|
||||
|
||||
switch result {
|
||||
case .finished:
|
||||
// Remove the temporary file
|
||||
OWSFileSystem.deleteFile(temporaryFileUrl.path)
|
||||
|
||||
/// Update the attachment state
|
||||
///
|
||||
/// **Note:** We **MUST** use the `'with()` function here as it will update the
|
||||
/// `isValid` and `duration` values based on the downloaded data and the state
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
_ = try attachment
|
||||
.with(
|
||||
state: .downloaded,
|
||||
|
@ -177,11 +178,9 @@ public enum AttachmentDownloadJob: JobExecutor {
|
|||
.saved(db)
|
||||
}
|
||||
|
||||
success(job, false)
|
||||
success(job, false, dependencies)
|
||||
|
||||
case .failure(let error):
|
||||
OWSFileSystem.deleteFile(temporaryFileUrl.path)
|
||||
|
||||
let targetState: Attachment.State
|
||||
let permanentFailure: Bool
|
||||
|
||||
|
@ -211,14 +210,14 @@ public enum AttachmentDownloadJob: JobExecutor {
|
|||
///
|
||||
/// **Note:** We **MUST** use the `'with()` function here as it will update the
|
||||
/// `isValid` and `duration` values based on the downloaded data and the state
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
_ = try Attachment
|
||||
.filter(id: attachment.id)
|
||||
.updateAll(db, Attachment.Columns.state.set(to: targetState))
|
||||
}
|
||||
|
||||
/// Trigger the failure and provide the `permanentFailure` value defined above
|
||||
failure(job, error, permanentFailure)
|
||||
failure(job, error, permanentFailure, dependencies)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -14,16 +14,17 @@ public enum AttachmentUploadJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
let threadId: String = job.threadId,
|
||||
let interactionId: Int64 = job.interactionId,
|
||||
let detailsData: Data = job.details,
|
||||
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData),
|
||||
let (attachment, openGroup): (Attachment, OpenGroup?) = Storage.shared.read({ db in
|
||||
let (attachment, openGroup): (Attachment, OpenGroup?) = dependencies.storage.read({ db in
|
||||
guard let attachment: Attachment = try Attachment.fetchOne(db, id: details.attachmentId) else {
|
||||
return nil
|
||||
}
|
||||
|
@ -32,29 +33,26 @@ public enum AttachmentUploadJob: JobExecutor {
|
|||
})
|
||||
else {
|
||||
SNLog("[AttachmentUploadJob] Failed due to missing details")
|
||||
failure(job, JobRunnerError.missingRequiredDetails, true)
|
||||
return
|
||||
return failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
|
||||
}
|
||||
|
||||
// If the original interaction no longer exists then don't bother uploading the attachment (ie. the
|
||||
// message was deleted before it even got sent)
|
||||
guard Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true else {
|
||||
guard dependencies.storage.read({ db in try Interaction.exists(db, id: interactionId) }) == true else {
|
||||
SNLog("[AttachmentUploadJob] Failed due to missing interaction")
|
||||
failure(job, StorageError.objectNotFound, true)
|
||||
return
|
||||
return failure(job, StorageError.objectNotFound, true, dependencies)
|
||||
}
|
||||
|
||||
// If the attachment is still pending download the hold off on running this job
|
||||
guard attachment.state != .pendingDownload && attachment.state != .downloading else {
|
||||
SNLog("[AttachmentUploadJob] Deferred as attachment is still being downloaded")
|
||||
deferred(job)
|
||||
return
|
||||
return deferred(job, dependencies)
|
||||
}
|
||||
|
||||
// If this upload is related to sending a message then trigger the 'handleMessageWillSend' logic
|
||||
// as if this is a retry the logic wouldn't run until after the upload has completed resulting in
|
||||
// a potentially incorrect delivery status
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
guard
|
||||
let sendJob: Job = try Job.fetchOne(db, id: details.messageSendJobId),
|
||||
let sendJobDetails: Data = sendJob.details,
|
||||
|
@ -74,7 +72,7 @@ public enum AttachmentUploadJob: JobExecutor {
|
|||
// reentrancy issues when the success/failure closures get called before the upload as the JobRunner
|
||||
// will attempt to update the state of the job immediately
|
||||
attachment
|
||||
.upload(to: (openGroup.map { .openGroup($0) } ?? .fileServer))
|
||||
.upload(to: (openGroup.map { .openGroup($0) } ?? .fileServer), using: dependencies)
|
||||
.subscribe(on: queue)
|
||||
.receive(on: queue)
|
||||
.sinkUntilComplete(
|
||||
|
@ -84,7 +82,7 @@ public enum AttachmentUploadJob: JobExecutor {
|
|||
// If this upload is related to sending a message then trigger the
|
||||
// 'handleFailedMessageSend' logic as we want to ensure the message
|
||||
// has the correct delivery status
|
||||
Storage.shared.read { db in
|
||||
dependencies.storage.read { db in
|
||||
guard
|
||||
let sendJob: Job = try Job.fetchOne(db, id: details.messageSendJobId),
|
||||
let sendJobDetails: Data = sendJob.details,
|
||||
|
@ -97,14 +95,15 @@ public enum AttachmentUploadJob: JobExecutor {
|
|||
message: details.message,
|
||||
with: .other(error),
|
||||
interactionId: interactionId,
|
||||
isSyncMessage: details.isSyncMessage
|
||||
isSyncMessage: details.isSyncMessage,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
SNLog("[AttachmentUploadJob] Failed due to error: \(error)")
|
||||
failure(job, error, false)
|
||||
failure(job, error, false, dependencies)
|
||||
|
||||
case .finished: success(job, false)
|
||||
case .finished: success(job, false, dependencies)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -12,9 +12,10 @@ public enum ConfigMessageReceiveJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
/// When the `configMessageReceive` job fails we want to unblock any `messageReceive` jobs it was blocking
|
||||
/// to ensure the user isn't losing any messages - this generally _shouldn't_ happen but if it does then having a temporary
|
||||
|
@ -24,7 +25,7 @@ public enum ConfigMessageReceiveJob: JobExecutor {
|
|||
let removeDependencyOnMessageReceiveJobs: () -> () = {
|
||||
guard let jobId: Int64 = job.id else { return }
|
||||
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
try JobDependencies
|
||||
.filter(JobDependencies.Columns.dependantId == jobId)
|
||||
.joining(
|
||||
|
@ -40,23 +41,21 @@ public enum ConfigMessageReceiveJob: JobExecutor {
|
|||
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
||||
else {
|
||||
removeDependencyOnMessageReceiveJobs()
|
||||
failure(job, JobRunnerError.missingRequiredDetails, true)
|
||||
return
|
||||
return failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
|
||||
}
|
||||
|
||||
// Ensure no standard messages are sent through this job
|
||||
guard !details.messages.contains(where: { $0.variant != .sharedConfigMessage }) else {
|
||||
SNLog("[ConfigMessageReceiveJob] Standard messages incorrectly sent to the 'configMessageReceive' job")
|
||||
removeDependencyOnMessageReceiveJobs()
|
||||
failure(job, MessageReceiverError.invalidMessage, true)
|
||||
return
|
||||
return failure(job, MessageReceiverError.invalidMessage, true, dependencies)
|
||||
}
|
||||
|
||||
var lastError: Error?
|
||||
let sharedConfigMessages: [SharedConfigMessage] = details.messages
|
||||
.compactMap { $0.message as? SharedConfigMessage }
|
||||
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
// Send any SharedConfigMessages to the SessionUtil to handle it
|
||||
do {
|
||||
try SessionUtil.handleConfigMessages(
|
||||
|
@ -72,9 +71,9 @@ public enum ConfigMessageReceiveJob: JobExecutor {
|
|||
switch lastError {
|
||||
case .some(let error):
|
||||
removeDependencyOnMessageReceiveJobs()
|
||||
failure(job, error, true)
|
||||
failure(job, error, true, dependencies)
|
||||
|
||||
case .none: success(job, false)
|
||||
case .none: success(job, false, dependencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,22 +15,24 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
SessionUtil.userConfigsEnabled,
|
||||
Identity.userCompletedRequiredOnboarding()
|
||||
else { return success(job, true) }
|
||||
else { return success(job, true, dependencies) }
|
||||
|
||||
// It's possible for multiple ConfigSyncJob's with the same target (user/group) to try to run at the
|
||||
// same time since as soon as one is started we will enqueue a second one, rather than adding dependencies
|
||||
// between the jobs we just continue to defer the subsequent job while the first one is running in
|
||||
// order to prevent multiple configurationSync jobs with the same target from running at the same time
|
||||
guard
|
||||
JobRunner
|
||||
.infoForCurrentlyRunningJobs(of: .configurationSync)
|
||||
dependencies
|
||||
.jobRunner
|
||||
.jobInfoFor(state: .running, variant: .configurationSync)
|
||||
.filter({ key, info in
|
||||
key != job.id && // Exclude this job
|
||||
info.threadId == job.threadId // Exclude jobs for different ids
|
||||
|
@ -39,14 +41,14 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
else {
|
||||
// Defer the job to run 'maxRunFrequency' from when this one ran (if we don't it'll try start
|
||||
// it again immediately which is pointless)
|
||||
let updatedJob: Job? = Storage.shared.write { db in
|
||||
let updatedJob: Job? = dependencies.storage.write { db in
|
||||
try job
|
||||
.with(nextRunTimestamp: Date().timeIntervalSince1970 + maxRunFrequency)
|
||||
.with(nextRunTimestamp: dependencies.dateNow.timeIntervalSince1970 + maxRunFrequency)
|
||||
.saved(db)
|
||||
}
|
||||
|
||||
SNLog("[ConfigurationSyncJob] For \(job.threadId ?? "UnknownId") deferred due to in progress job")
|
||||
return deferred(updatedJob ?? job)
|
||||
return deferred(updatedJob ?? job, dependencies)
|
||||
}
|
||||
|
||||
// If we don't have a userKeyPair yet then there is no need to sync the configuration
|
||||
|
@ -58,14 +60,14 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
.read({ db in try SessionUtil.pendingChanges(db, publicKey: publicKey) })
|
||||
else {
|
||||
SNLog("[ConfigurationSyncJob] For \(job.threadId ?? "UnknownId") failed due to invalid data")
|
||||
return failure(job, StorageError.generic, false)
|
||||
return failure(job, StorageError.generic, false, dependencies)
|
||||
}
|
||||
|
||||
// If there are no pending changes then the job can just complete (next time something
|
||||
// is updated we want to try and run immediately so don't scuedule another run in this case)
|
||||
guard !pendingConfigChanges.isEmpty else {
|
||||
SNLog("[ConfigurationSyncJob] For \(publicKey) completed with no pending changes")
|
||||
return success(job, true)
|
||||
return success(job, true, dependencies)
|
||||
}
|
||||
|
||||
// Identify the destination and merge all obsolete hashes into a single set
|
||||
|
@ -77,10 +79,10 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
.map { $0.obsoleteHashes }
|
||||
.reduce([], +)
|
||||
.asSet()
|
||||
let jobStartTimestamp: TimeInterval = Date().timeIntervalSince1970
|
||||
let jobStartTimestamp: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||
SNLog("[ConfigurationSyncJob] For \(publicKey) started with \(pendingConfigChanges.count) change\(pendingConfigChanges.count == 1 ? "" : "s")")
|
||||
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.readPublisher { db in
|
||||
try pendingConfigChanges.map { change -> MessageSender.PreparedSendData in
|
||||
try MessageSender.preparedSendData(
|
||||
|
@ -103,7 +105,8 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
|
||||
return (snodeMessage, namespace)
|
||||
},
|
||||
allObsoleteHashes: Array(allObsoleteHashes)
|
||||
allObsoleteHashes: Array(allObsoleteHashes),
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.subscribe(on: queue)
|
||||
|
@ -138,7 +141,7 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
case .finished: SNLog("[ConfigurationSyncJob] For \(publicKey) completed")
|
||||
case .failure(let error):
|
||||
SNLog("[ConfigurationSyncJob] For \(publicKey) failed due to error: \(error)")
|
||||
failure(job, error, false)
|
||||
failure(job, error, false, dependencies)
|
||||
}
|
||||
},
|
||||
receiveValue: { (configDumps: [ConfigDump]) in
|
||||
|
@ -146,7 +149,7 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
var shouldFinishCurrentJob: Bool = false
|
||||
|
||||
// Lastly we need to save the updated dumps to the database
|
||||
let updatedJob: Job? = Storage.shared.write { db in
|
||||
let updatedJob: Job? = dependencies.storage.write { db in
|
||||
// Save the updated dumps to the database
|
||||
try configDumps.forEach { try $0.save(db) }
|
||||
|
||||
|
@ -167,7 +170,7 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
{
|
||||
// If the next job isn't currently running then delay it's start time
|
||||
// until the 'nextRunTimestamp'
|
||||
if !JobRunner.isCurrentlyRunning(existingJob) {
|
||||
if !dependencies.jobRunner.isCurrentlyRunning(existingJob) {
|
||||
_ = try existingJob
|
||||
.with(nextRunTimestamp: nextRunTimestamp)
|
||||
.saved(db)
|
||||
|
@ -183,7 +186,7 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
.saved(db)
|
||||
}
|
||||
|
||||
success((updatedJob ?? job), shouldFinishCurrentJob)
|
||||
success((updatedJob ?? job), shouldFinishCurrentJob, dependencies)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -192,7 +195,11 @@ public enum ConfigurationSyncJob: JobExecutor {
|
|||
// MARK: - Convenience
|
||||
|
||||
public extension ConfigurationSyncJob {
|
||||
static func enqueue(_ db: Database, publicKey: String) {
|
||||
static func enqueue(
|
||||
_ db: Database,
|
||||
publicKey: String,
|
||||
dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
|
||||
guard SessionUtil.userConfigsEnabled(db) else {
|
||||
// If we don't have a userKeyPair (or name) yet then there is no need to sync the
|
||||
|
@ -206,7 +213,7 @@ public extension ConfigurationSyncJob {
|
|||
|
||||
let publicKey: String = getUserHexEncodedPublicKey(db)
|
||||
|
||||
JobRunner.add(
|
||||
dependencies.jobRunner.add(
|
||||
db,
|
||||
job: Job(
|
||||
variant: .messageSend,
|
||||
|
@ -215,26 +222,34 @@ public extension ConfigurationSyncJob {
|
|||
destination: Message.Destination.contact(publicKey: publicKey),
|
||||
message: legacyConfigMessage
|
||||
)
|
||||
)
|
||||
),
|
||||
canStartJob: true,
|
||||
using: dependencies
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Upsert a config sync job if needed
|
||||
JobRunner.upsert(
|
||||
dependencies.jobRunner.upsert(
|
||||
db,
|
||||
job: ConfigurationSyncJob.createIfNeeded(db, publicKey: publicKey)
|
||||
job: ConfigurationSyncJob.createIfNeeded(db, publicKey: publicKey, using: dependencies),
|
||||
canStartJob: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
@discardableResult static func createIfNeeded(_ db: Database, publicKey: String) -> Job? {
|
||||
@discardableResult static func createIfNeeded(
|
||||
_ db: Database,
|
||||
publicKey: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Job? {
|
||||
/// The ConfigurationSyncJob will automatically reschedule itself to run again after 3 seconds so if there is an existing
|
||||
/// job then there is no need to create another instance
|
||||
///
|
||||
/// **Note:** Jobs with different `threadId` values can run concurrently
|
||||
guard
|
||||
JobRunner
|
||||
.infoForCurrentlyRunningJobs(of: .configurationSync)
|
||||
dependencies.jobRunner
|
||||
.jobInfoFor(state: .running, variant: .configurationSync)
|
||||
.filter({ _, info in info.threadId == publicKey })
|
||||
.isEmpty,
|
||||
(try? Job
|
||||
|
@ -252,7 +267,7 @@ public extension ConfigurationSyncJob {
|
|||
)
|
||||
}
|
||||
|
||||
static func run() -> AnyPublisher<Void, Error> {
|
||||
static func run(using dependencies: Dependencies = Dependencies()) -> AnyPublisher<Void, Error> {
|
||||
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
|
||||
guard SessionUtil.userConfigsEnabled else {
|
||||
return Storage.shared
|
||||
|
@ -262,17 +277,18 @@ public extension ConfigurationSyncJob {
|
|||
// fresh install due to the migrations getting run)
|
||||
guard Identity.userCompletedRequiredOnboarding(db) else { throw StorageError.generic }
|
||||
|
||||
let publicKey: String = getUserHexEncodedPublicKey(db)
|
||||
let publicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
|
||||
return try MessageSender.preparedSendData(
|
||||
db,
|
||||
message: try ConfigurationMessage.getCurrent(db),
|
||||
to: Message.Destination.contact(publicKey: publicKey),
|
||||
namespace: .default,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
|
@ -282,9 +298,10 @@ public extension ConfigurationSyncJob {
|
|||
ConfigurationSyncJob.run(
|
||||
Job(variant: .configurationSync),
|
||||
queue: .global(qos: .userInitiated),
|
||||
success: { _, _ in resolver(Result.success(())) },
|
||||
failure: { _, error, _ in resolver(Result.failure(error ?? HTTPError.generic)) },
|
||||
deferred: { _ in }
|
||||
success: { _, _, _ in resolver(Result.success(())) },
|
||||
failure: { _, error, _, _ in resolver(Result.failure(error ?? HTTPError.generic)) },
|
||||
deferred: { _, _ in },
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,9 +13,10 @@ public enum DisappearingMessagesJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// The 'backgroundTask' gets captured and cleared within the 'completion' block
|
||||
let timestampNowMs: TimeInterval = TimeInterval(SnodeAPI.currentOffsetTimestampMs())
|
||||
|
@ -37,7 +38,7 @@ public enum DisappearingMessagesJob: JobExecutor {
|
|||
}
|
||||
|
||||
SNLog("[DisappearingMessagesJob] Deleted \(numDeleted) expired messages")
|
||||
success(updatedJob ?? job, false)
|
||||
success(updatedJob ?? job, false, dependencies)
|
||||
|
||||
// The 'if' is only there to prevent the "variable never read" warning from showing
|
||||
if backgroundTask != nil { backgroundTask = nil }
|
||||
|
|
|
@ -13,20 +13,21 @@ public enum FailedAttachmentDownloadsJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
var changeCount: Int = -1
|
||||
|
||||
// Update all 'sending' message states to 'failed'
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
changeCount = try Attachment
|
||||
.filter(Attachment.Columns.state == Attachment.State.downloading)
|
||||
.updateAll(db, Attachment.Columns.state.set(to: Attachment.State.failedDownload))
|
||||
}
|
||||
|
||||
SNLog("[FailedAttachmentDownloadsJob] Marked \(changeCount) attachments as failed")
|
||||
success(job, false)
|
||||
success(job, false, dependencies)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,15 +12,16 @@ public enum FailedMessageSendsJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
var changeCount: Int = -1
|
||||
var attachmentChangeCount: Int = -1
|
||||
|
||||
// Update all 'sending' message states to 'failed'
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
let sendChangeCount: Int = try RecipientState
|
||||
.filter(RecipientState.Columns.state == RecipientState.State.sending)
|
||||
.updateAll(db, RecipientState.Columns.state.set(to: RecipientState.State.failed))
|
||||
|
@ -34,6 +35,6 @@ public enum FailedMessageSendsJob: JobExecutor {
|
|||
}
|
||||
|
||||
SNLog("[FailedMessageSendsJob] Marked \(changeCount) message\(changeCount == 1 ? "" : "s") as failed (\(attachmentChangeCount) upload\(attachmentChangeCount == 1 ? "" : "s") cancelled)")
|
||||
success(job, false)
|
||||
success(job, false, dependencies)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,10 @@ public enum GarbageCollectionJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, 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)
|
||||
///
|
||||
|
@ -32,18 +33,18 @@ public enum GarbageCollectionJob: JobExecutor {
|
|||
.map { try? JSONDecoder().decode(Details.self, from: $0) }?
|
||||
.typesToCollect)
|
||||
.defaulting(to: Types.allCases)
|
||||
let timestampNow: TimeInterval = Date().timeIntervalSince1970
|
||||
let timestampNow: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||
|
||||
/// Only do a full collection if the job isn't the recurring one or it's been 23 hours since it last ran (23 hours so a user who opens the
|
||||
/// app at about the same time every day will trigger the garbage collection) - since this runs when the app becomes active we
|
||||
/// want to prevent it running to frequently (the app becomes active if a system alert, the notification center or the control panel
|
||||
/// are shown)
|
||||
let lastGarbageCollection: Date = UserDefaults.standard[.lastGarbageCollection]
|
||||
let lastGarbageCollection: Date = dependencies.standardUserDefaults[.lastGarbageCollection]
|
||||
.defaulting(to: Date.distantPast)
|
||||
let finalTypesToCollect: Set<Types> = {
|
||||
guard
|
||||
job.behaviour != .recurringOnActive ||
|
||||
Date().timeIntervalSince(lastGarbageCollection) > (23 * 60 * 60)
|
||||
dependencies.dateNow.timeIntervalSince(lastGarbageCollection) > (23 * 60 * 60)
|
||||
else {
|
||||
// Note: This should only contain the `Types` which are unlikely to ever cause
|
||||
// a startup delay (ie. avoid mass deletions and file management)
|
||||
|
@ -56,7 +57,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
|||
return typesToCollect.asSet()
|
||||
}()
|
||||
|
||||
Storage.shared.writeAsync(
|
||||
dependencies.storage.writeAsync(
|
||||
updates: { db in
|
||||
/// Remove any typing indicators
|
||||
if finalTypesToCollect.contains(.threadTypingIndicators) {
|
||||
|
@ -368,7 +369,7 @@ public enum GarbageCollectionJob: JobExecutor {
|
|||
|
||||
// If we couldn't get the file lists then fail (invalid state and don't want to delete all attachment/profile files)
|
||||
guard let fileInfo: FileInfo = maybeFileInfo else {
|
||||
failure(job, StorageError.generic, false)
|
||||
failure(job, StorageError.generic, false, dependencies)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -443,17 +444,17 @@ public enum GarbageCollectionJob: JobExecutor {
|
|||
|
||||
// Report a single file deletion as a job failure (even if other content was successfully removed)
|
||||
guard deletionErrors.isEmpty else {
|
||||
failure(job, (deletionErrors.first ?? StorageError.generic), false)
|
||||
failure(job, (deletionErrors.first ?? StorageError.generic), false, dependencies)
|
||||
return
|
||||
}
|
||||
|
||||
// If we did a full collection then update the 'lastGarbageCollection' date to
|
||||
// prevent a full collection from running again in the next 23 hours
|
||||
if job.behaviour == .recurringOnActive && Date().timeIntervalSince(lastGarbageCollection) > (23 * 60 * 60) {
|
||||
UserDefaults.standard[.lastGarbageCollection] = Date()
|
||||
if job.behaviour == .recurringOnActive && dependencies.dateNow.timeIntervalSince(lastGarbageCollection) > (23 * 60 * 60) {
|
||||
dependencies.standardUserDefaults[.lastGarbageCollection] = dependencies.dateNow
|
||||
}
|
||||
|
||||
success(job, false)
|
||||
success(job, false, dependencies)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -13,12 +13,13 @@ public enum GroupLeavingJob: JobExecutor {
|
|||
public static var requiresInteractionId: Bool = true
|
||||
|
||||
public static func run(
|
||||
_ job: SessionUtilitiesKit.Job,
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ())
|
||||
{
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
guard
|
||||
let detailsData: Data = job.details,
|
||||
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData),
|
||||
|
@ -26,13 +27,12 @@ public enum GroupLeavingJob: JobExecutor {
|
|||
let interactionId: Int64 = job.interactionId
|
||||
else {
|
||||
SNLog("[GroupLeavingJob] Failed due to missing details")
|
||||
failure(job, JobRunnerError.missingRequiredDetails, true)
|
||||
return
|
||||
return failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
|
||||
}
|
||||
|
||||
let destination: Message.Destination = .closedGroup(groupPublicKey: threadId)
|
||||
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { db in
|
||||
guard (try? SessionThread.exists(db, id: threadId)) == true else {
|
||||
SNLog("[GroupLeavingJob] Failed due to non-existent group conversation")
|
||||
|
@ -51,10 +51,11 @@ public enum GroupLeavingJob: JobExecutor {
|
|||
to: destination,
|
||||
namespace: destination.defaultNamespace,
|
||||
interactionId: job.interactionId,
|
||||
isSyncMessage: false
|
||||
isSyncMessage: false,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.subscribe(on: queue)
|
||||
.receive(on: queue)
|
||||
.sinkUntilComplete(
|
||||
|
@ -71,7 +72,7 @@ public enum GroupLeavingJob: JobExecutor {
|
|||
]
|
||||
|
||||
// Handle the appropriate response
|
||||
Storage.shared.writeAsync { db in
|
||||
dependencies.storage.writeAsync { db in
|
||||
// If it failed due to one of these errors then clear out any associated data (as somehow
|
||||
// the 'SessionThread' exists but not the data required to send the 'MEMBER_LEFT' message
|
||||
// which would leave the user in a state where they can't leave the group)
|
||||
|
@ -127,7 +128,7 @@ public enum GroupLeavingJob: JobExecutor {
|
|||
)
|
||||
}
|
||||
|
||||
success(job, false)
|
||||
success(job, false, dependencies)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -12,24 +12,23 @@ public enum MessageReceiveJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
guard
|
||||
let threadId: String = job.threadId,
|
||||
let detailsData: Data = job.details,
|
||||
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
||||
else {
|
||||
failure(job, JobRunnerError.missingRequiredDetails, true)
|
||||
return
|
||||
return failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
|
||||
}
|
||||
|
||||
// Ensure no config messages are sent through this job
|
||||
guard !details.messages.contains(where: { $0.variant == .sharedConfigMessage }) else {
|
||||
SNLog("[MessageReceiveJob] Config messages incorrectly sent to the 'messageReceive' job")
|
||||
failure(job, MessageReceiverError.invalidSharedConfigMessageHandling, true)
|
||||
return
|
||||
return failure(job, MessageReceiverError.invalidSharedConfigMessageHandling, true, dependencies)
|
||||
}
|
||||
|
||||
var updatedJob: Job = job
|
||||
|
@ -52,7 +51,7 @@ public enum MessageReceiveJob: JobExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
for (messageInfo, protoContent) in messageData {
|
||||
do {
|
||||
try MessageReceiver.handle(
|
||||
|
@ -111,13 +110,13 @@ public enum MessageReceiveJob: JobExecutor {
|
|||
// Handle the result
|
||||
switch lastError {
|
||||
case let error as MessageReceiverError where !error.isRetryable:
|
||||
failure(updatedJob, error, true)
|
||||
failure(updatedJob, error, true, dependencies)
|
||||
|
||||
case .some(let error):
|
||||
failure(updatedJob, error, false)
|
||||
failure(updatedJob, error, false, dependencies)
|
||||
|
||||
case .none:
|
||||
success(updatedJob, false)
|
||||
success(updatedJob, false, dependencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,17 +15,17 @@ public enum MessageSendJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
let detailsData: Data = job.details,
|
||||
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
||||
else {
|
||||
SNLog("[MessageSendJob] Failing due to missing details")
|
||||
failure(job, JobRunnerError.missingRequiredDetails, true)
|
||||
return
|
||||
return failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
|
||||
}
|
||||
|
||||
// We need to include 'fileIds' when sending messages with attachments to Open Groups
|
||||
|
@ -46,14 +46,13 @@ public enum MessageSendJob: JobExecutor {
|
|||
let interactionId: Int64 = job.interactionId
|
||||
else {
|
||||
SNLog("[MessageSendJob] Failing due to missing details")
|
||||
failure(job, JobRunnerError.missingRequiredDetails, true)
|
||||
return
|
||||
return failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
|
||||
}
|
||||
|
||||
// Retrieve the current attachment state
|
||||
typealias AttachmentState = (error: Error?, pendingUploadAttachmentIds: [String], preparedFileIds: [String])
|
||||
|
||||
let attachmentState: AttachmentState = Storage.shared
|
||||
let attachmentState: AttachmentState = dependencies.storage
|
||||
.read { db in
|
||||
// If the original interaction no longer exists then don't bother sending the message (ie. the
|
||||
// message was deleted before it even got sent)
|
||||
|
@ -91,6 +90,10 @@ public enum MessageSendJob: JobExecutor {
|
|||
switch attachment.state {
|
||||
case .uploading, .pendingDownload, .downloading, .failedUpload, .downloaded:
|
||||
return true
|
||||
|
||||
// If we've somehow got an attachment that is in an 'uploaded' state but doesn't
|
||||
// have a 'downloadUrl' then it's invalid and needs to be re-uploaded
|
||||
case .uploaded: return (attachment.downloadUrl == nil)
|
||||
|
||||
default: return false
|
||||
}
|
||||
|
@ -104,26 +107,26 @@ public enum MessageSendJob: JobExecutor {
|
|||
/// If we got an error when trying to retrieve the attachment state then this job is actually invalid so it
|
||||
/// should permanently fail
|
||||
guard attachmentState.error == nil else {
|
||||
return failure(job, (attachmentState.error ?? MessageSenderError.invalidMessage), true)
|
||||
return failure(job, (attachmentState.error ?? MessageSenderError.invalidMessage), true, dependencies)
|
||||
}
|
||||
|
||||
/// If we have any pending (or failed) attachment uploads then we should create jobs for them and insert them into the
|
||||
/// queue before the current job and defer it (this will mean the current job will re-run after these inserted jobs complete)
|
||||
guard attachmentState.pendingUploadAttachmentIds.isEmpty else {
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
try attachmentState.pendingUploadAttachmentIds
|
||||
.filter { attachmentId in
|
||||
// Don't add a new job if there is one already in the queue
|
||||
!JobRunner.hasPendingOrRunningJob(
|
||||
with: .attachmentUpload,
|
||||
details: AttachmentUploadJob.Details(
|
||||
!dependencies.jobRunner.hasJob(
|
||||
of: .attachmentUpload,
|
||||
with: AttachmentUploadJob.Details(
|
||||
messageSendJobId: jobId,
|
||||
attachmentId: attachmentId
|
||||
)
|
||||
)
|
||||
}
|
||||
.compactMap { attachmentId -> (jobId: Int64, job: Job)? in
|
||||
JobRunner
|
||||
dependencies.jobRunner
|
||||
.insert(
|
||||
db,
|
||||
job: Job(
|
||||
|
@ -150,7 +153,7 @@ public enum MessageSendJob: JobExecutor {
|
|||
}
|
||||
|
||||
SNLog("[MessageSendJob] Deferring due to pending attachment uploads")
|
||||
return deferred(job)
|
||||
return deferred(job, dependencies)
|
||||
}
|
||||
|
||||
// Store the fileIds so they can be sent with the open group message content
|
||||
|
@ -164,7 +167,7 @@ public enum MessageSendJob: JobExecutor {
|
|||
///
|
||||
/// **Note:** No need to upload attachments as part of this process as the above logic splits that out into it's own job
|
||||
/// so we shouldn't get here until attachments have already been uploaded
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { db in
|
||||
try MessageSender.preparedSendData(
|
||||
db,
|
||||
|
@ -172,30 +175,31 @@ public enum MessageSendJob: JobExecutor {
|
|||
to: details.destination,
|
||||
namespace: details.destination.defaultNamespace,
|
||||
interactionId: job.interactionId,
|
||||
isSyncMessage: details.isSyncMessage
|
||||
isSyncMessage: details.isSyncMessage,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.map { sendData in sendData.with(fileIds: messageFileIds) }
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.subscribe(on: queue)
|
||||
.receive(on: queue)
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.subscribe(on: queue, using: dependencies)
|
||||
.receive(on: queue, using: dependencies)
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
case .finished: success(job, false)
|
||||
case .finished: success(job, false, dependencies)
|
||||
case .failure(let error):
|
||||
SNLog("[MessageSendJob] Couldn't send message due to error: \(error).")
|
||||
|
||||
switch error {
|
||||
case let senderError as MessageSenderError where !senderError.isRetryable:
|
||||
failure(job, error, true)
|
||||
failure(job, error, true, dependencies)
|
||||
|
||||
case OnionRequestAPIError.httpRequestFailedAtDestination(let statusCode, _, _) where statusCode == 429: // Rate limited
|
||||
failure(job, error, true)
|
||||
failure(job, error, true, dependencies)
|
||||
|
||||
case SnodeAPIError.clockOutOfSync:
|
||||
SNLog("[MessageSendJob] \(originalSentTimestamp != nil ? "Permanently Failing" : "Failing") to send \(type(of: details.message)) due to clock out of sync issue.")
|
||||
failure(job, error, (originalSentTimestamp != nil))
|
||||
failure(job, error, (originalSentTimestamp != nil), dependencies)
|
||||
|
||||
default:
|
||||
SNLog("[MessageSendJob] Failed to send \(type(of: details.message)).")
|
||||
|
@ -203,15 +207,14 @@ public enum MessageSendJob: JobExecutor {
|
|||
if details.message is VisibleMessage {
|
||||
guard
|
||||
let interactionId: Int64 = job.interactionId,
|
||||
Storage.shared.read({ db in try Interaction.exists(db, id: interactionId) }) == true
|
||||
dependencies.storage.read({ db in try Interaction.exists(db, id: interactionId) }) == true
|
||||
else {
|
||||
// The message has been deleted so permanently fail the job
|
||||
failure(job, error, true)
|
||||
return
|
||||
return failure(job, error, true, dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
failure(job, error, false)
|
||||
failure(job, error, false, dependencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,17 +14,17 @@ public enum NotifyPushServerJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
let detailsData: Data = job.details,
|
||||
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
||||
else {
|
||||
SNLog("[NotifyPushServerJob] Failing due to missing details")
|
||||
failure(job, JobRunnerError.missingRequiredDetails, true)
|
||||
return
|
||||
return failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
|
||||
}
|
||||
|
||||
PushNotificationAPI
|
||||
|
@ -38,8 +38,8 @@ public enum NotifyPushServerJob: JobExecutor {
|
|||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
case .finished: success(job, false)
|
||||
case .failure(let error): failure(job, error, false)
|
||||
case .finished: success(job, false, dependencies)
|
||||
case .failure(let error): failure(job, error, false, dependencies)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -12,13 +12,14 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// Don't run when inactive or not in main app
|
||||
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
||||
deferred(job) // Don't need to do anything if it's not the main app
|
||||
deferred(job, dependencies) // Don't need to do anything if it's not the main app
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -26,7 +27,7 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor {
|
|||
// in the database so we need to create a dummy one to retrieve the default room data
|
||||
let defaultGroupId: String = OpenGroup.idFor(roomToken: "", server: OpenGroupAPI.defaultServer)
|
||||
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
guard try OpenGroup.exists(db, id: defaultGroupId) == false else { return }
|
||||
|
||||
_ = try OpenGroup(
|
||||
|
@ -49,11 +50,11 @@ public enum RetrieveDefaultOpenGroupRoomsJob: JobExecutor {
|
|||
switch result {
|
||||
case .finished:
|
||||
SNLog("[RetrieveDefaultOpenGroupRoomsJob] Successfully retrieved default Community rooms")
|
||||
success(job, false)
|
||||
success(job, false, dependencies)
|
||||
|
||||
case .failure(let error):
|
||||
SNLog("[RetrieveDefaultOpenGroupRoomsJob] Failed to get default Community rooms")
|
||||
failure(job, error, false)
|
||||
failure(job, error, false, dependencies)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -14,28 +14,27 @@ public enum SendReadReceiptsJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard
|
||||
let threadId: String = job.threadId,
|
||||
let detailsData: Data = job.details,
|
||||
let details: Details = try? JSONDecoder().decode(Details.self, from: detailsData)
|
||||
else {
|
||||
failure(job, JobRunnerError.missingRequiredDetails, true)
|
||||
return
|
||||
return failure(job, JobRunnerError.missingRequiredDetails, true, dependencies)
|
||||
}
|
||||
|
||||
// If there are no timestampMs values then the job can just complete (next time
|
||||
// something is marked as read we want to try and run immediately so don't scuedule
|
||||
// another run in this case)
|
||||
guard !details.timestampMsValues.isEmpty else {
|
||||
success(job, true)
|
||||
return
|
||||
return success(job, true, dependencies)
|
||||
}
|
||||
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { db in
|
||||
try MessageSender.preparedSendData(
|
||||
db,
|
||||
|
@ -48,19 +47,19 @@ public enum SendReadReceiptsJob: JobExecutor {
|
|||
isSyncMessage: false
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.subscribe(on: queue)
|
||||
.receive(on: queue)
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
case .failure(let error): failure(job, error, false)
|
||||
case .failure(let error): failure(job, error, false, dependencies)
|
||||
case .finished:
|
||||
// When we complete the 'SendReadReceiptsJob' we want to immediately schedule
|
||||
// another one for the same thread but with a 'nextRunTimestamp' set to the
|
||||
// 'maxRunFrequency' value to throttle the read receipt requests
|
||||
var shouldFinishCurrentJob: Bool = false
|
||||
let nextRunTimestamp: TimeInterval = (Date().timeIntervalSince1970 + maxRunFrequency)
|
||||
let nextRunTimestamp: TimeInterval = (dependencies.dateNow.timeIntervalSince1970 + maxRunFrequency)
|
||||
|
||||
let updatedJob: Job? = Storage.shared.write { db in
|
||||
// If another 'sendReadReceipts' job was scheduled then update that one
|
||||
|
@ -87,7 +86,7 @@ public enum SendReadReceiptsJob: JobExecutor {
|
|||
.saved(db)
|
||||
}
|
||||
|
||||
success(updatedJob ?? job, shouldFinishCurrentJob)
|
||||
success(updatedJob ?? job, shouldFinishCurrentJob, dependencies)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -12,37 +12,37 @@ public enum UpdateProfilePictureJob: JobExecutor {
|
|||
public static func run(
|
||||
_ job: Job,
|
||||
queue: DispatchQueue,
|
||||
success: @escaping (Job, Bool) -> (),
|
||||
failure: @escaping (Job, Error?, Bool) -> (),
|
||||
deferred: @escaping (Job) -> ()
|
||||
success: @escaping (Job, Bool, Dependencies) -> (),
|
||||
failure: @escaping (Job, Error?, Bool, Dependencies) -> (),
|
||||
deferred: @escaping (Job, Dependencies) -> (),
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// Don't run when inactive or not in main app
|
||||
guard (UserDefaults.sharedLokiProject?[.isMainAppActive]).defaulting(to: false) else {
|
||||
deferred(job) // Don't need to do anything if it's not the main app
|
||||
return
|
||||
return deferred(job, dependencies) // Don't need to do anything if it's not the main app
|
||||
}
|
||||
|
||||
// Only re-upload the profile picture if enough time has passed since the last upload
|
||||
guard
|
||||
let lastProfilePictureUpload: Date = UserDefaults.standard[.lastProfilePictureUpload],
|
||||
Date().timeIntervalSince(lastProfilePictureUpload) > (14 * 24 * 60 * 60)
|
||||
let lastProfilePictureUpload: Date = dependencies.standardUserDefaults[.lastProfilePictureUpload],
|
||||
dependencies.dateNow.timeIntervalSince(lastProfilePictureUpload) > (14 * 24 * 60 * 60)
|
||||
else {
|
||||
// Reset the `nextRunTimestamp` value just in case the last run failed so we don't get stuck
|
||||
// in a loop endlessly deferring the job
|
||||
if let jobId: Int64 = job.id {
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
try Job
|
||||
.filter(id: jobId)
|
||||
.updateAll(db, Job.Columns.nextRunTimestamp.set(to: 0))
|
||||
}
|
||||
}
|
||||
|
||||
SNLog("[UpdateProfilePictureJob] Deferred as not enough time has passed since the last update")
|
||||
deferred(job)
|
||||
return
|
||||
return deferred(job, dependencies)
|
||||
}
|
||||
|
||||
// Note: The user defaults flag is updated in ProfileManager
|
||||
let profile: Profile = Profile.fetchOrCreateCurrentUser()
|
||||
let profile: Profile = Profile.fetchOrCreateCurrentUser(using: dependencies)
|
||||
let profilePictureData: Data? = profile.profilePictureFileName
|
||||
.map { ProfileManager.loadProfileData(with: $0) }
|
||||
|
||||
|
@ -56,12 +56,12 @@ public enum UpdateProfilePictureJob: JobExecutor {
|
|||
// another database write
|
||||
queue.async {
|
||||
SNLog("[UpdateProfilePictureJob] Profile successfully updated")
|
||||
success(job, false)
|
||||
success(job, false, dependencies)
|
||||
}
|
||||
},
|
||||
failure: { error in
|
||||
SNLog("[UpdateProfilePictureJob] Failed to update profile")
|
||||
failure(job, error, false)
|
||||
failure(job, error, false, dependencies)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
@ -230,7 +230,8 @@ public extension Message {
|
|||
|
||||
static func processRawReceivedMessage(
|
||||
_ db: Database,
|
||||
rawMessage: SnodeReceivedMessage
|
||||
rawMessage: SnodeReceivedMessage,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> ProcessedMessage? {
|
||||
guard let envelope = SNProtoEnvelope.from(rawMessage) else {
|
||||
throw MessageReceiverError.invalidMessage
|
||||
|
@ -242,7 +243,8 @@ public extension Message {
|
|||
envelope: envelope,
|
||||
serverExpirationTimestamp: (TimeInterval(rawMessage.info.expirationDateMs) / 1000),
|
||||
serverHash: rawMessage.info.hash,
|
||||
handleClosedGroupKeyUpdateMessages: true
|
||||
handleClosedGroupKeyUpdateMessages: true,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Ensure we actually want to de-dupe messages for this namespace, otherwise just
|
||||
|
@ -289,7 +291,8 @@ public extension Message {
|
|||
static func processRawReceivedMessage(
|
||||
_ db: Database,
|
||||
serializedData: Data,
|
||||
serverHash: String?
|
||||
serverHash: String?,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> ProcessedMessage? {
|
||||
guard let envelope = try? SNProtoEnvelope.parseData(serializedData) else {
|
||||
throw MessageReceiverError.invalidMessage
|
||||
|
@ -303,7 +306,8 @@ public extension Message {
|
|||
ControlMessageProcessRecord.defaultExpirationSeconds
|
||||
),
|
||||
serverHash: serverHash,
|
||||
handleClosedGroupKeyUpdateMessages: true
|
||||
handleClosedGroupKeyUpdateMessages: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -312,7 +316,8 @@ public extension Message {
|
|||
/// closed group key update messages (the `NotificationServiceExtension` does this itself)
|
||||
static func processRawReceivedMessageAsNotification(
|
||||
_ db: Database,
|
||||
envelope: SNProtoEnvelope
|
||||
envelope: SNProtoEnvelope,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> ProcessedMessage? {
|
||||
let processedMessage: ProcessedMessage? = try processRawReceivedMessage(
|
||||
db,
|
||||
|
@ -322,7 +327,8 @@ public extension Message {
|
|||
ControlMessageProcessRecord.defaultExpirationSeconds
|
||||
),
|
||||
serverHash: nil,
|
||||
handleClosedGroupKeyUpdateMessages: false
|
||||
handleClosedGroupKeyUpdateMessages: false,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
return processedMessage
|
||||
|
@ -334,7 +340,7 @@ public extension Message {
|
|||
openGroupServerPublicKey: String,
|
||||
message: OpenGroupAPI.Message,
|
||||
data: Data,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> ProcessedMessage? {
|
||||
// Need a sender in order to process the message
|
||||
guard let sender: String = message.sender, let timestamp = message.posted else { return nil }
|
||||
|
@ -357,7 +363,7 @@ public extension Message {
|
|||
openGroupMessageServerId: message.id,
|
||||
openGroupServerPublicKey: openGroupServerPublicKey,
|
||||
handleClosedGroupKeyUpdateMessages: false,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -368,7 +374,7 @@ public extension Message {
|
|||
data: Data,
|
||||
isOutgoing: Bool? = nil,
|
||||
otherBlindedPublicKey: String? = nil,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> ProcessedMessage? {
|
||||
// Note: The `posted` value is in seconds but all messages in the database use milliseconds for timestamps
|
||||
let envelopeBuilder = SNProtoEnvelope.builder(type: .sessionMessage, timestamp: UInt64(floor(message.posted * 1000)))
|
||||
|
@ -390,7 +396,7 @@ public extension Message {
|
|||
isOutgoing: isOutgoing,
|
||||
otherBlindedPublicKey: otherBlindedPublicKey,
|
||||
handleClosedGroupKeyUpdateMessages: false,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -399,24 +405,26 @@ public extension Message {
|
|||
openGroupId: String,
|
||||
message: OpenGroupAPI.Message,
|
||||
associatedPendingChanges: [OpenGroupAPI.PendingChange],
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> [Reaction] {
|
||||
var results: [Reaction] = []
|
||||
guard let reactions = message.reactions else { return results }
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let blinded15UserPublicKey: String? = SessionThread
|
||||
.getUserHexEncodedBlindedKey(
|
||||
db,
|
||||
threadId: openGroupId,
|
||||
threadVariant: .community,
|
||||
blindingPrefix: .blinded15
|
||||
blindingPrefix: .blinded15,
|
||||
using: dependencies
|
||||
)
|
||||
let blinded25UserPublicKey: String? = SessionThread
|
||||
.getUserHexEncodedBlindedKey(
|
||||
db,
|
||||
threadId: openGroupId,
|
||||
threadVariant: .community,
|
||||
blindingPrefix: .blinded25
|
||||
blindingPrefix: .blinded25,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
for (encodedEmoji, rawReaction) in reactions {
|
||||
|
@ -536,7 +544,7 @@ public extension Message {
|
|||
isOutgoing: Bool? = nil,
|
||||
otherBlindedPublicKey: String? = nil,
|
||||
handleClosedGroupKeyUpdateMessages: Bool,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> ProcessedMessage? {
|
||||
let (message, proto, threadId, threadVariant) = try MessageReceiver.parse(
|
||||
db,
|
||||
|
@ -547,7 +555,7 @@ public extension Message {
|
|||
openGroupServerPublicKey: openGroupServerPublicKey,
|
||||
isOutgoing: isOutgoing,
|
||||
otherBlindedPublicKey: otherBlindedPublicKey,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
message.serverHash = serverHash
|
||||
|
||||
|
@ -568,7 +576,8 @@ public extension Message {
|
|||
db,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant,
|
||||
message: closedGroupControlMessage
|
||||
message: closedGroupControlMessage,
|
||||
using: dependencies
|
||||
)
|
||||
return nil
|
||||
|
||||
|
|
|
@ -0,0 +1,403 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import Sodium
|
||||
import Clibsodium
|
||||
import Curve25519Kit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
// MARK: - Nonce
|
||||
|
||||
internal extension OpenGroupAPI {
|
||||
class NonceGenerator16Byte: NonceGenerator {
|
||||
public var NonceBytes: Int { 16 }
|
||||
}
|
||||
|
||||
class NonceGenerator24Byte: NonceGenerator {
|
||||
public var NonceBytes: Int { 24 }
|
||||
}
|
||||
}
|
||||
|
||||
public extension Crypto.Size {
|
||||
static let nonce16: Crypto.Size = Crypto.Size(id: "nonce16") { OpenGroupAPI.NonceGenerator16Byte().NonceBytes }
|
||||
static let nonce24: Crypto.Size = Crypto.Size(id: "nonce24") { OpenGroupAPI.NonceGenerator24Byte().NonceBytes }
|
||||
}
|
||||
|
||||
public extension Crypto.Action {
|
||||
static func generateNonce16() -> Crypto.Action {
|
||||
return Crypto.Action(id: "generateNonce16") { OpenGroupAPI.NonceGenerator16Byte().nonce() }
|
||||
}
|
||||
|
||||
static func generateNonce24() -> Crypto.Action {
|
||||
return Crypto.Action(id: "generateNonce24") { OpenGroupAPI.NonceGenerator24Byte().nonce() }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AeadXChaCha20Poly1305Ietf
|
||||
|
||||
public extension Crypto.Size {
|
||||
static let aeadXChaCha20KeyBytes: Crypto.Size = Crypto.Size(id: "aeadXChaCha20KeyBytes") {
|
||||
Sodium().aead.xchacha20poly1305ietf.KeyBytes
|
||||
}
|
||||
static let aeadXChaCha20ABytes: Crypto.Size = Crypto.Size(id: "aeadXChaCha20ABytes") {
|
||||
Sodium().aead.xchacha20poly1305ietf.ABytes
|
||||
}
|
||||
}
|
||||
|
||||
public extension Crypto.Action {
|
||||
/// This method is the same as the standard AeadXChaCha20Poly1305Ietf `encrypt` method except it allows the
|
||||
/// specification of a nonce which allows for deterministic behaviour with unit testing
|
||||
static func encryptAeadXChaCha20(
|
||||
message: Bytes,
|
||||
secretKey: Bytes,
|
||||
nonce: Bytes,
|
||||
additionalData: Bytes? = nil,
|
||||
using dependencies: Dependencies
|
||||
) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "encryptAeadXChaCha20",
|
||||
args: [message, secretKey, nonce, additionalData]
|
||||
) {
|
||||
guard secretKey.count == dependencies.crypto.size(.aeadXChaCha20KeyBytes) else { return nil }
|
||||
|
||||
var authenticatedCipherText = Bytes(
|
||||
repeating: 0,
|
||||
count: message.count + dependencies.crypto.size(.aeadXChaCha20ABytes)
|
||||
)
|
||||
var authenticatedCipherTextLen: UInt64 = 0
|
||||
|
||||
let result = crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||
&authenticatedCipherText, &authenticatedCipherTextLen,
|
||||
message, UInt64(message.count),
|
||||
additionalData, UInt64(additionalData?.count ?? 0),
|
||||
nil, nonce, secretKey
|
||||
)
|
||||
|
||||
guard result == 0 else { return nil }
|
||||
|
||||
return authenticatedCipherText
|
||||
}
|
||||
}
|
||||
|
||||
static func decryptAeadXChaCha20(
|
||||
authenticatedCipherText: Bytes,
|
||||
secretKey: Bytes,
|
||||
nonce: Bytes,
|
||||
additionalData: Bytes? = nil
|
||||
) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "decryptAeadXChaCha20",
|
||||
args: [authenticatedCipherText, secretKey, nonce, additionalData]
|
||||
) {
|
||||
return Sodium().aead.xchacha20poly1305ietf.decrypt(
|
||||
authenticatedCipherText: authenticatedCipherText,
|
||||
secretKey: secretKey,
|
||||
nonce: nonce,
|
||||
additionalData: additionalData
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Blinding
|
||||
|
||||
/// These extenion methods are used to generate a sign "blinded" messages
|
||||
///
|
||||
/// According to the Swift engineers the only situation when `UnsafeRawBufferPointer.baseAddress` is nil is when it's an
|
||||
/// empty collection; as such our guard cases wihch return `-1` when unwrapping this value should never be hit and we can ignore
|
||||
/// them as possible results.
|
||||
///
|
||||
/// For more information see:
|
||||
/// https://forums.swift.org/t/when-is-unsafemutablebufferpointer-baseaddress-nil/32136/5
|
||||
/// https://github.com/apple/swift-evolution/blob/master/proposals/0055-optional-unsafe-pointers.md#unsafebufferpointer
|
||||
public extension Crypto.Action {
|
||||
private static let scalarLength: Int = Int(crypto_core_ed25519_scalarbytes()) // 32
|
||||
private static let noClampLength: Int = Int(Sodium.lib_crypto_scalarmult_ed25519_bytes()) // 32
|
||||
private static let scalarMultLength: Int = Int(crypto_scalarmult_bytes()) // 32
|
||||
fileprivate static let publicKeyLength: Int = Int(crypto_scalarmult_bytes()) // 32
|
||||
fileprivate static let secretKeyLength: Int = Int(crypto_sign_secretkeybytes()) // 64
|
||||
|
||||
/// 64-byte blake2b hash then reduce to get the blinding factor
|
||||
static func generateBlindingFactor(
|
||||
serverPublicKey: String,
|
||||
using dependencies: Dependencies
|
||||
) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "generateBlindingFactor",
|
||||
args: [serverPublicKey]
|
||||
) {
|
||||
/// k = salt.crypto_core_ed25519_scalar_reduce(blake2b(server_pk, digest_size=64).digest())
|
||||
let serverPubKeyData: Data = Data(hex: serverPublicKey)
|
||||
|
||||
guard
|
||||
!serverPubKeyData.isEmpty,
|
||||
let serverPublicKeyHashBytes: Bytes = try? dependencies.crypto.perform(
|
||||
.hash(message: [UInt8](serverPubKeyData), outputLength: 64)
|
||||
)
|
||||
else { return nil }
|
||||
|
||||
/// Reduce the server public key into an ed25519 scalar (`k`)
|
||||
let kPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.scalarLength)
|
||||
|
||||
_ = serverPublicKeyHashBytes.withUnsafeBytes { (serverPublicKeyHashPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let serverPublicKeyHashBaseAddress: UnsafePointer<UInt8> = serverPublicKeyHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_reduce(kPtr, serverPublicKeyHashBaseAddress)
|
||||
return 0
|
||||
}
|
||||
|
||||
return Data(bytes: kPtr, count: Crypto.Action.scalarLength).bytes
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate k*a. To get 'a' (the Ed25519 private key scalar) we call the sodium function to
|
||||
/// convert to an *x* secret key, which seems wrong--but isn't because converted keys use the
|
||||
/// same secret scalar secret (and so this is just the most convenient way to get 'a' out of
|
||||
/// a sodium Ed25519 secret key)
|
||||
fileprivate static func generatePrivateKeyScalar(secretKey: Bytes) -> Bytes {
|
||||
/// a = s.to_curve25519_private_key().encode()
|
||||
let aPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.scalarMultLength)
|
||||
|
||||
/// Looks like the `crypto_sign_ed25519_sk_to_curve25519` function can't actually fail so no need to verify the result
|
||||
/// See: https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_sign/ed25519/ref10/keypair.c#L70
|
||||
_ = secretKey.withUnsafeBytes { (secretKeyPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let secretKeyBaseAddress: UnsafePointer<UInt8> = secretKeyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
return crypto_sign_ed25519_sk_to_curve25519(aPtr, secretKeyBaseAddress)
|
||||
}
|
||||
|
||||
return Data(bytes: aPtr, count: Crypto.Action.scalarMultLength).bytes
|
||||
}
|
||||
|
||||
/// Constructs an Ed25519 signature from a root Ed25519 key and a blinded scalar/pubkey pair, with one tweak to the
|
||||
/// construction: we add kA into the hashed value that yields r so that we have domain separation for different blinded
|
||||
/// pubkeys (this doesn't affect verification at all)
|
||||
static func sogsSignature(
|
||||
message: Bytes,
|
||||
secretKey: Bytes,
|
||||
blindedSecretKey ka: Bytes,
|
||||
blindedPublicKey kA: Bytes
|
||||
) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "sogsSignature",
|
||||
args: [message, secretKey, ka, kA]
|
||||
) {
|
||||
/// H_rh = sha512(s.encode()).digest()[32:]
|
||||
let H_rh: Bytes = Bytes(SHA512.hash(data: secretKey).suffix(32))
|
||||
|
||||
/// r = salt.crypto_core_ed25519_scalar_reduce(sha512_multipart(H_rh, kA, message_parts))
|
||||
let combinedHashBytes: Bytes = SHA512.hash(data: H_rh + kA + message).bytes
|
||||
let rPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.scalarLength)
|
||||
|
||||
_ = combinedHashBytes.withUnsafeBytes { (combinedHashPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let combinedHashBaseAddress: UnsafePointer<UInt8> = combinedHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_reduce(rPtr, combinedHashBaseAddress)
|
||||
return 0
|
||||
}
|
||||
|
||||
/// sig_R = salt.crypto_scalarmult_ed25519_base_noclamp(r)
|
||||
let sig_RPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.noClampLength)
|
||||
guard crypto_scalarmult_ed25519_base_noclamp(sig_RPtr, rPtr) == 0 else { return nil }
|
||||
|
||||
/// HRAM = salt.crypto_core_ed25519_scalar_reduce(sha512_multipart(sig_R, kA, message_parts))
|
||||
let sig_RBytes: Bytes = Data(bytes: sig_RPtr, count: Crypto.Action.noClampLength).bytes
|
||||
let HRAMHashBytes: Bytes = SHA512.hash(data: sig_RBytes + kA + message).bytes
|
||||
let HRAMPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.scalarLength)
|
||||
|
||||
_ = HRAMHashBytes.withUnsafeBytes { (HRAMHashPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let HRAMHashBaseAddress: UnsafePointer<UInt8> = HRAMHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_reduce(HRAMPtr, HRAMHashBaseAddress)
|
||||
return 0
|
||||
}
|
||||
|
||||
/// sig_s = salt.crypto_core_ed25519_scalar_add(r, salt.crypto_core_ed25519_scalar_mul(HRAM, ka))
|
||||
let sig_sMulPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.scalarLength)
|
||||
let sig_sPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.scalarLength)
|
||||
|
||||
_ = ka.withUnsafeBytes { (kaPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let kaBaseAddress: UnsafePointer<UInt8> = kaPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_mul(sig_sMulPtr, HRAMPtr, kaBaseAddress)
|
||||
Sodium.lib_crypto_core_ed25519_scalar_add(sig_sPtr, rPtr, sig_sMulPtr)
|
||||
return 0
|
||||
}
|
||||
|
||||
/// full_sig = sig_R + sig_s
|
||||
return (Data(bytes: sig_RPtr, count: Crypto.Action.noClampLength).bytes + Data(bytes: sig_sPtr, count: Crypto.Action.scalarLength).bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Combines two keys (`kA`)
|
||||
static func combineKeys(
|
||||
lhsKeyBytes: Bytes,
|
||||
rhsKeyBytes: Bytes
|
||||
) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "combineKeys",
|
||||
args: [lhsKeyBytes, rhsKeyBytes]
|
||||
) {
|
||||
let combinedPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.noClampLength)
|
||||
|
||||
let result = rhsKeyBytes.withUnsafeBytes { (rhsKeyBytesPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
return lhsKeyBytes.withUnsafeBytes { (lhsKeyBytesPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let lhsKeyBytesBaseAddress: UnsafePointer<UInt8> = lhsKeyBytesPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
guard let rhsKeyBytesBaseAddress: UnsafePointer<UInt8> = rhsKeyBytesPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
return Sodium.lib_crypto_scalarmult_ed25519_noclamp(combinedPtr, lhsKeyBytesBaseAddress, rhsKeyBytesBaseAddress)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure the above worked
|
||||
guard result == 0 else { return nil }
|
||||
|
||||
return Data(bytes: combinedPtr, count: Crypto.Action.noClampLength).bytes
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate a shared secret for a message from A to B:
|
||||
///
|
||||
/// BLAKE2b(a kB || kA || kB)
|
||||
///
|
||||
/// The receiver can calulate the same value via:
|
||||
///
|
||||
/// BLAKE2b(b kA || kA || kB)
|
||||
static func sharedBlindedEncryptionKey(
|
||||
secretKey: Bytes,
|
||||
otherBlindedPublicKey: Bytes,
|
||||
fromBlindedPublicKey kA: Bytes,
|
||||
toBlindedPublicKey kB: Bytes,
|
||||
using dependencies: Dependencies
|
||||
) -> Crypto.Action {
|
||||
return Crypto.Action(
|
||||
id: "sharedBlindedEncryptionKey",
|
||||
args: [secretKey, otherBlindedPublicKey, kA, kB]
|
||||
) {
|
||||
let aBytes: Bytes = generatePrivateKeyScalar(secretKey: secretKey)
|
||||
let combinedKeyBytes: Bytes = try dependencies.crypto.perform(
|
||||
.combineKeys(lhsKeyBytes: aBytes, rhsKeyBytes: otherBlindedPublicKey)
|
||||
)
|
||||
|
||||
return try dependencies.crypto.perform(
|
||||
.hash(message: (combinedKeyBytes + kA + kB), outputLength: 32)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Crypto.KeyPairType {
|
||||
/// Constructs a "blinded" key pair (`ka, kA`) based on an open group server `publicKey` and an ed25519 `keyPair`
|
||||
static func blindedKeyPair(
|
||||
serverPublicKey: String,
|
||||
edKeyPair: KeyPair,
|
||||
using dependencies: Dependencies
|
||||
) -> Crypto.KeyPairType {
|
||||
return Crypto.KeyPairType(
|
||||
id: "blindedKeyPair",
|
||||
args: [serverPublicKey, edKeyPair]
|
||||
) {
|
||||
guard
|
||||
edKeyPair.publicKey.count == Crypto.Action.publicKeyLength,
|
||||
edKeyPair.secretKey.count == Crypto.Action.secretKeyLength,
|
||||
let kBytes: Bytes = try? dependencies.crypto.perform(
|
||||
.generateBlindingFactor(serverPublicKey: serverPublicKey, using: dependencies)
|
||||
)
|
||||
else { return nil }
|
||||
|
||||
let aBytes: Bytes = Crypto.Action.generatePrivateKeyScalar(secretKey: edKeyPair.secretKey)
|
||||
|
||||
/// Generate the blinded key pair `ka`, `kA`
|
||||
let kaPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.secretKeyLength)
|
||||
let kAPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Crypto.Action.publicKeyLength)
|
||||
|
||||
_ = aBytes.withUnsafeBytes { (aPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
return kBytes.withUnsafeBytes { (kPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let kBaseAddress: UnsafePointer<UInt8> = kPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
guard let aBaseAddress: UnsafePointer<UInt8> = aPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_mul(kaPtr, kBaseAddress, aBaseAddress)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
guard crypto_scalarmult_ed25519_base_noclamp(kAPtr, kaPtr) == 0 else { return nil }
|
||||
|
||||
return KeyPair(
|
||||
publicKey: Data(bytes: kAPtr, count: Crypto.Action.publicKeyLength).bytes,
|
||||
secretKey: Data(bytes: kaPtr, count: Crypto.Action.secretKeyLength).bytes
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Crypto.Verification {
|
||||
/// This method should be used to check if a users standard sessionId matches a blinded one
|
||||
static func sessionId(
|
||||
_ standardSessionId: String,
|
||||
matchesBlindedId blindedSessionId: String,
|
||||
serverPublicKey: String,
|
||||
using dependencies: Dependencies
|
||||
) -> Crypto.Verification {
|
||||
return Crypto.Verification(
|
||||
id: "sessionId",
|
||||
args: [standardSessionId, blindedSessionId, serverPublicKey]
|
||||
) {
|
||||
// Only support generating blinded keys for standard session ids
|
||||
guard
|
||||
let sessionId: SessionId = SessionId(from: standardSessionId),
|
||||
sessionId.prefix == .standard,
|
||||
let blindedId: SessionId = SessionId(from: blindedSessionId),
|
||||
(
|
||||
blindedId.prefix == .blinded15 ||
|
||||
blindedId.prefix == .blinded25
|
||||
),
|
||||
let kBytes: Bytes = try? dependencies.crypto.perform(
|
||||
.generateBlindingFactor(serverPublicKey: serverPublicKey, using: dependencies)
|
||||
)
|
||||
else { return false }
|
||||
|
||||
/// From the session id (ignoring 05 prefix) we have two possible ed25519 pubkeys; the first is the positive (which is what
|
||||
/// Signal's XEd25519 conversion always uses)
|
||||
///
|
||||
/// Note: The below method is code we have exposed from the `curve25519_verify` method within the Curve25519 library
|
||||
/// rather than custom code we have written
|
||||
guard let xEd25519Key: Data = try? Ed25519.publicKey(from: Data(hex: sessionId.publicKey)) else { return false }
|
||||
|
||||
/// Blind the positive public key
|
||||
guard
|
||||
let pk1: Bytes = try? dependencies.crypto.perform(
|
||||
.combineKeys(lhsKeyBytes: kBytes, rhsKeyBytes: xEd25519Key.bytes)
|
||||
)
|
||||
else { return false }
|
||||
|
||||
/// For the negative, what we're going to get out of the above is simply the negative of pk1, so flip the sign bit to get pk2
|
||||
/// pk2 = pk1[0:31] + bytes([pk1[31] ^ 0b1000_0000])
|
||||
let pk2: Bytes = (pk1[0..<31] + [(pk1[31] ^ 0b1000_0000)])
|
||||
|
||||
return (
|
||||
SessionId(.blinded15, publicKey: pk1).publicKey == blindedId.publicKey ||
|
||||
SessionId(.blinded15, publicKey: pk2).publicKey == blindedId.publicKey
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -69,7 +69,6 @@ public extension OpenGroupAPI {
|
|||
info = HTTP.ResponseInfo(code: 0, headers: [:])
|
||||
data = [:]
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ extension OpenGroupAPI.Message {
|
|||
guard let sender: String = maybeSender, let data = Data(base64Encoded: base64EncodedData), let signature = Data(base64Encoded: base64EncodedSignature) else {
|
||||
throw HTTPError.parsingFailed
|
||||
}
|
||||
guard let dependencies: SMKDependencies = decoder.userInfo[Dependencies.userInfoKey] as? SMKDependencies else {
|
||||
guard let dependencies: Dependencies = decoder.userInfo[Dependencies.userInfoKey] as? Dependencies else {
|
||||
throw HTTPError.parsingFailed
|
||||
}
|
||||
|
||||
|
@ -78,13 +78,21 @@ extension OpenGroupAPI.Message {
|
|||
|
||||
switch SessionId.Prefix(from: sender) {
|
||||
case .blinded15, .blinded25:
|
||||
guard dependencies.sign.verify(message: data.bytes, publicKey: publicKey.bytes, signature: signature.bytes) else {
|
||||
guard
|
||||
dependencies.crypto.verify(
|
||||
.signature(message: data.bytes, publicKey: publicKey.bytes, signature: signature.bytes)
|
||||
)
|
||||
else {
|
||||
SNLog("Ignoring message with invalid signature.")
|
||||
throw HTTPError.parsingFailed
|
||||
}
|
||||
|
||||
case .standard, .unblinded:
|
||||
guard (try? dependencies.ed25519.verifySignature(signature, publicKey: publicKey, data: data)) == true else {
|
||||
guard
|
||||
dependencies.crypto.verify(
|
||||
.signatureEd25519(signature, publicKey: publicKey, data: data)
|
||||
)
|
||||
else {
|
||||
SNLog("Ignoring message with invalid signature.")
|
||||
throw HTTPError.parsingFailed
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ public enum OpenGroupAPI {
|
|||
server: String,
|
||||
hasPerformedInitialPoll: Bool,
|
||||
timeSinceLastPoll: TimeInterval,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<BatchResponse> {
|
||||
let lastInboxMessageId: Int64 = (try? OpenGroup
|
||||
.select(.inboxLatestMessageId)
|
||||
|
@ -143,7 +143,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
server: String,
|
||||
requests: [ErasedPreparedSendData],
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<BatchResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -173,7 +173,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
server: String,
|
||||
requests: [ErasedPreparedSendData],
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<BatchResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -202,7 +202,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
server: String,
|
||||
forceBlinded: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<Capabilities> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -225,7 +225,7 @@ public enum OpenGroupAPI {
|
|||
public static func preparedRooms(
|
||||
_ db: Database,
|
||||
server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[Room]> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -244,7 +244,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<Room> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -267,7 +267,7 @@ public enum OpenGroupAPI {
|
|||
lastUpdated: Int64,
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<RoomPollInfo> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -292,7 +292,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<CapabilitiesAndRoomResponse> {
|
||||
return try OpenGroupAPI
|
||||
.preparedSequence(
|
||||
|
@ -332,13 +332,18 @@ public enum OpenGroupAPI {
|
|||
}
|
||||
}
|
||||
|
||||
public typealias CapabilitiesAndRoomsResponse = (
|
||||
capabilities: (info: ResponseInfoType, data: Capabilities),
|
||||
rooms: (info: ResponseInfoType, data: [Room])
|
||||
)
|
||||
|
||||
/// This is a convenience method which constructs a `/sequence` of the `capabilities` and `rooms` requests, refer to those
|
||||
/// methods for the documented behaviour of each method
|
||||
public static func preparedCapabilitiesAndRooms(
|
||||
_ db: Database,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
) throws -> PreparedSendData<(capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room]))> {
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<CapabilitiesAndRoomsResponse> {
|
||||
return try OpenGroupAPI
|
||||
.preparedSequence(
|
||||
db,
|
||||
|
@ -351,7 +356,7 @@ public enum OpenGroupAPI {
|
|||
],
|
||||
using: dependencies
|
||||
)
|
||||
.map { (info: ResponseInfoType, response: BatchResponse) -> (capabilities: (info: ResponseInfoType, data: Capabilities), rooms: (info: ResponseInfoType, data: [Room])) in
|
||||
.map { (info: ResponseInfoType, response: BatchResponse) -> CapabilitiesAndRoomsResponse in
|
||||
let maybeCapabilities: HTTP.BatchSubResponse<Capabilities>? = (response[.capabilities] as? HTTP.BatchSubResponse<Capabilities>)
|
||||
let maybeRooms: HTTP.BatchSubResponse<[Room]>? = response.data
|
||||
.first(where: { key, _ in
|
||||
|
@ -387,7 +392,7 @@ public enum OpenGroupAPI {
|
|||
whisperTo: String?,
|
||||
whisperMods: Bool,
|
||||
fileIds: [String]?,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<Message> {
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else {
|
||||
throw OpenGroupAPIError.signingFailed
|
||||
|
@ -419,7 +424,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<Message> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -443,7 +448,7 @@ public enum OpenGroupAPI {
|
|||
fileIds: [Int64]?,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: plaintext.bytes, for: server, fallbackSigningType: .standard, using: dependencies) else {
|
||||
throw OpenGroupAPIError.signingFailed
|
||||
|
@ -473,7 +478,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -497,7 +502,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[Failable<Message>]> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -526,7 +531,7 @@ public enum OpenGroupAPI {
|
|||
messageId: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[Failable<Message>]> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -555,7 +560,7 @@ public enum OpenGroupAPI {
|
|||
seqNo: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[Failable<Message>]> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -591,7 +596,7 @@ public enum OpenGroupAPI {
|
|||
sessionId: String,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -615,7 +620,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
/// URL(String:) won't convert raw emojis, so need to do a little encoding here.
|
||||
/// The raw emoji will come back when calling url.path
|
||||
|
@ -646,7 +651,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<ReactionAddResponse> {
|
||||
/// URL(String:) won't convert raw emojis, so need to do a little encoding here.
|
||||
/// The raw emoji will come back when calling url.path
|
||||
|
@ -675,7 +680,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<ReactionRemoveResponse> {
|
||||
/// URL(String:) won't convert raw emojis, so need to do a little encoding here.
|
||||
/// The raw emoji will come back when calling url.path
|
||||
|
@ -705,7 +710,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<ReactionRemoveAllResponse> {
|
||||
/// URL(String:) won't convert raw emojis, so need to do a little encoding here.
|
||||
/// The raw emoji will come back when calling url.path
|
||||
|
@ -743,7 +748,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -766,7 +771,7 @@ public enum OpenGroupAPI {
|
|||
id: Int64,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -788,7 +793,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -817,7 +822,7 @@ public enum OpenGroupAPI {
|
|||
fileName: String? = nil,
|
||||
to roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<FileUploadResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -849,7 +854,7 @@ public enum OpenGroupAPI {
|
|||
fileId: String,
|
||||
from roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<Data> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -872,7 +877,7 @@ public enum OpenGroupAPI {
|
|||
public static func preparedInbox(
|
||||
_ db: Database,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[DirectMessage]?> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -893,7 +898,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
id: Int64,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[DirectMessage]?> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -915,7 +920,7 @@ public enum OpenGroupAPI {
|
|||
ciphertext: Data,
|
||||
toInboxFor blindedSessionId: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> PreparedSendData<SendDirectMessageResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -939,7 +944,7 @@ public enum OpenGroupAPI {
|
|||
public static func preparedOutbox(
|
||||
_ db: Database,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[DirectMessage]?> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -960,7 +965,7 @@ public enum OpenGroupAPI {
|
|||
_ db: Database,
|
||||
id: Int64,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<[DirectMessage]?> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -1013,7 +1018,7 @@ public enum OpenGroupAPI {
|
|||
for timeout: TimeInterval? = nil,
|
||||
from roomTokens: [String]? = nil,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -1062,7 +1067,7 @@ public enum OpenGroupAPI {
|
|||
sessionId: String,
|
||||
from roomTokens: [String]?,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
return try OpenGroupAPI
|
||||
.prepareSendData(
|
||||
|
@ -1140,7 +1145,7 @@ public enum OpenGroupAPI {
|
|||
visible: Bool,
|
||||
for roomTokens: [String]?,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<NoResponse> {
|
||||
guard (moderator != nil && admin == nil) || (moderator == nil && admin != nil) else {
|
||||
throw HTTPError.generic
|
||||
|
@ -1173,7 +1178,7 @@ public enum OpenGroupAPI {
|
|||
sessionId: String,
|
||||
in roomToken: String,
|
||||
on server: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<BatchResponse> {
|
||||
return try OpenGroupAPI
|
||||
.preparedSequence(
|
||||
|
@ -1208,7 +1213,7 @@ public enum OpenGroupAPI {
|
|||
for serverName: String,
|
||||
fallbackSigningType signingType: SessionId.Prefix,
|
||||
forceBlinded: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> (publicKey: String, signature: Bytes)? {
|
||||
guard
|
||||
let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
||||
|
@ -1228,13 +1233,14 @@ public enum OpenGroupAPI {
|
|||
|
||||
// If we have no capabilities or if the server supports blinded keys then sign using the blinded key
|
||||
if forceBlinded || capabilities.isEmpty || capabilities.contains(.blind) {
|
||||
guard let blindedKeyPair: KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let signatureResult: Bytes = dependencies.sodium.sogsSignature(message: messageBytes, secretKey: userEdKeyPair.secretKey, blindedSecretKey: blindedKeyPair.secretKey, blindedPublicKey: blindedKeyPair.publicKey) else {
|
||||
return nil
|
||||
}
|
||||
guard
|
||||
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
|
||||
.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, using: dependencies)
|
||||
),
|
||||
let signatureResult: Bytes = try? dependencies.crypto.perform(
|
||||
.sogsSignature(message: messageBytes, secretKey: userEdKeyPair.secretKey, blindedSecretKey: blindedKeyPair.secretKey, blindedPublicKey: blindedKeyPair.publicKey)
|
||||
)
|
||||
else { return nil }
|
||||
|
||||
return (
|
||||
publicKey: SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString,
|
||||
|
@ -1245,9 +1251,11 @@ public enum OpenGroupAPI {
|
|||
// Otherwise sign using the fallback type
|
||||
switch signingType {
|
||||
case .unblinded:
|
||||
guard let signatureResult: Bytes = dependencies.sign.signature(message: messageBytes, secretKey: userEdKeyPair.secretKey) else {
|
||||
return nil
|
||||
}
|
||||
guard
|
||||
let signatureResult: Bytes = try? dependencies.crypto.perform(
|
||||
.signature(message: messageBytes, secretKey: userEdKeyPair.secretKey)
|
||||
)
|
||||
else { return nil }
|
||||
|
||||
return (
|
||||
publicKey: SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString,
|
||||
|
@ -1256,10 +1264,12 @@ public enum OpenGroupAPI {
|
|||
|
||||
// Default to using the 'standard' key
|
||||
default:
|
||||
guard let userKeyPair: KeyPair = Identity.fetchUserKeyPair(db) else { return nil }
|
||||
guard let signatureResult: Bytes = try? dependencies.ed25519.sign(data: messageBytes, keyPair: userKeyPair) else {
|
||||
return nil
|
||||
}
|
||||
guard
|
||||
let userKeyPair: KeyPair = Identity.fetchUserKeyPair(db),
|
||||
let signatureResult: Bytes = try? dependencies.crypto.perform(
|
||||
.signEd25519(data: messageBytes, keyPair: userKeyPair)
|
||||
)
|
||||
else { return nil }
|
||||
|
||||
return (
|
||||
publicKey: SessionId(.standard, publicKey: userKeyPair.publicKey).hexString,
|
||||
|
@ -1275,7 +1285,7 @@ public enum OpenGroupAPI {
|
|||
for serverName: String,
|
||||
with serverPublicKey: String,
|
||||
forceBlinded: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> URLRequest? {
|
||||
guard let url: URL = request.url else { return nil }
|
||||
|
||||
|
@ -1283,12 +1293,12 @@ public enum OpenGroupAPI {
|
|||
let path: String = url.path
|
||||
.appending(url.query.map { value in "?\(value)" })
|
||||
let method: String = (request.httpMethod ?? "GET")
|
||||
let timestamp: Int = Int(floor(Date().timeIntervalSince1970))
|
||||
let nonce: Data = Data(dependencies.nonceGenerator16.nonce())
|
||||
let timestamp: Int = Int(floor(dependencies.dateNow.timeIntervalSince1970))
|
||||
let serverPublicKeyData: Data = Data(hex: serverPublicKey)
|
||||
|
||||
guard
|
||||
!serverPublicKeyData.isEmpty,
|
||||
let nonce: Data = (try? dependencies.crypto.perform(.generateNonce16())).map({ Data($0) }),
|
||||
let timestampBytes: Bytes = "\(timestamp)".data(using: .ascii)?.bytes
|
||||
else { return nil }
|
||||
|
||||
|
@ -1296,7 +1306,7 @@ public enum OpenGroupAPI {
|
|||
let bodyHash: Bytes? = {
|
||||
guard let body: Data = request.httpBody else { return nil }
|
||||
|
||||
return dependencies.genericHash.hash(message: body.bytes, outputLength: 64)
|
||||
return try? dependencies.crypto.perform(.hash(message: body.bytes, outputLength: 64))
|
||||
}()
|
||||
|
||||
/// Generate the signature message
|
||||
|
@ -1341,7 +1351,7 @@ public enum OpenGroupAPI {
|
|||
responseType: R.Type,
|
||||
forceBlinded: Bool = false,
|
||||
timeout: TimeInterval = HTTP.defaultTimeout,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData<R> {
|
||||
let urlRequest: URLRequest = try request.generateUrlRequest()
|
||||
let maybePublicKey: String? = try? OpenGroup
|
||||
|
@ -1369,19 +1379,21 @@ public enum OpenGroupAPI {
|
|||
/// This method takes in the `PreparedSendData<R>` and actually sends it to the API
|
||||
public static func send<R>(
|
||||
data: PreparedSendData<R>?,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<(ResponseInfoType, R), Error> {
|
||||
guard let validData: PreparedSendData<R> = data else {
|
||||
return Fail(error: OpenGroupAPIError.invalidPreparedData)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return dependencies.onionApi
|
||||
.sendOnionRequest(
|
||||
validData.request,
|
||||
to: validData.server,
|
||||
with: validData.publicKey,
|
||||
timeout: validData.timeout
|
||||
return dependencies.network
|
||||
.send(
|
||||
.onionRequest(
|
||||
validData.request,
|
||||
to: validData.server,
|
||||
with: validData.publicKey,
|
||||
timeout: validData.timeout
|
||||
)
|
||||
)
|
||||
.decoded(with: validData, using: dependencies)
|
||||
.eraseToAnyPublisher()
|
||||
|
|
|
@ -12,48 +12,17 @@ import SessionSnodeKit
|
|||
public final class OpenGroupManager {
|
||||
public typealias DefaultRoomInfo = (room: OpenGroupAPI.Room, existingImageData: Data?)
|
||||
|
||||
// MARK: - Cache
|
||||
|
||||
public class Cache: OGMMutableCacheType {
|
||||
public var defaultRoomsPublisher: AnyPublisher<[DefaultRoomInfo], Error>?
|
||||
public var groupImagePublishers: [String: AnyPublisher<Data, Error>] = [:]
|
||||
|
||||
public var pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server
|
||||
public var isPolling: Bool = false
|
||||
|
||||
/// Server URL to value
|
||||
public var hasPerformedInitialPoll: [String: Bool] = [:]
|
||||
public var timeSinceLastPoll: [String: TimeInterval] = [:]
|
||||
|
||||
fileprivate var _timeSinceLastOpen: TimeInterval?
|
||||
public func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval {
|
||||
if let storedTimeSinceLastOpen: TimeInterval = _timeSinceLastOpen {
|
||||
return storedTimeSinceLastOpen
|
||||
}
|
||||
|
||||
guard let lastOpen: Date = dependencies.standardUserDefaults[.lastOpen] else {
|
||||
_timeSinceLastOpen = .greatestFiniteMagnitude
|
||||
return .greatestFiniteMagnitude
|
||||
}
|
||||
|
||||
_timeSinceLastOpen = Date().timeIntervalSince(lastOpen)
|
||||
return Date().timeIntervalSince(lastOpen)
|
||||
}
|
||||
|
||||
public var pendingChanges: [OpenGroupAPI.PendingChange] = []
|
||||
}
|
||||
|
||||
// MARK: - Variables
|
||||
|
||||
public static let shared: OpenGroupManager = OpenGroupManager()
|
||||
|
||||
// MARK: - Polling
|
||||
|
||||
public func startPolling(using dependencies: OGMDependencies = OGMDependencies()) {
|
||||
public func startPolling(using dependencies: Dependencies = Dependencies()) {
|
||||
// Run on the 'workQueue' to ensure any 'Atomic' access doesn't block the main thread
|
||||
// on startup
|
||||
OpenGroupAPI.workQueue.async {
|
||||
guard !dependencies.cache.isPolling else { return }
|
||||
OpenGroupAPI.workQueue.async(using: dependencies) {
|
||||
guard !dependencies.caches[.openGroupManager].isPolling else { return }
|
||||
|
||||
let servers: Set<String> = dependencies.storage
|
||||
.read { db in
|
||||
|
@ -70,7 +39,7 @@ public final class OpenGroupManager {
|
|||
.defaulting(to: [])
|
||||
|
||||
// Update the cache state and re-create all of the pollers
|
||||
dependencies.mutableCache.mutate { cache in
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||
cache.isPolling = true
|
||||
cache.pollers = servers
|
||||
.reduce(into: [:]) { result, server in
|
||||
|
@ -80,13 +49,14 @@ public final class OpenGroupManager {
|
|||
}
|
||||
|
||||
// Now that the pollers have been created actually start them
|
||||
dependencies.cache.pollers.forEach { _, poller in poller.startIfNeeded(using: dependencies) }
|
||||
dependencies.caches[.openGroupManager].pollers
|
||||
.forEach { _, poller in poller.startIfNeeded(using: dependencies) }
|
||||
}
|
||||
}
|
||||
|
||||
public func stopPolling(using dependencies: OGMDependencies = OGMDependencies()) {
|
||||
dependencies.mutableCache.mutate {
|
||||
$0.pollers.forEach { (_, openGroupPoller) in openGroupPoller.stop() }
|
||||
public func stopPolling(using dependencies: Dependencies = Dependencies()) {
|
||||
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||
$0.pollers.forEach { _, openGroupPoller in openGroupPoller.stop() }
|
||||
$0.pollers.removeAll()
|
||||
$0.isPolling = false
|
||||
}
|
||||
|
@ -132,7 +102,13 @@ public final class OpenGroupManager {
|
|||
return options.contains(serverHost)
|
||||
}
|
||||
|
||||
public func hasExistingOpenGroup(_ db: Database, roomToken: String, server: String, publicKey: String, dependencies: OGMDependencies = OGMDependencies()) -> Bool {
|
||||
public func hasExistingOpenGroup(
|
||||
_ db: Database,
|
||||
roomToken: String,
|
||||
server: String,
|
||||
publicKey: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Bool {
|
||||
guard let serverUrl: URL = URL(string: server.lowercased()) else { return false }
|
||||
|
||||
let serverPort: String = OpenGroupManager.port(for: server, serverUrl: serverUrl)
|
||||
|
@ -164,7 +140,7 @@ public final class OpenGroupManager {
|
|||
}
|
||||
|
||||
// First check if there is no poller for the specified server
|
||||
if Set(dependencies.cache.pollers.keys).intersection(serverOptions).isEmpty {
|
||||
if Set(dependencies.caches[.openGroupManager].pollers.keys).intersection(serverOptions).isEmpty {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -187,10 +163,10 @@ public final class OpenGroupManager {
|
|||
server: String,
|
||||
publicKey: String,
|
||||
calledFromConfigHandling: Bool,
|
||||
dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Bool {
|
||||
// If we are currently polling for this server and already have a TSGroupThread for this room the do nothing
|
||||
if hasExistingOpenGroup(db, roomToken: roomToken, server: server, publicKey: publicKey, dependencies: dependencies) {
|
||||
if hasExistingOpenGroup(db, roomToken: roomToken, server: server, publicKey: publicKey, using: dependencies) {
|
||||
SNLog("Ignoring join open group attempt (already joined), user initiated: \(!calledFromConfigHandling)")
|
||||
return false
|
||||
}
|
||||
|
@ -256,7 +232,7 @@ public final class OpenGroupManager {
|
|||
server: String,
|
||||
publicKey: String,
|
||||
calledFromConfigHandling: Bool,
|
||||
dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard successfullyAddedGroup else {
|
||||
return Just(())
|
||||
|
@ -311,7 +287,7 @@ public final class OpenGroupManager {
|
|||
publicKey: publicKey,
|
||||
for: roomToken,
|
||||
on: targetServer,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
) {
|
||||
resolver(Result.success(()))
|
||||
}
|
||||
|
@ -322,7 +298,7 @@ public final class OpenGroupManager {
|
|||
receiveCompletion: { result in
|
||||
switch result {
|
||||
case .finished: break
|
||||
case .failure: SNLog("Failed to join open group.")
|
||||
case .failure(let error): SNLog("Failed to join open group with error: \(error).")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -333,7 +309,7 @@ public final class OpenGroupManager {
|
|||
_ db: Database,
|
||||
openGroupId: String,
|
||||
calledFromConfigHandling: Bool,
|
||||
using dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
let server: String? = try? OpenGroup
|
||||
.select(.server)
|
||||
|
@ -358,9 +334,9 @@ public final class OpenGroupManager {
|
|||
.defaulting(to: 1)
|
||||
|
||||
if numActiveRooms == 1, let server: String = server?.lowercased() {
|
||||
let poller = dependencies.cache.pollers[server]
|
||||
let poller = dependencies.caches[.openGroupManager].pollers[server]
|
||||
poller?.stop()
|
||||
dependencies.mutableCache.mutate { $0.pollers[server] = nil }
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { $0.pollers[server] = nil }
|
||||
}
|
||||
|
||||
// Remove all the data (everything should cascade delete)
|
||||
|
@ -435,7 +411,7 @@ public final class OpenGroupManager {
|
|||
for roomToken: String,
|
||||
on server: String,
|
||||
waitForImageToComplete: Bool = false,
|
||||
dependencies: OGMDependencies = OGMDependencies(),
|
||||
using dependencies: Dependencies,
|
||||
completion: (() -> ())? = nil
|
||||
) throws {
|
||||
// Create the open group model and get or create the thread
|
||||
|
@ -521,18 +497,19 @@ public final class OpenGroupManager {
|
|||
}
|
||||
}
|
||||
|
||||
db.afterNextTransactionNested { _ in
|
||||
db.afterNextTransactionNested { reentrantDb in
|
||||
// Dispatch async to the workQueue to prevent holding up the DBWrite thread from the
|
||||
// above transaction
|
||||
OpenGroupAPI.workQueue.async {
|
||||
OpenGroupAPI.workQueue.async(using: dependencies) {
|
||||
// Start the poller if needed
|
||||
if dependencies.cache.pollers[server.lowercased()] == nil {
|
||||
dependencies.mutableCache.mutate {
|
||||
if dependencies.caches[.openGroupManager].pollers[server.lowercased()] == nil {
|
||||
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||
$0.pollers[server.lowercased()]?.stop()
|
||||
$0.pollers[server.lowercased()] = OpenGroupAPI.Poller(for: server.lowercased())
|
||||
}
|
||||
|
||||
dependencies.cache.pollers[server.lowercased()]?.startIfNeeded(using: dependencies)
|
||||
dependencies.caches[.openGroupManager].pollers[server.lowercased()]?
|
||||
.startIfNeeded(using: dependencies)
|
||||
}
|
||||
|
||||
/// Start downloading the room image (if we don't have one or it's been updated)
|
||||
|
@ -553,8 +530,8 @@ public final class OpenGroupManager {
|
|||
)
|
||||
// Note: We need to subscribe and receive on different threads to ensure the
|
||||
// logic in 'receiveValue' doesn't result in a reentrancy database issue
|
||||
.subscribe(on: OpenGroupAPI.workQueue)
|
||||
.receive(on: DispatchQueue.global(qos: .default))
|
||||
.subscribe(on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
.receive(on: DispatchQueue.global(qos: .default), using: dependencies)
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { _ in
|
||||
if waitForImageToComplete {
|
||||
|
@ -562,7 +539,7 @@ public final class OpenGroupManager {
|
|||
}
|
||||
},
|
||||
receiveValue: { data in
|
||||
dependencies.storage.write { db in
|
||||
dependencies.storage.write(using: dependencies) { db in
|
||||
_ = try OpenGroup
|
||||
.filter(id: threadId)
|
||||
.updateAll(db, OpenGroup.Columns.imageData.set(to: data))
|
||||
|
@ -588,7 +565,7 @@ public final class OpenGroupManager {
|
|||
messages: [OpenGroupAPI.Message],
|
||||
for roomToken: String,
|
||||
on server: String,
|
||||
dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard let openGroup: OpenGroup = try? OpenGroup.fetchOne(db, id: OpenGroup.idFor(roomToken: roomToken, server: server)) else {
|
||||
SNLog("Couldn't handle open group messages.")
|
||||
|
@ -623,7 +600,7 @@ public final class OpenGroupManager {
|
|||
openGroupServerPublicKey: openGroup.publicKey,
|
||||
message: message,
|
||||
data: data,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
if let messageInfo: MessageReceiveJob.Details.MessageInfo = processedMessage?.messageInfo {
|
||||
|
@ -634,7 +611,7 @@ public final class OpenGroupManager {
|
|||
message: messageInfo.message,
|
||||
serverExpirationTimestamp: messageInfo.serverExpirationTimestamp,
|
||||
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
largestValidSeqNo = max(largestValidSeqNo, message.seqNo)
|
||||
}
|
||||
|
@ -661,7 +638,7 @@ public final class OpenGroupManager {
|
|||
db,
|
||||
openGroupId: openGroup.id,
|
||||
message: message,
|
||||
associatedPendingChanges: dependencies.cache.pendingChanges
|
||||
associatedPendingChanges: dependencies.caches[.openGroupManager].pendingChanges
|
||||
.filter {
|
||||
guard $0.server == server && $0.room == roomToken && $0.changeType == .reaction else {
|
||||
return false
|
||||
|
@ -672,7 +649,7 @@ public final class OpenGroupManager {
|
|||
}
|
||||
return false
|
||||
},
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
try MessageReceiver.handleOpenGroupReactions(
|
||||
|
@ -708,7 +685,7 @@ public final class OpenGroupManager {
|
|||
.updateAll(db, OpenGroup.Columns.sequenceNumber.set(to: largestValidSeqNo))
|
||||
|
||||
// Update pendingChange cache based on the `largestValidSeqNo` value
|
||||
dependencies.mutableCache.mutate {
|
||||
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||
$0.pendingChanges = $0.pendingChanges
|
||||
.filter { $0.seqNo == nil || $0.seqNo! > largestValidSeqNo }
|
||||
}
|
||||
|
@ -719,7 +696,7 @@ public final class OpenGroupManager {
|
|||
messages: [OpenGroupAPI.DirectMessage],
|
||||
fromOutbox: Bool,
|
||||
on server: String,
|
||||
dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// Don't need to do anything if we have no messages (it's a valid case)
|
||||
guard !messages.isEmpty else { return }
|
||||
|
@ -762,7 +739,7 @@ public final class OpenGroupManager {
|
|||
data: messageData,
|
||||
isOutgoing: fromOutbox,
|
||||
otherBlindedPublicKey: (fromOutbox ? message.recipient : message.sender),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// We want to update the BlindedIdLookup cache with the message info so we can avoid using the
|
||||
|
@ -788,7 +765,7 @@ public final class OpenGroupManager {
|
|||
openGroupServer: server.lowercased(),
|
||||
openGroupPublicKey: openGroup.publicKey,
|
||||
isCheckingForOutbox: fromOutbox,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}()
|
||||
lookupCache[message.recipient] = lookup
|
||||
|
@ -817,7 +794,7 @@ public final class OpenGroupManager {
|
|||
message: messageInfo.message,
|
||||
serverExpirationTimestamp: messageInfo.serverExpirationTimestamp,
|
||||
associatedWithProto: try SNProtoContent.parseData(messageInfo.serializedProtoData),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -846,7 +823,7 @@ public final class OpenGroupManager {
|
|||
in roomToken: String,
|
||||
on server: String,
|
||||
type: OpenGroupAPI.PendingChange.ReactAction,
|
||||
using dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> OpenGroupAPI.PendingChange {
|
||||
let pendingChange = OpenGroupAPI.PendingChange(
|
||||
server: server,
|
||||
|
@ -859,7 +836,7 @@ public final class OpenGroupManager {
|
|||
)
|
||||
)
|
||||
|
||||
dependencies.mutableCache.mutate {
|
||||
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||
$0.pendingChanges.append(pendingChange)
|
||||
}
|
||||
|
||||
|
@ -869,9 +846,9 @@ public final class OpenGroupManager {
|
|||
public static func updatePendingChange(
|
||||
_ pendingChange: OpenGroupAPI.PendingChange,
|
||||
seqNo: Int64?,
|
||||
using dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
dependencies.mutableCache.mutate {
|
||||
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||
if let index = $0.pendingChanges.firstIndex(of: pendingChange) {
|
||||
$0.pendingChanges[index].seqNo = seqNo
|
||||
}
|
||||
|
@ -880,9 +857,9 @@ public final class OpenGroupManager {
|
|||
|
||||
public static func removePendingChange(
|
||||
_ pendingChange: OpenGroupAPI.PendingChange,
|
||||
using dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
dependencies.mutableCache.mutate {
|
||||
dependencies.caches.mutate(cache: .openGroupManager) {
|
||||
if let index = $0.pendingChanges.firstIndex(of: pendingChange) {
|
||||
$0.pendingChanges.remove(at: index)
|
||||
}
|
||||
|
@ -894,7 +871,7 @@ public final class OpenGroupManager {
|
|||
_ db: Database? = nil,
|
||||
capability: Capability.Variant,
|
||||
on server: String?,
|
||||
using dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Bool {
|
||||
guard let server: String = server else { return false }
|
||||
guard let db: Database = db else {
|
||||
|
@ -919,7 +896,7 @@ public final class OpenGroupManager {
|
|||
_ publicKey: String,
|
||||
for roomToken: String?,
|
||||
on server: String?,
|
||||
using dependencies: OGMDependencies = OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> Bool {
|
||||
guard let roomToken: String = roomToken, let server: String = server else { return false }
|
||||
|
||||
|
@ -943,7 +920,7 @@ public final class OpenGroupManager {
|
|||
|
||||
// Conveniently the logic for these different cases works in order so we can fallthrough each
|
||||
// case with only minor efficiency losses
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
|
||||
switch sessionId.prefix {
|
||||
case .standard:
|
||||
|
@ -967,10 +944,12 @@ public final class OpenGroupManager {
|
|||
.filter(id: groupId)
|
||||
.asRequest(of: String.self)
|
||||
.fetchOne(db),
|
||||
let blindedKeyPair: KeyPair = dependencies.sodium.blindedKeyPair(
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
edKeyPair: userEdKeyPair,
|
||||
genericHash: dependencies.genericHash
|
||||
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
edKeyPair: userEdKeyPair,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
else { return false }
|
||||
guard
|
||||
|
@ -1005,19 +984,16 @@ public final class OpenGroupManager {
|
|||
}
|
||||
|
||||
@discardableResult public static func getDefaultRoomsIfNeeded(
|
||||
using dependencies: OGMDependencies = OGMDependencies(
|
||||
subscribeQueue: OpenGroupAPI.workQueue,
|
||||
receiveQueue: OpenGroupAPI.workQueue
|
||||
)
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<[DefaultRoomInfo], Error> {
|
||||
// Note: If we already have a 'defaultRoomsPromise' then there is no need to get it again
|
||||
if let existingPublisher: AnyPublisher<[DefaultRoomInfo], Error> = dependencies.cache.defaultRoomsPublisher {
|
||||
if let existingPublisher: AnyPublisher<[DefaultRoomInfo], Error> = dependencies.caches[.openGroupManager].defaultRoomsPublisher {
|
||||
return existingPublisher
|
||||
}
|
||||
|
||||
// Try to retrieve the default rooms 8 times
|
||||
let publisher: AnyPublisher<[DefaultRoomInfo], Error> = dependencies.storage
|
||||
.readPublisher { db in
|
||||
.readPublisher { db -> OpenGroupAPI.PreparedSendData<OpenGroupAPI.CapabilitiesAndRoomsResponse> in
|
||||
try OpenGroupAPI.preparedCapabilitiesAndRooms(
|
||||
db,
|
||||
on: OpenGroupAPI.defaultServer,
|
||||
|
@ -1025,9 +1001,9 @@ public final class OpenGroupManager {
|
|||
)
|
||||
}
|
||||
.flatMap { OpenGroupAPI.send(data: $0, using: dependencies) }
|
||||
.subscribe(on: dependencies.subscribeQueue)
|
||||
.receive(on: dependencies.receiveQueue)
|
||||
.retry(8)
|
||||
.subscribe(on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
.receive(on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
.retry(8, using: dependencies)
|
||||
.map { info, response -> [DefaultRoomInfo]? in
|
||||
dependencies.storage.write { db -> [DefaultRoomInfo] in
|
||||
// Store the capabilities first
|
||||
|
@ -1092,7 +1068,7 @@ public final class OpenGroupManager {
|
|||
switch result {
|
||||
case .finished: break
|
||||
case .failure:
|
||||
dependencies.mutableCache.mutate { cache in
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||
cache.defaultRoomsPublisher = nil
|
||||
}
|
||||
}
|
||||
|
@ -1101,7 +1077,7 @@ public final class OpenGroupManager {
|
|||
.shareReplay(1)
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
dependencies.mutableCache.mutate { cache in
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||
cache.defaultRoomsPublisher = publisher
|
||||
}
|
||||
|
||||
|
@ -1116,9 +1092,7 @@ public final class OpenGroupManager {
|
|||
for roomToken: String,
|
||||
on server: String,
|
||||
existingData: Data?,
|
||||
using dependencies: OGMDependencies = OGMDependencies(
|
||||
subscribeQueue: .global(qos: .background)
|
||||
)
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Data, Error> {
|
||||
// Normally the image for a given group is stored with the group thread, so it's only
|
||||
// fetched once. However, on the join open group screen we show images for groups the
|
||||
|
@ -1131,7 +1105,7 @@ public final class OpenGroupManager {
|
|||
// there is one.
|
||||
let threadId: String = OpenGroup.idFor(roomToken: roomToken, server: server)
|
||||
let lastOpenGroupImageUpdate: Date? = dependencies.standardUserDefaults[.lastOpenGroupImageUpdate]
|
||||
let now: Date = Date()
|
||||
let now: Date = dependencies.dateNow
|
||||
let timeSinceLastUpdate: TimeInterval = (lastOpenGroupImageUpdate.map { now.timeIntervalSince($0) } ?? .greatestFiniteMagnitude)
|
||||
let updateInterval: TimeInterval = (7 * 24 * 60 * 60)
|
||||
let canUseExistingImage: Bool = (
|
||||
|
@ -1145,14 +1119,14 @@ public final class OpenGroupManager {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
if let publisher: AnyPublisher<Data, Error> = dependencies.cache.groupImagePublishers[threadId] {
|
||||
if let publisher: AnyPublisher<Data, Error> = dependencies.caches[.openGroupManager].groupImagePublishers[threadId] {
|
||||
return publisher
|
||||
}
|
||||
|
||||
// Defer the actual download and run it on a separate thread to avoid blocking the calling thread
|
||||
let publisher: AnyPublisher<Data, Error> = Deferred {
|
||||
Future { resolver in
|
||||
dependencies.subscribeQueue.async {
|
||||
DispatchQueue.global(qos: .background).async(using: dependencies) {
|
||||
// Hold on to the publisher until it has completed at least once
|
||||
dependencies.storage
|
||||
.readPublisher { db -> (Data?, OpenGroupAPI.PreparedSendData<Data>?) in
|
||||
|
@ -1226,10 +1200,10 @@ public final class OpenGroupManager {
|
|||
// Automatically subscribe for the roomImage download (want to download regardless of
|
||||
// whether the upstream subscribes)
|
||||
publisher
|
||||
.subscribe(on: dependencies.subscribeQueue)
|
||||
.subscribe(on: DispatchQueue.global(qos: .background), using: dependencies)
|
||||
.sinkUntilComplete()
|
||||
|
||||
dependencies.mutableCache.mutate { cache in
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||
cache.groupImagePublishers[threadId] = publisher
|
||||
}
|
||||
|
||||
|
@ -1237,9 +1211,65 @@ public final class OpenGroupManager {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - OpenGroupManager Cache
|
||||
|
||||
public extension OpenGroupManager {
|
||||
class Cache: OGMCacheType {
|
||||
public var defaultRoomsPublisher: AnyPublisher<[DefaultRoomInfo], Error>?
|
||||
public var groupImagePublishers: [String: AnyPublisher<Data, Error>] = [:]
|
||||
|
||||
public var pollers: [String: OpenGroupAPI.Poller] = [:] // One for each server
|
||||
public var isPolling: Bool = false
|
||||
|
||||
/// Server URL to value
|
||||
public var hasPerformedInitialPoll: [String: Bool] = [:]
|
||||
public var timeSinceLastPoll: [String: TimeInterval] = [:]
|
||||
|
||||
fileprivate var _timeSinceLastOpen: TimeInterval?
|
||||
public func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval {
|
||||
if let storedTimeSinceLastOpen: TimeInterval = _timeSinceLastOpen {
|
||||
return storedTimeSinceLastOpen
|
||||
}
|
||||
|
||||
guard let lastOpen: Date = dependencies.standardUserDefaults[.lastOpen] else {
|
||||
_timeSinceLastOpen = .greatestFiniteMagnitude
|
||||
return .greatestFiniteMagnitude
|
||||
}
|
||||
|
||||
_timeSinceLastOpen = dependencies.dateNow.timeIntervalSince(lastOpen)
|
||||
return dependencies.dateNow.timeIntervalSince(lastOpen)
|
||||
}
|
||||
|
||||
public var pendingChanges: [OpenGroupAPI.PendingChange] = []
|
||||
}
|
||||
}
|
||||
|
||||
public extension Cache {
|
||||
static let openGroupManager: CacheInfo.Config<OGMCacheType, OGMImmutableCacheType> = CacheInfo.create(
|
||||
createInstance: { OpenGroupManager.Cache() },
|
||||
mutableInstance: { $0 },
|
||||
immutableInstance: { $0 }
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: - OGMCacheType
|
||||
|
||||
public protocol OGMMutableCacheType: OGMCacheType {
|
||||
/// This is a read-only version of the `OpenGroupManager.Cache` designed to avoid unintentionally mutating the instance in a
|
||||
/// non-thread-safe way
|
||||
public protocol OGMImmutableCacheType: ImmutableCacheType {
|
||||
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { get }
|
||||
var groupImagePublishers: [String: AnyPublisher<Data, Error>] { get }
|
||||
|
||||
var pollers: [String: OpenGroupAPI.Poller] { get }
|
||||
var isPolling: Bool { get }
|
||||
|
||||
var hasPerformedInitialPoll: [String: Bool] { get }
|
||||
var timeSinceLastPoll: [String: TimeInterval] { get }
|
||||
|
||||
var pendingChanges: [OpenGroupAPI.PendingChange] { get }
|
||||
}
|
||||
|
||||
public protocol OGMCacheType: OGMImmutableCacheType, MutableCacheType {
|
||||
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { get set }
|
||||
var groupImagePublishers: [String: AnyPublisher<Data, Error>] { get set }
|
||||
|
||||
|
@ -1253,90 +1283,3 @@ public protocol OGMMutableCacheType: OGMCacheType {
|
|||
|
||||
func getTimeSinceLastOpen(using dependencies: Dependencies) -> TimeInterval
|
||||
}
|
||||
|
||||
/// This is a read-only version of the `OGMMutableCacheType` designed to avoid unintentionally mutating the instance in a
|
||||
/// non-thread-safe way
|
||||
public protocol OGMCacheType {
|
||||
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? { get }
|
||||
var groupImagePublishers: [String: AnyPublisher<Data, Error>] { get }
|
||||
|
||||
var pollers: [String: OpenGroupAPI.Poller] { get }
|
||||
var isPolling: Bool { get }
|
||||
|
||||
var hasPerformedInitialPoll: [String: Bool] { get }
|
||||
var timeSinceLastPoll: [String: TimeInterval] { get }
|
||||
|
||||
var pendingChanges: [OpenGroupAPI.PendingChange] { get }
|
||||
}
|
||||
|
||||
// MARK: - OGMDependencies
|
||||
|
||||
extension OpenGroupManager {
|
||||
public class OGMDependencies: SMKDependencies {
|
||||
/// These should not be accessed directly but rather via an instance of this type
|
||||
private static let _cacheInstance: OGMMutableCacheType = OpenGroupManager.Cache()
|
||||
private static let _cacheInstanceAccessQueue = DispatchQueue(label: "OGMCacheInstanceAccess")
|
||||
|
||||
internal var _mutableCache: Atomic<OGMMutableCacheType?>
|
||||
public var mutableCache: Atomic<OGMMutableCacheType> {
|
||||
get {
|
||||
Dependencies.getMutableValueSettingIfNull(&_mutableCache) {
|
||||
OGMDependencies._cacheInstanceAccessQueue.sync { OGMDependencies._cacheInstance }
|
||||
}
|
||||
}
|
||||
}
|
||||
public var cache: OGMCacheType {
|
||||
get {
|
||||
Dependencies.getValueSettingIfNull(&_mutableCache) {
|
||||
OGMDependencies._cacheInstanceAccessQueue.sync { OGMDependencies._cacheInstance }
|
||||
}
|
||||
}
|
||||
set {
|
||||
guard let mutableValue: OGMMutableCacheType = newValue as? OGMMutableCacheType else { return }
|
||||
|
||||
_mutableCache.mutate { $0 = mutableValue }
|
||||
}
|
||||
}
|
||||
|
||||
public init(
|
||||
subscribeQueue: DispatchQueue? = nil,
|
||||
receiveQueue: DispatchQueue? = nil,
|
||||
cache: OGMMutableCacheType? = nil,
|
||||
onionApi: OnionRequestAPIType.Type? = nil,
|
||||
generalCache: MutableGeneralCacheType? = nil,
|
||||
storage: Storage? = nil,
|
||||
scheduler: ValueObservationScheduler? = nil,
|
||||
sodium: SodiumType? = nil,
|
||||
box: BoxType? = nil,
|
||||
genericHash: GenericHashType? = nil,
|
||||
sign: SignType? = nil,
|
||||
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
|
||||
ed25519: Ed25519Type? = nil,
|
||||
nonceGenerator16: NonceGenerator16ByteType? = nil,
|
||||
nonceGenerator24: NonceGenerator24ByteType? = nil,
|
||||
standardUserDefaults: UserDefaultsType? = nil,
|
||||
date: Date? = nil
|
||||
) {
|
||||
_mutableCache = Atomic(cache)
|
||||
|
||||
super.init(
|
||||
subscribeQueue: subscribeQueue,
|
||||
receiveQueue: receiveQueue,
|
||||
onionApi: onionApi,
|
||||
generalCache: generalCache,
|
||||
storage: storage,
|
||||
scheduler: scheduler,
|
||||
sodium: sodium,
|
||||
box: box,
|
||||
genericHash: genericHash,
|
||||
sign: sign,
|
||||
aeadXChaCha20Poly1305Ietf: aeadXChaCha20Poly1305Ietf,
|
||||
ed25519: ed25519,
|
||||
nonceGenerator16: nonceGenerator16,
|
||||
nonceGenerator24: nonceGenerator24,
|
||||
standardUserDefaults: standardUserDefaults,
|
||||
date: date
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Sodium
|
||||
|
||||
public protocol NonceGenerator16ByteType {
|
||||
var NonceBytes: Int { get }
|
||||
|
||||
func nonce() -> Array<UInt8>
|
||||
}
|
||||
|
||||
public protocol NonceGenerator24ByteType {
|
||||
var NonceBytes: Int { get }
|
||||
|
||||
func nonce() -> Array<UInt8>
|
||||
}
|
||||
|
||||
extension OpenGroupAPI {
|
||||
public class NonceGenerator16Byte: NonceGenerator, NonceGenerator16ByteType {
|
||||
public var NonceBytes: Int { 16 }
|
||||
}
|
||||
|
||||
public class NonceGenerator24Byte: NonceGenerator, NonceGenerator24ByteType {
|
||||
public var NonceBytes: Int { 24 }
|
||||
}
|
||||
}
|
|
@ -29,7 +29,7 @@ public extension OpenGroupAPI {
|
|||
private let method: HTTPMethod
|
||||
private let path: String
|
||||
public let endpoint: Endpoint
|
||||
fileprivate let batchEndpoints: [Endpoint]
|
||||
internal let batchEndpoints: [Endpoint]
|
||||
public let batchResponseTypes: [Decodable.Type]
|
||||
|
||||
/// The `jsonBodyEncoder` is used to simplify the encoding for `BatchRequest`
|
||||
|
@ -185,7 +185,7 @@ public extension OpenGroupAPI.PreparedSendData {
|
|||
public extension Publisher where Output == (ResponseInfoType, Data?), Failure == Error {
|
||||
func decoded<R>(
|
||||
with preparedData: OpenGroupAPI.PreparedSendData<R>,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<(ResponseInfoType, R), Error> {
|
||||
self
|
||||
.tryMap { responseInfo, maybeData -> (ResponseInfoType, R) in
|
||||
|
|
|
@ -1,109 +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 }
|
||||
var NonceBytes: Int { get }
|
||||
|
||||
func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes?
|
||||
func decrypt(authenticatedCipherText: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes?
|
||||
}
|
||||
|
||||
public protocol Ed25519Type {
|
||||
func sign(data: Bytes, keyPair: KeyPair) throws -> Bytes?
|
||||
func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool
|
||||
}
|
||||
|
||||
public protocol BoxType {
|
||||
func seal(message: Bytes, recipientPublicKey: Bytes) -> Bytes?
|
||||
func open(anonymousCipherText: Bytes, recipientPublicKey: Bytes, recipientSecretKey: Bytes) -> Bytes?
|
||||
}
|
||||
|
||||
public protocol GenericHashType {
|
||||
func hash(message: Bytes, key: Bytes?) -> Bytes?
|
||||
func hash(message: Bytes, outputLength: Int) -> Bytes?
|
||||
func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes?
|
||||
}
|
||||
|
||||
public protocol SignType {
|
||||
var Bytes: Int { get }
|
||||
var PublicKeyBytes: Int { get }
|
||||
|
||||
func toX25519(ed25519PublicKey: Bytes) -> Bytes?
|
||||
func signature(message: Bytes, secretKey: Bytes) -> Bytes?
|
||||
func verify(message: Bytes, publicKey: Bytes, signature: Bytes) -> Bool
|
||||
}
|
||||
|
||||
// MARK: - Default Values
|
||||
|
||||
extension GenericHashType {
|
||||
func hash(message: Bytes) -> Bytes? { return hash(message: message, key: nil) }
|
||||
|
||||
func hashSaltPersonal(message: Bytes, outputLength: Int, salt: Bytes, personal: Bytes) -> Bytes? {
|
||||
return hashSaltPersonal(message: message, outputLength: outputLength, key: nil, salt: salt, personal: personal)
|
||||
}
|
||||
}
|
||||
|
||||
extension AeadXChaCha20Poly1305IetfType {
|
||||
func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes) -> Bytes? {
|
||||
return encrypt(message: message, secretKey: secretKey, nonce: nonce, additionalData: nil)
|
||||
}
|
||||
|
||||
func decrypt(authenticatedCipherText: Bytes, secretKey: Bytes, nonce: Bytes) -> Bytes? {
|
||||
return decrypt(authenticatedCipherText: authenticatedCipherText, secretKey: secretKey, nonce: nonce, additionalData: nil)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Conformance
|
||||
|
||||
extension Sodium: SodiumType {
|
||||
public func getBox() -> BoxType { return box }
|
||||
public func getGenericHash() -> GenericHashType { return genericHash }
|
||||
public func getSign() -> SignType { return sign }
|
||||
public func getAeadXChaCha20Poly1305Ietf() -> AeadXChaCha20Poly1305IetfType { return aead.xchacha20poly1305ietf }
|
||||
|
||||
public func blindedKeyPair(serverPublicKey: String, edKeyPair: KeyPair) -> KeyPair? {
|
||||
return blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: edKeyPair, genericHash: getGenericHash())
|
||||
}
|
||||
}
|
||||
|
||||
extension Box: BoxType {}
|
||||
extension GenericHash: GenericHashType {}
|
||||
extension Sign: SignType {}
|
||||
extension Aead.XChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {}
|
||||
|
||||
struct Ed25519Wrapper: Ed25519Type {
|
||||
func sign(data: Bytes, keyPair: KeyPair) throws -> Bytes? {
|
||||
let ecKeyPair: ECKeyPair = try ECKeyPair(
|
||||
publicKeyData: Data(keyPair.publicKey),
|
||||
privateKeyData: Data(keyPair.secretKey)
|
||||
)
|
||||
|
||||
return try Ed25519.sign(Data(data), with: ecKeyPair).bytes
|
||||
}
|
||||
|
||||
func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool {
|
||||
return try Ed25519.verifySignature(signature, publicKey: publicKey, data: data)
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import Sodium
|
||||
import SessionSnodeKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
public class SMKDependencies: SSKDependencies {
|
||||
internal var _sodium: Atomic<SodiumType?>
|
||||
public var sodium: SodiumType {
|
||||
get { Dependencies.getValueSettingIfNull(&_sodium) { Sodium() } }
|
||||
set { _sodium.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _box: Atomic<BoxType?>
|
||||
public var box: BoxType {
|
||||
get { Dependencies.getValueSettingIfNull(&_box) { sodium.getBox() } }
|
||||
set { _box.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _genericHash: Atomic<GenericHashType?>
|
||||
public var genericHash: GenericHashType {
|
||||
get { Dependencies.getValueSettingIfNull(&_genericHash) { sodium.getGenericHash() } }
|
||||
set { _genericHash.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _sign: Atomic<SignType?>
|
||||
public var sign: SignType {
|
||||
get { Dependencies.getValueSettingIfNull(&_sign) { sodium.getSign() } }
|
||||
set { _sign.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _aeadXChaCha20Poly1305Ietf: Atomic<AeadXChaCha20Poly1305IetfType?>
|
||||
public var aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType {
|
||||
get { Dependencies.getValueSettingIfNull(&_aeadXChaCha20Poly1305Ietf) { sodium.getAeadXChaCha20Poly1305Ietf() } }
|
||||
set { _aeadXChaCha20Poly1305Ietf.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _ed25519: Atomic<Ed25519Type?>
|
||||
public var ed25519: Ed25519Type {
|
||||
get { Dependencies.getValueSettingIfNull(&_ed25519) { Ed25519Wrapper() } }
|
||||
set { _ed25519.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _nonceGenerator16: Atomic<NonceGenerator16ByteType?>
|
||||
public var nonceGenerator16: NonceGenerator16ByteType {
|
||||
get { Dependencies.getValueSettingIfNull(&_nonceGenerator16) { OpenGroupAPI.NonceGenerator16Byte() } }
|
||||
set { _nonceGenerator16.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
internal var _nonceGenerator24: Atomic<NonceGenerator24ByteType?>
|
||||
public var nonceGenerator24: NonceGenerator24ByteType {
|
||||
get { Dependencies.getValueSettingIfNull(&_nonceGenerator24) { OpenGroupAPI.NonceGenerator24Byte() } }
|
||||
set { _nonceGenerator24.mutate { $0 = newValue } }
|
||||
}
|
||||
|
||||
// MARK: - Initialization
|
||||
|
||||
public init(
|
||||
subscribeQueue: DispatchQueue? = nil,
|
||||
receiveQueue: DispatchQueue? = nil,
|
||||
onionApi: OnionRequestAPIType.Type? = nil,
|
||||
generalCache: MutableGeneralCacheType? = nil,
|
||||
storage: Storage? = nil,
|
||||
scheduler: ValueObservationScheduler? = nil,
|
||||
sodium: SodiumType? = nil,
|
||||
box: BoxType? = nil,
|
||||
genericHash: GenericHashType? = nil,
|
||||
sign: SignType? = nil,
|
||||
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
|
||||
ed25519: Ed25519Type? = nil,
|
||||
nonceGenerator16: NonceGenerator16ByteType? = nil,
|
||||
nonceGenerator24: NonceGenerator24ByteType? = nil,
|
||||
standardUserDefaults: UserDefaultsType? = nil,
|
||||
date: Date? = nil
|
||||
) {
|
||||
_sodium = Atomic(sodium)
|
||||
_box = Atomic(box)
|
||||
_genericHash = Atomic(genericHash)
|
||||
_sign = Atomic(sign)
|
||||
_aeadXChaCha20Poly1305Ietf = Atomic(aeadXChaCha20Poly1305Ietf)
|
||||
_ed25519 = Atomic(ed25519)
|
||||
_nonceGenerator16 = Atomic(nonceGenerator16)
|
||||
_nonceGenerator24 = Atomic(nonceGenerator24)
|
||||
|
||||
super.init(
|
||||
subscribeQueue: subscribeQueue,
|
||||
receiveQueue: receiveQueue,
|
||||
onionApi: onionApi,
|
||||
generalCache: generalCache,
|
||||
storage: storage,
|
||||
scheduler: scheduler,
|
||||
standardUserDefaults: standardUserDefaults,
|
||||
date: date
|
||||
)
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ public enum MessageSenderError: LocalizedError, Equatable {
|
|||
case encryptionFailed
|
||||
case noUsername
|
||||
case attachmentsNotUploaded
|
||||
case blindingFailed
|
||||
|
||||
// Closed groups
|
||||
case noThread
|
||||
|
@ -21,7 +22,10 @@ public enum MessageSenderError: LocalizedError, Equatable {
|
|||
|
||||
internal var isRetryable: Bool {
|
||||
switch self {
|
||||
case .invalidMessage, .protoConversionFailed, .invalidClosedGroupUpdate, .signingFailed, .encryptionFailed: return false
|
||||
case .invalidMessage, .protoConversionFailed, .invalidClosedGroupUpdate,
|
||||
.signingFailed, .encryptionFailed, .blindingFailed:
|
||||
return false
|
||||
|
||||
default: return true
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +40,7 @@ public enum MessageSenderError: LocalizedError, Equatable {
|
|||
case .encryptionFailed: return "Couldn't encrypt message."
|
||||
case .noUsername: return "Missing username."
|
||||
case .attachmentsNotUploaded: return "Attachments for this message have not been uploaded."
|
||||
case .blindingFailed: return "Couldn't blind the sender"
|
||||
|
||||
// Closed groups
|
||||
case .noThread: return "Couldn't find a thread associated with the given group public key."
|
||||
|
@ -58,6 +63,7 @@ public enum MessageSenderError: LocalizedError, Equatable {
|
|||
case (.noThread, .noThread): return true
|
||||
case (.noKeyPair, .noKeyPair): return true
|
||||
case (.invalidClosedGroupUpdate, .invalidClosedGroupUpdate): return true
|
||||
case (.blindingFailed, .blindingFailed): return true
|
||||
|
||||
case (.other(let lhsError), .other(let rhsError)):
|
||||
// Not ideal but the best we can do
|
||||
|
|
|
@ -194,7 +194,11 @@ extension MessageReceiver {
|
|||
|
||||
// MARK: - Convenience
|
||||
|
||||
public static func handleIncomingCallOfferInBusyState(_ db: Database, message: CallMessage) throws {
|
||||
public static func handleIncomingCallOfferInBusyState(
|
||||
_ db: Database,
|
||||
message: CallMessage,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
let messageInfo: CallMessage.MessageInfo = CallMessage.MessageInfo(state: .missed)
|
||||
|
||||
guard
|
||||
|
@ -229,7 +233,7 @@ extension MessageReceiver {
|
|||
.inserted(db)
|
||||
|
||||
MessageSender.sendImmediate(
|
||||
preparedSendData: try MessageSender
|
||||
data: try MessageSender
|
||||
.preparedSendData(
|
||||
db,
|
||||
message: CallMessage(
|
||||
|
@ -242,8 +246,10 @@ extension MessageReceiver {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: thread.id, threadVariant: thread.variant)
|
||||
.defaultNamespace,
|
||||
interactionId: nil // Explicitly nil as it's a separate message from above
|
||||
)
|
||||
interactionId: nil, // Explicitly nil as it's a separate message from above
|
||||
using: dependencies
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.sinkUntilComplete()
|
||||
|
|
|
@ -12,10 +12,11 @@ extension MessageReceiver {
|
|||
_ db: Database,
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
message: ClosedGroupControlMessage
|
||||
message: ClosedGroupControlMessage,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
switch message.kind {
|
||||
case .new: try handleNewClosedGroup(db, message: message)
|
||||
case .new: try handleNewClosedGroup(db, message: message, using: dependencies)
|
||||
|
||||
case .encryptionKeyPair:
|
||||
try handleClosedGroupEncryptionKeyPair(
|
||||
|
@ -65,7 +66,11 @@ extension MessageReceiver {
|
|||
|
||||
// MARK: - Specific Handling
|
||||
|
||||
private static func handleNewClosedGroup(_ db: Database, message: ClosedGroupControlMessage) throws {
|
||||
private static func handleNewClosedGroup(
|
||||
_ db: Database,
|
||||
message: ClosedGroupControlMessage,
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
guard case let .new(publicKeyAsData, name, encryptionKeyPair, membersAsData, adminsAsData, expirationTimer) = message.kind else {
|
||||
return
|
||||
}
|
||||
|
@ -112,7 +117,8 @@ extension MessageReceiver {
|
|||
admins: adminsAsData.map { $0.toHexString() },
|
||||
expirationTimer: expirationTimer,
|
||||
formationTimestampMs: sentTimestamp,
|
||||
calledFromConfigHandling: false
|
||||
calledFromConfigHandling: false,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -125,7 +131,8 @@ extension MessageReceiver {
|
|||
admins: [String],
|
||||
expirationTimer: UInt32,
|
||||
formationTimestampMs: UInt64,
|
||||
calledFromConfigHandling: Bool
|
||||
calledFromConfigHandling: Bool,
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
// With new closed groups we only want to create them if the admin creating the closed group is an
|
||||
// approved contact (to prevent spam via closed groups getting around message requests if users are
|
||||
|
@ -222,7 +229,7 @@ extension MessageReceiver {
|
|||
}
|
||||
|
||||
// Start polling
|
||||
ClosedGroupPoller.shared.startIfNeeded(for: groupPublicKey)
|
||||
ClosedGroupPoller.shared.startIfNeeded(for: groupPublicKey, using: dependencies)
|
||||
|
||||
// Resubscribe for group push notifications
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
|
|
|
@ -7,7 +7,11 @@ import SessionUIKit
|
|||
import SessionUtilitiesKit
|
||||
|
||||
extension MessageReceiver {
|
||||
internal static func handleLegacyConfigurationMessage(_ db: Database, message: ConfigurationMessage) throws {
|
||||
internal static func handleLegacyConfigurationMessage(
|
||||
_ db: Database,
|
||||
message: ConfigurationMessage,
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
// FIXME: Remove this once `useSharedUtilForUserConfig` is permanent
|
||||
guard !SessionUtil.userConfigsEnabled(db) else {
|
||||
TopBannerController.show(warning: .outdatedUserConfig)
|
||||
|
@ -46,7 +50,8 @@ extension MessageReceiver {
|
|||
)
|
||||
}(),
|
||||
sentTimestamp: messageSentTimestamp,
|
||||
calledFromConfigHandling: true
|
||||
calledFromConfigHandling: true,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Create a contact for the current user if needed (also force-approve the current user
|
||||
|
@ -192,7 +197,8 @@ extension MessageReceiver {
|
|||
admins: [String](closedGroup.admins),
|
||||
expirationTimer: closedGroup.expirationTimer,
|
||||
formationTimestampMs: message.sentTimestamp!,
|
||||
calledFromConfigHandling: false // Legacy config isn't an issue
|
||||
calledFromConfigHandling: false, // Legacy config isn't an issue
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ extension MessageReceiver {
|
|||
internal static func handleMessageRequestResponse(
|
||||
_ db: Database,
|
||||
message: MessageRequestResponse,
|
||||
dependencies: SMKDependencies
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
let userPublicKey = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let userPublicKey = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
var blindedContactIds: [String] = []
|
||||
|
||||
// Ignore messages which were sent from the current user
|
||||
|
@ -42,7 +42,8 @@ extension MessageReceiver {
|
|||
fileName: nil
|
||||
)
|
||||
}(),
|
||||
sentTimestamp: messageSentTimestamp
|
||||
sentTimestamp: messageSentTimestamp,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -73,11 +74,13 @@ extension MessageReceiver {
|
|||
// If the sessionId matches the blindedId then this thread needs to be converted to an
|
||||
// un-blinded thread
|
||||
guard
|
||||
dependencies.sodium.sessionId(
|
||||
senderId,
|
||||
matchesBlindedId: blindedIdLookup.blindedId,
|
||||
serverPublicKey: blindedIdLookup.openGroupPublicKey,
|
||||
genericHash: dependencies.genericHash
|
||||
dependencies.crypto.verify(
|
||||
.sessionId(
|
||||
senderId,
|
||||
matchesBlindedId: blindedIdLookup.blindedId,
|
||||
serverPublicKey: blindedIdLookup.openGroupPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
else { return }
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ extension MessageReceiver {
|
|||
threadVariant: SessionThread.Variant,
|
||||
message: VisibleMessage,
|
||||
associatedWithProto proto: SNProtoContent,
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> Int64 {
|
||||
guard let sender: String = message.sender, let dataMessage = proto.dataMessage else {
|
||||
throw MessageReceiverError.invalidMessage
|
||||
|
@ -43,7 +43,8 @@ extension MessageReceiver {
|
|||
fileName: nil
|
||||
)
|
||||
}(),
|
||||
sentTimestamp: messageSentTimestamp
|
||||
sentTimestamp: messageSentTimestamp,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -64,7 +65,7 @@ extension MessageReceiver {
|
|||
}
|
||||
|
||||
// Store the message variant so we can run variant-specific behaviours
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let thread: SessionThread = try SessionThread
|
||||
.fetchOrCreate(db, id: threadId, variant: threadVariant, shouldBeVisible: nil)
|
||||
let maybeOpenGroup: OpenGroup? = {
|
||||
|
@ -90,10 +91,12 @@ extension MessageReceiver {
|
|||
|
||||
guard
|
||||
let userEdKeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db),
|
||||
let blindedKeyPair: KeyPair = sodium.blindedKeyPair(
|
||||
serverPublicKey: openGroup.publicKey,
|
||||
edKeyPair: userEdKeyPair,
|
||||
genericHash: sodium.genericHash
|
||||
let blindedKeyPair: KeyPair = try? dependencies.crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: openGroup.publicKey,
|
||||
edKeyPair: userEdKeyPair,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
else { return .standardIncoming }
|
||||
|
||||
|
@ -167,7 +170,8 @@ extension MessageReceiver {
|
|||
db,
|
||||
threadId: thread.id,
|
||||
body: message.text,
|
||||
quoteAuthorId: dataMessage.quote?.author
|
||||
quoteAuthorId: dataMessage.quote?.author,
|
||||
using: dependencies
|
||||
),
|
||||
// Note: Ensure we don't ever expire open group messages
|
||||
expiresInSeconds: (disappearingMessagesConfiguration.isEnabled && message.openGroupServerMessageId == nil ?
|
||||
|
@ -298,7 +302,7 @@ extension MessageReceiver {
|
|||
.appending(quote?.attachmentId)
|
||||
.appending(linkPreview?.attachmentId)
|
||||
.forEach { attachmentId in
|
||||
JobRunner.add(
|
||||
dependencies.jobRunner.add(
|
||||
db,
|
||||
job: Job(
|
||||
variant: .attachmentDownload,
|
||||
|
@ -308,7 +312,8 @@ extension MessageReceiver {
|
|||
attachmentId: attachmentId
|
||||
)
|
||||
),
|
||||
canStartJob: isMainAppActive
|
||||
canStartJob: isMainAppActive,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import Foundation
|
|||
import Combine
|
||||
import GRDB
|
||||
import Sodium
|
||||
import Curve25519Kit
|
||||
import SessionUtilitiesKit
|
||||
import SessionSnodeKit
|
||||
|
||||
|
@ -13,21 +12,21 @@ extension MessageSender {
|
|||
|
||||
public static func createClosedGroup(
|
||||
name: String,
|
||||
members: Set<String>
|
||||
members: Set<String>,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<SessionThread, Error> {
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.writePublisher { db -> (String, SessionThread, [MessageSender.PreparedSendData], Set<String>) in
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
var members: Set<String> = members
|
||||
// Generate the group's two keys
|
||||
guard
|
||||
let groupKeyPair: KeyPair = dependencies.crypto.generate(.x25519KeyPair()),
|
||||
let encryptionKeyPair: KeyPair = dependencies.crypto.generate(.x25519KeyPair())
|
||||
else { throw MessageSenderError.noKeyPair }
|
||||
|
||||
// Generate the group's public key
|
||||
let groupKeyPair: ECKeyPair = Curve25519.generateKeyPair()
|
||||
let groupPublicKey: String = KeyPair(
|
||||
publicKey: groupKeyPair.publicKey.bytes,
|
||||
secretKey: groupKeyPair.privateKey.bytes
|
||||
).hexEncodedPublicKey // Includes the 'SessionId.Prefix.standard' prefix
|
||||
// Generate the key pair that'll be used for encryption and decryption
|
||||
let encryptionKeyPair: ECKeyPair = Curve25519.generateKeyPair()
|
||||
// Includes the 'SessionId.Prefix.standard' prefix
|
||||
let groupPublicKey: String = groupKeyPair.hexEncodedPublicKey
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
var members: Set<String> = members
|
||||
|
||||
// Create the group
|
||||
members.insert(userPublicKey) // Ensure the current user is included in the member list
|
||||
|
@ -49,8 +48,8 @@ extension MessageSender {
|
|||
let latestKeyPairReceivedTimestamp: TimeInterval = (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000)
|
||||
try ClosedGroupKeyPair(
|
||||
threadId: groupPublicKey,
|
||||
publicKey: encryptionKeyPair.publicKey,
|
||||
secretKey: encryptionKeyPair.privateKey,
|
||||
publicKey: Data(encryptionKeyPair.publicKey),
|
||||
secretKey: Data(encryptionKeyPair.secretKey),
|
||||
receivedTimestamp: latestKeyPairReceivedTimestamp
|
||||
).insert(db)
|
||||
|
||||
|
@ -78,8 +77,8 @@ extension MessageSender {
|
|||
db,
|
||||
groupPublicKey: groupPublicKey,
|
||||
name: name,
|
||||
latestKeyPairPublicKey: encryptionKeyPair.publicKey,
|
||||
latestKeyPairSecretKey: encryptionKeyPair.privateKey,
|
||||
latestKeyPairPublicKey: Data(encryptionKeyPair.publicKey),
|
||||
latestKeyPairSecretKey: Data(encryptionKeyPair.secretKey),
|
||||
latestKeyPairReceivedTimestamp: latestKeyPairReceivedTimestamp,
|
||||
disappearingConfig: DisappearingMessagesConfiguration.defaultWith(groupPublicKey),
|
||||
members: members,
|
||||
|
@ -94,10 +93,7 @@ extension MessageSender {
|
|||
kind: .new(
|
||||
publicKey: Data(hex: groupPublicKey),
|
||||
name: name,
|
||||
encryptionKeyPair: KeyPair(
|
||||
publicKey: encryptionKeyPair.publicKey.bytes,
|
||||
secretKey: encryptionKeyPair.privateKey.bytes
|
||||
),
|
||||
encryptionKeyPair: encryptionKeyPair,
|
||||
members: membersAsData,
|
||||
admins: adminsAsData,
|
||||
expirationTimer: 0
|
||||
|
@ -108,7 +104,8 @@ extension MessageSender {
|
|||
),
|
||||
to: .contact(publicKey: memberId),
|
||||
namespace: Message.Destination.contact(publicKey: memberId).defaultNamespace,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
let allActiveLegacyGroupIds: Set<String> = try ClosedGroup
|
||||
|
@ -129,7 +126,7 @@ extension MessageSender {
|
|||
.MergeMany(
|
||||
// Send a closed group update message to all members individually
|
||||
memberSendData
|
||||
.map { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.map { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.appending(
|
||||
// Resubscribe to all legacy groups
|
||||
PushNotificationAPI.subscribeToLegacyGroups(
|
||||
|
@ -144,7 +141,7 @@ extension MessageSender {
|
|||
.handleEvents(
|
||||
receiveOutput: { thread in
|
||||
// Start polling
|
||||
ClosedGroupPoller.shared.startIfNeeded(for: thread.id)
|
||||
ClosedGroupPoller.shared.startIfNeeded(for: thread.id, using: dependencies)
|
||||
}
|
||||
)
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -160,21 +157,25 @@ extension MessageSender {
|
|||
targetMembers: Set<String>,
|
||||
userPublicKey: String,
|
||||
allGroupMembers: [GroupMember],
|
||||
closedGroup: ClosedGroup
|
||||
closedGroup: ClosedGroup,
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard allGroupMembers.contains(where: { $0.role == .admin && $0.profileId == userPublicKey }) else {
|
||||
return Fail(error: MessageSenderError.invalidClosedGroupUpdate)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return Storage.shared
|
||||
return dependencies.storage
|
||||
.readPublisher { db -> (ClosedGroupKeyPair, MessageSender.PreparedSendData) in
|
||||
// Generate the new encryption key pair
|
||||
let legacyNewKeyPair: ECKeyPair = Curve25519.generateKeyPair()
|
||||
guard let legacyNewKeyPair: KeyPair = dependencies.crypto.generate(.x25519KeyPair()) else {
|
||||
throw MessageSenderError.noKeyPair
|
||||
}
|
||||
|
||||
let newKeyPair: ClosedGroupKeyPair = ClosedGroupKeyPair(
|
||||
threadId: closedGroup.threadId,
|
||||
publicKey: legacyNewKeyPair.publicKey,
|
||||
secretKey: legacyNewKeyPair.privateKey,
|
||||
publicKey: Data(legacyNewKeyPair.publicKey),
|
||||
secretKey: Data(legacyNewKeyPair.secretKey),
|
||||
receivedTimestamp: (TimeInterval(SnodeAPI.currentOffsetTimestampMs()) / 1000)
|
||||
)
|
||||
|
||||
|
@ -202,7 +203,8 @@ extension MessageSender {
|
|||
encryptedKeyPair: try MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
plaintext: plaintext,
|
||||
for: memberPublicKey
|
||||
for: memberPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -213,20 +215,21 @@ extension MessageSender {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup)
|
||||
.defaultNamespace,
|
||||
interactionId: nil
|
||||
interactionId: nil,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
return (newKeyPair, sendData)
|
||||
}
|
||||
.flatMap { newKeyPair, sendData -> AnyPublisher<ClosedGroupKeyPair, Error> in
|
||||
MessageSender.sendImmediate(preparedSendData: sendData)
|
||||
MessageSender.sendImmediate(data: sendData, using: dependencies)
|
||||
.map { _ in newKeyPair }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.handleEvents(
|
||||
receiveOutput: { newKeyPair in
|
||||
/// Store it **after** having sent out the message to the group
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
try newKeyPair.insert(db)
|
||||
|
||||
// Update libSession
|
||||
|
@ -260,11 +263,12 @@ extension MessageSender {
|
|||
public static func update(
|
||||
groupPublicKey: String,
|
||||
with members: Set<String>,
|
||||
name: String
|
||||
name: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
return Storage.shared
|
||||
.writePublisher { db -> (String, ClosedGroup, [GroupMember], Set<String>) in
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
|
||||
// Get the group, check preconditions & prepare
|
||||
guard (try? SessionThread.exists(db, id: groupPublicKey)) == true else {
|
||||
|
@ -301,7 +305,8 @@ extension MessageSender {
|
|||
message: ClosedGroupControlMessage(kind: .nameChange(name: name)),
|
||||
interactionId: interactionId,
|
||||
threadId: groupPublicKey,
|
||||
threadVariant: .legacyGroup
|
||||
threadVariant: .legacyGroup,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Update libSession
|
||||
|
@ -330,7 +335,8 @@ extension MessageSender {
|
|||
addedMembers: addedMembers,
|
||||
userPublicKey: userPublicKey,
|
||||
allGroupMembers: allGroupMembers,
|
||||
closedGroup: closedGroup
|
||||
closedGroup: closedGroup,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
@ -357,7 +363,8 @@ extension MessageSender {
|
|||
removedMembers: removedMembers,
|
||||
userPublicKey: userPublicKey,
|
||||
allGroupMembers: allGroupMembers,
|
||||
closedGroup: closedGroup
|
||||
closedGroup: closedGroup,
|
||||
using: dependencies
|
||||
)
|
||||
.catch { _ in Fail(error: MessageSenderError.invalidClosedGroupUpdate).eraseToAnyPublisher() }
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -373,7 +380,8 @@ extension MessageSender {
|
|||
addedMembers: Set<String>,
|
||||
userPublicKey: String,
|
||||
allGroupMembers: [GroupMember],
|
||||
closedGroup: ClosedGroup
|
||||
closedGroup: ClosedGroup,
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
guard let disappearingMessagesConfig: DisappearingMessagesConfiguration = try DisappearingMessagesConfiguration.fetchOne(db, id: closedGroup.threadId) else {
|
||||
throw StorageError.objectNotFound
|
||||
|
@ -428,7 +436,8 @@ extension MessageSender {
|
|||
),
|
||||
interactionId: interactionId,
|
||||
threadId: closedGroup.threadId,
|
||||
threadVariant: .legacyGroup
|
||||
threadVariant: .legacyGroup,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
try addedMembers.forEach { member in
|
||||
|
@ -455,7 +464,8 @@ extension MessageSender {
|
|||
),
|
||||
interactionId: nil,
|
||||
threadId: member,
|
||||
threadVariant: .contact
|
||||
threadVariant: .contact,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Add the users to the group
|
||||
|
@ -478,7 +488,8 @@ extension MessageSender {
|
|||
removedMembers: Set<String>,
|
||||
userPublicKey: String,
|
||||
allGroupMembers: [GroupMember],
|
||||
closedGroup: ClosedGroup
|
||||
closedGroup: ClosedGroup,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard !removedMembers.contains(userPublicKey) else {
|
||||
SNLog("Invalid closed group update.")
|
||||
|
@ -499,7 +510,7 @@ extension MessageSender {
|
|||
.map { $0.profileId }
|
||||
let members: Set<String> = Set(groupMemberIds).subtracting(removedMembers)
|
||||
|
||||
return Storage.shared
|
||||
return dependencies.storage
|
||||
.writePublisher { db in
|
||||
// Update zombie & member list
|
||||
try GroupMember
|
||||
|
@ -544,16 +555,18 @@ extension MessageSender {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: closedGroup.threadId, threadVariant: .legacyGroup)
|
||||
.defaultNamespace,
|
||||
interactionId: interactionId
|
||||
interactionId: interactionId,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.flatMap { MessageSender.sendImmediate(preparedSendData: $0) }
|
||||
.flatMap { MessageSender.sendImmediate(data: $0, using: dependencies) }
|
||||
.flatMap { _ -> AnyPublisher<Void, Error> in
|
||||
MessageSender.generateAndSendNewEncryptionKeyPair(
|
||||
targetMembers: members,
|
||||
userPublicKey: userPublicKey,
|
||||
allGroupMembers: allGroupMembers,
|
||||
closedGroup: closedGroup
|
||||
closedGroup: closedGroup,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -570,9 +583,10 @@ extension MessageSender {
|
|||
public static func leave(
|
||||
_ db: Database,
|
||||
groupPublicKey: String,
|
||||
deleteThread: Bool
|
||||
deleteThread: Bool,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
|
||||
// Notify the user
|
||||
let interaction: Interaction = try Interaction(
|
||||
|
@ -583,7 +597,7 @@ extension MessageSender {
|
|||
timestampMs: SnodeAPI.currentOffsetTimestampMs()
|
||||
).inserted(db)
|
||||
|
||||
JobRunner.upsert(
|
||||
dependencies.jobRunner.upsert(
|
||||
db,
|
||||
job: Job(
|
||||
variant: .groupLeaving,
|
||||
|
@ -592,14 +606,17 @@ extension MessageSender {
|
|||
details: GroupLeavingJob.Details(
|
||||
deleteThread: deleteThread
|
||||
)
|
||||
)
|
||||
),
|
||||
canStartJob: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
public static func sendLatestEncryptionKeyPair(
|
||||
_ db: Database,
|
||||
to publicKey: String,
|
||||
for groupPublicKey: String
|
||||
for groupPublicKey: String,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
guard let thread: SessionThread = try? SessionThread.fetchOne(db, id: groupPublicKey) else {
|
||||
return SNLog("Couldn't send key pair for nonexistent closed group.")
|
||||
|
@ -635,7 +652,8 @@ extension MessageSender {
|
|||
let ciphertext = try MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
plaintext: plaintext,
|
||||
for: publicKey
|
||||
for: publicKey,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
SNLog("Sending latest encryption key pair to: \(publicKey).")
|
||||
|
@ -654,7 +672,8 @@ extension MessageSender {
|
|||
),
|
||||
interactionId: nil,
|
||||
threadId: thread.id,
|
||||
threadVariant: thread.variant
|
||||
threadVariant: thread.variant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
catch {}
|
||||
|
|
|
@ -6,18 +6,24 @@ import Sodium
|
|||
import SessionUtilitiesKit
|
||||
|
||||
extension MessageReceiver {
|
||||
internal static func decryptWithSessionProtocol(ciphertext: Data, using x25519KeyPair: KeyPair, dependencies: SMKDependencies = SMKDependencies()) throws -> (plaintext: Data, senderX25519PublicKey: String) {
|
||||
let recipientX25519PrivateKey = x25519KeyPair.secretKey
|
||||
let recipientX25519PublicKey = x25519KeyPair.publicKey
|
||||
let signatureSize = dependencies.sign.Bytes
|
||||
let ed25519PublicKeySize = dependencies.sign.PublicKeyBytes
|
||||
internal static func decryptWithSessionProtocol(
|
||||
ciphertext: Data,
|
||||
using x25519KeyPair: KeyPair,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> (plaintext: Data, senderX25519PublicKey: String) {
|
||||
let recipientX25519PrivateKey: Bytes = x25519KeyPair.secretKey
|
||||
let recipientX25519PublicKey: Bytes = x25519KeyPair.publicKey
|
||||
let signatureSize: Int = dependencies.crypto.size(.signature)
|
||||
let ed25519PublicKeySize: Int = dependencies.crypto.size(.publicKey)
|
||||
|
||||
// 1. ) Decrypt the message
|
||||
guard
|
||||
let plaintextWithMetadata = dependencies.box.open(
|
||||
anonymousCipherText: Bytes(ciphertext),
|
||||
recipientPublicKey: Box.PublicKey(Bytes(recipientX25519PublicKey)),
|
||||
recipientSecretKey: Bytes(recipientX25519PrivateKey)
|
||||
let plaintextWithMetadata = try? dependencies.crypto.perform(
|
||||
.open(
|
||||
anonymousCipherText: Bytes(ciphertext),
|
||||
recipientPublicKey: Box.PublicKey(Bytes(recipientX25519PublicKey)),
|
||||
recipientSecretKey: Bytes(recipientX25519PrivateKey)
|
||||
)
|
||||
),
|
||||
plaintextWithMetadata.count > (signatureSize + ed25519PublicKeySize)
|
||||
else {
|
||||
|
@ -32,79 +38,100 @@ extension MessageReceiver {
|
|||
// 3. ) Verify the signature
|
||||
let verificationData = plaintext + senderED25519PublicKey + recipientX25519PublicKey
|
||||
|
||||
guard dependencies.sign.verify(message: verificationData, publicKey: senderED25519PublicKey, signature: signature) else {
|
||||
throw MessageReceiverError.invalidSignature
|
||||
}
|
||||
guard
|
||||
dependencies.crypto.verify(
|
||||
.signature(message: verificationData, publicKey: senderED25519PublicKey, signature: signature)
|
||||
)
|
||||
else { throw MessageReceiverError.invalidSignature }
|
||||
|
||||
// 4. ) Get the sender's X25519 public key
|
||||
guard let senderX25519PublicKey = dependencies.sign.toX25519(ed25519PublicKey: senderED25519PublicKey) else {
|
||||
throw MessageReceiverError.decryptionFailed
|
||||
}
|
||||
guard
|
||||
let senderX25519PublicKey = try? dependencies.crypto.perform(
|
||||
.toX25519(ed25519PublicKey: senderED25519PublicKey)
|
||||
)
|
||||
else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
return (Data(plaintext), SessionId(.standard, publicKey: senderX25519PublicKey).hexString)
|
||||
}
|
||||
|
||||
internal static func decryptWithSessionBlindingProtocol(data: Data, isOutgoing: Bool, otherBlindedPublicKey: String, with openGroupPublicKey: String, userEd25519KeyPair: KeyPair, using dependencies: SMKDependencies = SMKDependencies()) throws -> (plaintext: Data, senderX25519PublicKey: String) {
|
||||
internal static func decryptWithSessionBlindingProtocol(
|
||||
data: Data,
|
||||
isOutgoing: Bool,
|
||||
otherBlindedPublicKey: String,
|
||||
with openGroupPublicKey: String,
|
||||
userEd25519KeyPair: KeyPair,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> (plaintext: Data, senderX25519PublicKey: String) {
|
||||
/// Ensure the data is at least long enough to have the required components
|
||||
guard
|
||||
data.count > (dependencies.nonceGenerator24.NonceBytes + 2),
|
||||
let blindedKeyPair = dependencies.sodium.blindedKeyPair(
|
||||
serverPublicKey: openGroupPublicKey,
|
||||
edKeyPair: userEd25519KeyPair,
|
||||
genericHash: dependencies.genericHash
|
||||
data.count > (dependencies.crypto.size(.nonce24) + 2),
|
||||
let blindedKeyPair = dependencies.crypto.generate(
|
||||
.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, using: dependencies)
|
||||
)
|
||||
else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
/// Step one: calculate the shared encryption key, receiving from A to B
|
||||
let otherKeyBytes: Bytes = Data(hex: otherBlindedPublicKey.removingIdPrefixIfNeeded()).bytes
|
||||
let kA: Bytes = (isOutgoing ? blindedKeyPair.publicKey : otherKeyBytes)
|
||||
guard let dec_key: Bytes = dependencies.sodium.sharedBlindedEncryptionKey(
|
||||
secretKey: userEd25519KeyPair.secretKey,
|
||||
otherBlindedPublicKey: otherKeyBytes,
|
||||
fromBlindedPublicKey: kA,
|
||||
toBlindedPublicKey: (isOutgoing ? otherKeyBytes : blindedKeyPair.publicKey),
|
||||
genericHash: dependencies.genericHash
|
||||
) else {
|
||||
throw MessageReceiverError.decryptionFailed
|
||||
}
|
||||
guard
|
||||
let dec_key: Bytes = try? dependencies.crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: userEd25519KeyPair.secretKey,
|
||||
otherBlindedPublicKey: otherKeyBytes,
|
||||
fromBlindedPublicKey: kA,
|
||||
toBlindedPublicKey: (isOutgoing ? otherKeyBytes : blindedKeyPair.publicKey),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
/// v, ct, nc = data[0], data[1:-24], data[-24:]
|
||||
let version: UInt8 = data.bytes[0]
|
||||
let ciphertext: Bytes = Bytes(data.bytes[1..<(data.count - dependencies.nonceGenerator24.NonceBytes)])
|
||||
let nonce: Bytes = Bytes(data.bytes[(data.count - dependencies.nonceGenerator24.NonceBytes)..<data.count])
|
||||
let ciphertext: Bytes = Bytes(data.bytes[1..<(data.count - dependencies.crypto.size(.nonce24))])
|
||||
let nonce: Bytes = Bytes(data.bytes[(data.count - dependencies.crypto.size(.nonce24))..<data.count])
|
||||
|
||||
/// Make sure our encryption version is okay
|
||||
guard version == 0 else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
/// Decrypt
|
||||
guard let innerBytes: Bytes = dependencies.aeadXChaCha20Poly1305Ietf.decrypt(authenticatedCipherText: ciphertext, secretKey: dec_key, nonce: nonce) else {
|
||||
throw MessageReceiverError.decryptionFailed
|
||||
}
|
||||
guard
|
||||
let innerBytes: Bytes = try? dependencies.crypto.perform(
|
||||
.decryptAeadXChaCha20(
|
||||
authenticatedCipherText: ciphertext,
|
||||
secretKey: dec_key,
|
||||
nonce: nonce
|
||||
)
|
||||
)
|
||||
else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
/// Ensure the length is correct
|
||||
guard innerBytes.count > dependencies.sign.PublicKeyBytes else { throw MessageReceiverError.decryptionFailed }
|
||||
guard innerBytes.count > dependencies.crypto.size(.publicKey) else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
/// Split up: the last 32 bytes are the sender's *unblinded* ed25519 key
|
||||
let plaintext: Bytes = Bytes(innerBytes[
|
||||
0...(innerBytes.count - 1 - dependencies.sign.PublicKeyBytes)
|
||||
0...(innerBytes.count - 1 - dependencies.crypto.size(.publicKey))
|
||||
])
|
||||
let sender_edpk: Bytes = Bytes(innerBytes[
|
||||
(innerBytes.count - dependencies.sign.PublicKeyBytes)...(innerBytes.count - 1)
|
||||
(innerBytes.count - dependencies.crypto.size(.publicKey))...(innerBytes.count - 1)
|
||||
])
|
||||
|
||||
/// Verify that the inner sender_edpk (A) yields the same outer kA we got with the message
|
||||
guard let blindingFactor: Bytes = dependencies.sodium.generateBlindingFactor(serverPublicKey: openGroupPublicKey, genericHash: dependencies.genericHash) else {
|
||||
throw MessageReceiverError.invalidSignature
|
||||
}
|
||||
guard let sharedSecret: Bytes = dependencies.sodium.combineKeys(lhsKeyBytes: blindingFactor, rhsKeyBytes: sender_edpk) else {
|
||||
throw MessageReceiverError.invalidSignature
|
||||
}
|
||||
guard kA == sharedSecret else { throw MessageReceiverError.invalidSignature }
|
||||
guard
|
||||
let blindingFactor: Bytes = try? dependencies.crypto.perform(
|
||||
.generateBlindingFactor(serverPublicKey: openGroupPublicKey, using: dependencies)
|
||||
),
|
||||
let sharedSecret: Bytes = try? dependencies.crypto.perform(
|
||||
.combineKeys(lhsKeyBytes: blindingFactor, rhsKeyBytes: sender_edpk)
|
||||
),
|
||||
kA == sharedSecret
|
||||
else { throw MessageReceiverError.invalidSignature }
|
||||
|
||||
/// Get the sender's X25519 public key
|
||||
guard let senderSessionIdBytes: Bytes = dependencies.sign.toX25519(ed25519PublicKey: sender_edpk) else {
|
||||
throw MessageReceiverError.decryptionFailed
|
||||
}
|
||||
guard
|
||||
let senderSessionIdBytes: Bytes = try? dependencies.crypto.perform(
|
||||
.toX25519(ed25519PublicKey: sender_edpk)
|
||||
)
|
||||
else { throw MessageReceiverError.decryptionFailed }
|
||||
|
||||
return (Data(plaintext), SessionId(.standard, publicKey: senderSessionIdBytes).hexString)
|
||||
}
|
||||
|
|
|
@ -18,9 +18,9 @@ public enum MessageReceiver {
|
|||
openGroupServerPublicKey: String?,
|
||||
isOutgoing: Bool? = nil,
|
||||
otherBlindedPublicKey: String? = nil,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> (Message, SNProtoContent, String, SessionThread.Variant) {
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let isOpenGroupMessage: Bool = (openGroupId != nil)
|
||||
|
||||
// Decrypt the contents
|
||||
|
@ -188,7 +188,7 @@ public enum MessageReceiver {
|
|||
message: Message,
|
||||
serverExpirationTimestamp: TimeInterval?,
|
||||
associatedWithProto proto: SNProtoContent,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
// Check if the message requires an existing conversation (if it does and the conversation isn't in
|
||||
// the config then the message will be dropped)
|
||||
|
@ -203,7 +203,7 @@ public enum MessageReceiver {
|
|||
message: message,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
switch message {
|
||||
|
@ -227,7 +227,8 @@ public enum MessageReceiver {
|
|||
db,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant,
|
||||
message: message
|
||||
message: message,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case let message as DataExtractionNotification:
|
||||
|
@ -247,7 +248,11 @@ public enum MessageReceiver {
|
|||
)
|
||||
|
||||
case let message as ConfigurationMessage:
|
||||
try MessageReceiver.handleLegacyConfigurationMessage(db, message: message)
|
||||
try MessageReceiver.handleLegacyConfigurationMessage(
|
||||
db,
|
||||
message: message,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case let message as UnsendRequest:
|
||||
try MessageReceiver.handleUnsendRequest(
|
||||
|
@ -269,7 +274,7 @@ public enum MessageReceiver {
|
|||
try MessageReceiver.handleMessageRequestResponse(
|
||||
db,
|
||||
message: message,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case let message as VisibleMessage:
|
||||
|
@ -365,7 +370,7 @@ public enum MessageReceiver {
|
|||
message: Message,
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
switch message {
|
||||
case is ReadReceipt: return // No visible artifact created so better to keep for more reliable read states
|
||||
|
@ -374,7 +379,7 @@ public enum MessageReceiver {
|
|||
}
|
||||
|
||||
// Determine the state of the conversation and the validity of the message
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let conversationVisibleInConfig: Bool = SessionUtil.conversationInConfig(
|
||||
db,
|
||||
threadId: threadId,
|
||||
|
|
|
@ -14,7 +14,8 @@ extension MessageSender {
|
|||
interaction: Interaction,
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
isSyncMessage: Bool = false
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
// Only 'VisibleMessage' types can be sent via this method
|
||||
guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage }
|
||||
|
@ -26,7 +27,8 @@ extension MessageSender {
|
|||
threadId: threadId,
|
||||
interactionId: interactionId,
|
||||
to: try Message.Destination.from(db, threadId: threadId, threadVariant: threadVariant),
|
||||
isSyncMessage: isSyncMessage
|
||||
isSyncMessage: isSyncMessage,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -36,7 +38,8 @@ extension MessageSender {
|
|||
interactionId: Int64?,
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant,
|
||||
isSyncMessage: Bool = false
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
send(
|
||||
db,
|
||||
|
@ -44,7 +47,8 @@ extension MessageSender {
|
|||
threadId: threadId,
|
||||
interactionId: interactionId,
|
||||
to: try Message.Destination.from(db, threadId: threadId, threadVariant: threadVariant),
|
||||
isSyncMessage: isSyncMessage
|
||||
isSyncMessage: isSyncMessage,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -54,7 +58,8 @@ extension MessageSender {
|
|||
threadId: String?,
|
||||
interactionId: Int64?,
|
||||
to destination: Message.Destination,
|
||||
isSyncMessage: Bool = false
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// If it's a sync message then we need to make some slight tweaks before sending so use the proper
|
||||
// sync message sending process instead of the standard process
|
||||
|
@ -65,12 +70,13 @@ extension MessageSender {
|
|||
destination: destination,
|
||||
threadId: threadId,
|
||||
interactionId: interactionId,
|
||||
isAlreadySyncMessage: false
|
||||
isAlreadySyncMessage: false,
|
||||
using: dependencies
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
JobRunner.add(
|
||||
dependencies.jobRunner.add(
|
||||
db,
|
||||
job: Job(
|
||||
variant: .messageSend,
|
||||
|
@ -81,7 +87,9 @@ extension MessageSender {
|
|||
message: message,
|
||||
isSyncMessage: isSyncMessage
|
||||
)
|
||||
)
|
||||
),
|
||||
canStartJob: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -91,7 +99,8 @@ extension MessageSender {
|
|||
_ db: Database,
|
||||
interaction: Interaction,
|
||||
threadId: String,
|
||||
threadVariant: SessionThread.Variant
|
||||
threadVariant: SessionThread.Variant,
|
||||
using dependencies: Dependencies
|
||||
) throws -> PreparedSendData {
|
||||
// Only 'VisibleMessage' types can be sent via this method
|
||||
guard interaction.variant == .standardOutgoing else { throw MessageSenderError.invalidMessage }
|
||||
|
@ -104,11 +113,15 @@ extension MessageSender {
|
|||
namespace: try Message.Destination
|
||||
.from(db, threadId: threadId, threadVariant: threadVariant)
|
||||
.defaultNamespace,
|
||||
interactionId: interactionId
|
||||
interactionId: interactionId,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
public static func performUploadsIfNeeded(preparedSendData: PreparedSendData) -> AnyPublisher<PreparedSendData, Error> {
|
||||
public static func performUploadsIfNeeded(
|
||||
preparedSendData: PreparedSendData,
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<PreparedSendData, Error> {
|
||||
// We need an interactionId in order for a message to have uploads
|
||||
guard let interactionId: Int64 = preparedSendData.interactionId else {
|
||||
return Just(preparedSendData)
|
||||
|
@ -127,7 +140,7 @@ extension MessageSender {
|
|||
}
|
||||
}()
|
||||
|
||||
return Storage.shared
|
||||
return dependencies.storage
|
||||
.readPublisher { db -> (attachments: [Attachment], openGroup: OpenGroup?) in
|
||||
let attachmentStateInfo: [Attachment.StateInfo] = (try? Attachment
|
||||
.stateInfo(interactionId: interactionId, state: .uploading)
|
||||
|
@ -162,7 +175,8 @@ extension MessageSender {
|
|||
to: (
|
||||
openGroup.map { Attachment.Destination.openGroup($0) } ??
|
||||
.fileServer
|
||||
)
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
@ -10,7 +10,7 @@ extension MessageSender {
|
|||
_ db: Database,
|
||||
plaintext: Data,
|
||||
for recipientHexEncodedX25519PublicKey: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> Data {
|
||||
guard let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else {
|
||||
throw MessageSenderError.noUserED25519KeyPair
|
||||
|
@ -19,14 +19,21 @@ extension MessageSender {
|
|||
let recipientX25519PublicKey = Data(hex: recipientHexEncodedX25519PublicKey.removingIdPrefixIfNeeded())
|
||||
|
||||
let verificationData = plaintext + Data(userEd25519KeyPair.publicKey) + recipientX25519PublicKey
|
||||
guard let signature = dependencies.sign.signature(message: Bytes(verificationData), secretKey: userEd25519KeyPair.secretKey) else {
|
||||
throw MessageSenderError.signingFailed
|
||||
}
|
||||
guard
|
||||
let signature = try? dependencies.crypto.perform(
|
||||
.signature(message: Bytes(verificationData), secretKey: userEd25519KeyPair.secretKey)
|
||||
)
|
||||
else { throw MessageSenderError.signingFailed }
|
||||
|
||||
let plaintextWithMetadata = plaintext + Data(userEd25519KeyPair.publicKey) + Data(signature)
|
||||
guard let ciphertext = dependencies.box.seal(message: Bytes(plaintextWithMetadata), recipientPublicKey: Bytes(recipientX25519PublicKey)) else {
|
||||
throw MessageSenderError.encryptionFailed
|
||||
}
|
||||
guard
|
||||
let ciphertext = try? dependencies.crypto.perform(
|
||||
.seal(
|
||||
message: Bytes(plaintextWithMetadata),
|
||||
recipientPublicKey: Bytes(recipientX25519PublicKey)
|
||||
)
|
||||
)
|
||||
else { throw MessageSenderError.encryptionFailed }
|
||||
|
||||
return Data(ciphertext)
|
||||
}
|
||||
|
@ -36,7 +43,7 @@ extension MessageSender {
|
|||
plaintext: Data,
|
||||
for recipientBlindedId: String,
|
||||
openGroupPublicKey: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> Data {
|
||||
guard
|
||||
SessionId.Prefix(from: recipientBlindedId) == .blinded15 ||
|
||||
|
@ -45,32 +52,37 @@ extension MessageSender {
|
|||
guard let userEd25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else {
|
||||
throw MessageSenderError.noUserED25519KeyPair
|
||||
}
|
||||
guard let blindedKeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, genericHash: dependencies.genericHash) else {
|
||||
throw MessageSenderError.signingFailed
|
||||
}
|
||||
guard
|
||||
let blindedKeyPair = dependencies.crypto.generate(
|
||||
.blindedKeyPair(serverPublicKey: openGroupPublicKey, edKeyPair: userEd25519KeyPair, using: dependencies)
|
||||
)
|
||||
else { throw MessageSenderError.signingFailed }
|
||||
|
||||
let recipientBlindedPublicKey = Data(hex: recipientBlindedId.removingIdPrefixIfNeeded())
|
||||
|
||||
/// Step one: calculate the shared encryption key, sending from A to B
|
||||
guard let enc_key: Bytes = dependencies.sodium.sharedBlindedEncryptionKey(
|
||||
secretKey: userEd25519KeyPair.secretKey,
|
||||
otherBlindedPublicKey: recipientBlindedPublicKey.bytes,
|
||||
fromBlindedPublicKey: blindedKeyPair.publicKey,
|
||||
toBlindedPublicKey: recipientBlindedPublicKey.bytes,
|
||||
genericHash: dependencies.genericHash
|
||||
) else {
|
||||
throw MessageSenderError.signingFailed
|
||||
}
|
||||
guard
|
||||
let enc_key: Bytes = try? dependencies.crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: userEd25519KeyPair.secretKey,
|
||||
otherBlindedPublicKey: recipientBlindedPublicKey.bytes,
|
||||
fromBlindedPublicKey: blindedKeyPair.publicKey,
|
||||
toBlindedPublicKey: recipientBlindedPublicKey.bytes,
|
||||
using: dependencies
|
||||
)
|
||||
),
|
||||
let nonce: Bytes = try? dependencies.crypto.perform(.generateNonce24())
|
||||
else { throw MessageSenderError.signingFailed }
|
||||
|
||||
/// Inner data: msg || A (i.e. the sender's ed25519 master pubkey, *not* kA blinded pubkey)
|
||||
let innerBytes: Bytes = (plaintext.bytes + userEd25519KeyPair.publicKey)
|
||||
|
||||
/// Encrypt using xchacha20-poly1305
|
||||
let nonce: Bytes = dependencies.nonceGenerator24.nonce()
|
||||
|
||||
guard let ciphertext = dependencies.aeadXChaCha20Poly1305Ietf.encrypt(message: innerBytes, secretKey: enc_key, nonce: nonce) else {
|
||||
throw MessageSenderError.encryptionFailed
|
||||
}
|
||||
guard
|
||||
let ciphertext = try? dependencies.crypto.perform(
|
||||
.encryptAeadXChaCha20(message: innerBytes, secretKey: enc_key, nonce: nonce, using: dependencies)
|
||||
)
|
||||
else { throw MessageSenderError.encryptionFailed }
|
||||
|
||||
/// data = b'\x00' + ciphertext + nonce
|
||||
return Data(Bytes(arrayLiteral: 0) + ciphertext + nonce)
|
||||
|
|
|
@ -140,10 +140,10 @@ public final class MessageSender {
|
|||
namespace: SnodeAPI.Namespace?,
|
||||
interactionId: Int64?,
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws -> PreparedSendData {
|
||||
// Common logic for all destinations
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, dependencies: dependencies)
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let messageSendTimestamp: Int64 = SnodeAPI.currentOffsetTimestampMs()
|
||||
let updatedMessage: Message = message
|
||||
|
||||
|
@ -199,7 +199,7 @@ public final class MessageSender {
|
|||
userPublicKey: String,
|
||||
messageSendTimestamp: Int64,
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> PreparedSendData {
|
||||
message.sender = userPublicKey
|
||||
message.recipient = {
|
||||
|
@ -276,7 +276,7 @@ public final class MessageSender {
|
|||
do {
|
||||
switch destination {
|
||||
case .contact(let publicKey):
|
||||
ciphertext = try encryptWithSessionProtocol(db, plaintext: plaintext, for: publicKey)
|
||||
ciphertext = try encryptWithSessionProtocol(db, plaintext: plaintext, for: publicKey, using: dependencies)
|
||||
|
||||
case .closedGroup(let groupPublicKey):
|
||||
guard let encryptionKeyPair: ClosedGroupKeyPair = try? ClosedGroupKeyPair.fetchLatestKeyPair(db, threadId: groupPublicKey) else {
|
||||
|
@ -286,7 +286,8 @@ public final class MessageSender {
|
|||
ciphertext = try encryptWithSessionProtocol(
|
||||
db,
|
||||
plaintext: plaintext,
|
||||
for: SessionId(.standard, publicKey: encryptionKeyPair.publicKey.bytes).hexString
|
||||
for: SessionId(.standard, publicKey: encryptionKeyPair.publicKey.bytes).hexString,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case .openGroup, .openGroupInbox: preconditionFailure()
|
||||
|
@ -365,7 +366,7 @@ public final class MessageSender {
|
|||
to destination: Message.Destination,
|
||||
interactionId: Int64?,
|
||||
messageSendTimestamp: Int64,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> PreparedSendData {
|
||||
let threadId: String
|
||||
|
||||
|
@ -394,7 +395,7 @@ public final class MessageSender {
|
|||
throw MessageSenderError.invalidMessage
|
||||
}
|
||||
|
||||
message.sender = {
|
||||
message.sender = try {
|
||||
let capabilities: [Capability.Variant] = (try? Capability
|
||||
.select(.variant)
|
||||
.filter(Capability.Columns.openGroupServer == server)
|
||||
|
@ -407,9 +408,11 @@ public final class MessageSender {
|
|||
guard capabilities.isEmpty || capabilities.contains(.blind) else {
|
||||
return SessionId(.unblinded, publicKey: userEdKeyPair.publicKey).hexString
|
||||
}
|
||||
guard let blindedKeyPair: KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: openGroup.publicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else {
|
||||
preconditionFailure()
|
||||
}
|
||||
guard
|
||||
let blindedKeyPair: KeyPair = dependencies.crypto.generate(
|
||||
.blindedKeyPair(serverPublicKey: openGroup.publicKey, edKeyPair: userEdKeyPair, using: dependencies)
|
||||
)
|
||||
else { throw MessageSenderError.signingFailed }
|
||||
|
||||
return SessionId(.blinded15, publicKey: blindedKeyPair.publicKey).hexString
|
||||
}()
|
||||
|
@ -492,7 +495,7 @@ public final class MessageSender {
|
|||
interactionId: Int64?,
|
||||
userPublicKey: String,
|
||||
messageSendTimestamp: Int64,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws -> PreparedSendData {
|
||||
guard case .openGroupInbox(_, let openGroupPublicKey, let recipientBlindedPublicKey) = destination else {
|
||||
throw MessageSenderError.invalidMessage
|
||||
|
@ -582,10 +585,10 @@ public final class MessageSender {
|
|||
// MARK: - Sending
|
||||
|
||||
public static func sendImmediate(
|
||||
preparedSendData: PreparedSendData,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
data: PreparedSendData,
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard preparedSendData.shouldSend else {
|
||||
guard data.shouldSend else {
|
||||
return Just(())
|
||||
.setFailureType(to: Error.self)
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -597,7 +600,7 @@ public final class MessageSender {
|
|||
//
|
||||
// If you see this error then you need to call
|
||||
// `MessageSender.performUploadsIfNeeded(queue:preparedSendData:)` before calling this function
|
||||
switch preparedSendData.message {
|
||||
switch data.message {
|
||||
case let visibleMessage as VisibleMessage:
|
||||
let expectedAttachmentUploadCount: Int = (
|
||||
visibleMessage.attachmentIds.count +
|
||||
|
@ -605,17 +608,17 @@ public final class MessageSender {
|
|||
(visibleMessage.quote?.attachmentId != nil ? 1 : 0)
|
||||
)
|
||||
|
||||
guard expectedAttachmentUploadCount == preparedSendData.totalAttachmentsUploaded else {
|
||||
guard expectedAttachmentUploadCount == data.totalAttachmentsUploaded else {
|
||||
// Make sure to actually handle this as a failure (if we don't then the message
|
||||
// won't go into an error state correctly)
|
||||
if let message: Message = preparedSendData.message {
|
||||
if let message: Message = data.message {
|
||||
dependencies.storage.read { db in
|
||||
MessageSender.handleFailedMessageSend(
|
||||
db,
|
||||
message: message,
|
||||
with: .attachmentsNotUploaded,
|
||||
interactionId: preparedSendData.interactionId,
|
||||
isSyncMessage: (preparedSendData.isSyncMessage == true),
|
||||
interactionId: data.interactionId,
|
||||
isSyncMessage: (data.isSyncMessage == true),
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
@ -630,10 +633,10 @@ public final class MessageSender {
|
|||
default: break
|
||||
}
|
||||
|
||||
switch preparedSendData.destination {
|
||||
case .contact, .closedGroup: return sendToSnodeDestination(data: preparedSendData, using: dependencies)
|
||||
case .openGroup: return sendToOpenGroupDestination(data: preparedSendData, using: dependencies)
|
||||
case .openGroupInbox: return sendToOpenGroupInbox(data: preparedSendData, using: dependencies)
|
||||
switch data.destination {
|
||||
case .contact, .closedGroup: return sendToSnodeDestination(data: data, using: dependencies)
|
||||
case .openGroup: return sendToOpenGroupDestination(data: data, using: dependencies)
|
||||
case .openGroupInbox: return sendToOpenGroupInbox(data: data, using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -641,7 +644,7 @@ public final class MessageSender {
|
|||
|
||||
private static func sendToSnodeDestination(
|
||||
data: PreparedSendData,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard
|
||||
let message: Message = data.message,
|
||||
|
@ -653,14 +656,11 @@ public final class MessageSender {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
return SnodeAPI
|
||||
.sendMessage(
|
||||
snodeMessage,
|
||||
in: namespace
|
||||
)
|
||||
.flatMap { response -> AnyPublisher<Void, Error> in
|
||||
return dependencies.network
|
||||
.send(.message(snodeMessage, in: namespace, using: dependencies))
|
||||
.flatMap { info, response -> AnyPublisher<Void, Error> in
|
||||
let updatedMessage: Message = message
|
||||
updatedMessage.serverHash = response.1.hash
|
||||
updatedMessage.serverHash = response.hash
|
||||
|
||||
let job: Job? = Job(
|
||||
variant: .notifyPushServer,
|
||||
|
@ -695,7 +695,7 @@ public final class MessageSender {
|
|||
|
||||
guard shouldNotify else { return () }
|
||||
|
||||
JobRunner.add(db, job: job)
|
||||
dependencies.jobRunner.add(db, job: job, canStartJob: true, using: dependencies)
|
||||
return ()
|
||||
}
|
||||
.flatMap { _ -> AnyPublisher<Void, Error> in
|
||||
|
@ -718,15 +718,16 @@ public final class MessageSender {
|
|||
NotifyPushServerJob.run(
|
||||
job,
|
||||
queue: .global(qos: .default),
|
||||
success: { _, _ in resolver(Result.success(())) },
|
||||
failure: { _, _, _ in
|
||||
success: { _, _, _ in resolver(Result.success(())) },
|
||||
failure: { _, _, _, _ in
|
||||
// Always fulfill because the notify PN server job isn't critical.
|
||||
resolver(Result.success(()))
|
||||
},
|
||||
deferred: { _ in
|
||||
deferred: { _, _ in
|
||||
// Always fulfill because the notify PN server job isn't critical.
|
||||
resolver(Result.success(()))
|
||||
}
|
||||
},
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -761,7 +762,7 @@ public final class MessageSender {
|
|||
|
||||
private static func sendToOpenGroupDestination(
|
||||
data: PreparedSendData,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard
|
||||
let message: Message = data.message,
|
||||
|
@ -829,7 +830,7 @@ public final class MessageSender {
|
|||
|
||||
private static func sendToOpenGroupInbox(
|
||||
data: PreparedSendData,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard
|
||||
let message: Message = data.message,
|
||||
|
@ -926,7 +927,7 @@ public final class MessageSender {
|
|||
interactionId: Int64?,
|
||||
serverTimestampMs: UInt64? = nil,
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
// If the message was a reaction then we want to update the reaction instead of the original
|
||||
// interaction (which the 'interactionId' is pointing to
|
||||
|
@ -964,13 +965,15 @@ public final class MessageSender {
|
|||
.updateAll(db, RecipientState.Columns.state.set(to: RecipientState.State.sent))
|
||||
|
||||
// Start the disappearing messages timer if needed
|
||||
JobRunner.upsert(
|
||||
dependencies.jobRunner.upsert(
|
||||
db,
|
||||
job: DisappearingMessagesJob.updateNextRunIfNeeded(
|
||||
db,
|
||||
interaction: interaction,
|
||||
startedAtMs: TimeInterval(SnodeAPI.currentOffsetTimestampMs())
|
||||
)
|
||||
),
|
||||
canStartJob: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -995,7 +998,8 @@ public final class MessageSender {
|
|||
destination: destination,
|
||||
threadId: threadId,
|
||||
interactionId: interactionId,
|
||||
isAlreadySyncMessage: isSyncMessage
|
||||
isAlreadySyncMessage: isSyncMessage,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1005,7 +1009,7 @@ public final class MessageSender {
|
|||
with error: MessageSenderError,
|
||||
interactionId: Int64?,
|
||||
isSyncMessage: Bool = false,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> Error {
|
||||
// If the message was a reaction then we don't want to do anything to the original
|
||||
// interaciton (which the 'interactionId' is pointing to
|
||||
|
@ -1072,11 +1076,12 @@ public final class MessageSender {
|
|||
destination: Message.Destination,
|
||||
threadId: String?,
|
||||
interactionId: Int64?,
|
||||
isAlreadySyncMessage: Bool
|
||||
isAlreadySyncMessage: Bool,
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
// Sync the message if it's not a sync message, wasn't already sent to the current user and
|
||||
// it's a message type which should be synced
|
||||
let currentUserPublicKey = getUserHexEncodedPublicKey(db)
|
||||
let currentUserPublicKey = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
|
||||
if
|
||||
case .contact(let publicKey) = destination,
|
||||
|
@ -1087,7 +1092,7 @@ public final class MessageSender {
|
|||
if let message = message as? VisibleMessage { message.syncTarget = publicKey }
|
||||
if let message = message as? ExpirationTimerUpdate { message.syncTarget = publicKey }
|
||||
|
||||
JobRunner.add(
|
||||
dependencies.jobRunner.add(
|
||||
db,
|
||||
job: Job(
|
||||
variant: .messageSend,
|
||||
|
@ -1098,7 +1103,9 @@ public final class MessageSender {
|
|||
message: message,
|
||||
isSyncMessage: true
|
||||
)
|
||||
)
|
||||
),
|
||||
canStartJob: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,11 +25,11 @@ public enum PushNotificationAPI {
|
|||
public static func subscribe(
|
||||
token: Data,
|
||||
isForcedUpdate: Bool,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
let hexEncodedToken: String = token.toHexString()
|
||||
let oldToken: String? = UserDefaults.standard[.deviceToken]
|
||||
let lastUploadTime: Double = UserDefaults.standard[.lastDeviceTokenUpload]
|
||||
let oldToken: String? = dependencies.standardUserDefaults[.deviceToken]
|
||||
let lastUploadTime: Double = dependencies.standardUserDefaults[.lastDeviceTokenUpload]
|
||||
let now: TimeInterval = Date().timeIntervalSince1970
|
||||
|
||||
guard isForcedUpdate || hexEncodedToken != oldToken || now - lastUploadTime > tokenExpirationInterval else {
|
||||
|
@ -39,7 +39,7 @@ public enum PushNotificationAPI {
|
|||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
guard let notificationsEncryptionKey: Data = try? getOrGenerateEncryptionKey() else {
|
||||
guard let notificationsEncryptionKey: Data = try? getOrGenerateEncryptionKey(using: dependencies) else {
|
||||
SNLog("Unable to retrieve PN encryption key.")
|
||||
return Just(())
|
||||
.setFailureType(to: Error.self)
|
||||
|
@ -47,13 +47,13 @@ public enum PushNotificationAPI {
|
|||
}
|
||||
|
||||
// TODO: Need to generate requests for each updated group as well
|
||||
return Storage.shared
|
||||
.readPublisher { db -> (SubscribeRequest, String, Set<String>) in
|
||||
return dependencies.storage
|
||||
.readPublisher(using: dependencies) { db -> (SubscribeRequest, String, Set<String>) in
|
||||
guard let userED25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else {
|
||||
throw SnodeAPIError.noKeyPair
|
||||
}
|
||||
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db)
|
||||
let currentUserPublicKey: String = getUserHexEncodedPublicKey(db, using: dependencies)
|
||||
let request: SubscribeRequest = SubscribeRequest(
|
||||
pubkey: currentUserPublicKey,
|
||||
namespaces: [.default],
|
||||
|
@ -94,19 +94,20 @@ public enum PushNotificationAPI {
|
|||
request: PushNotificationAPIRequest(
|
||||
endpoint: .subscribe,
|
||||
body: request
|
||||
)
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
.decoded(as: SubscribeResponse.self, using: dependencies)
|
||||
.retry(maxRetryCount)
|
||||
.retry(maxRetryCount, using: dependencies)
|
||||
.handleEvents(
|
||||
receiveOutput: { _, response in
|
||||
guard response.success == true else {
|
||||
return SNLog("Couldn't subscribe for push notifications due to error (\(response.error ?? -1)): \(response.message ?? "nil").")
|
||||
}
|
||||
|
||||
UserDefaults.standard[.deviceToken] = hexEncodedToken
|
||||
UserDefaults.standard[.lastDeviceTokenUpload] = now
|
||||
UserDefaults.standard[.isUsingFullAPNs] = true
|
||||
dependencies.standardUserDefaults[.deviceToken] = hexEncodedToken
|
||||
dependencies.standardUserDefaults[.lastDeviceTokenUpload] = now
|
||||
dependencies.standardUserDefaults[.isUsingFullAPNs] = true
|
||||
},
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
|
@ -122,7 +123,8 @@ public enum PushNotificationAPI {
|
|||
forced: true,
|
||||
token: hexEncodedToken,
|
||||
currentUserPublicKey: currentUserPublicKey,
|
||||
legacyGroupIds: legacyGroupIds
|
||||
legacyGroupIds: legacyGroupIds,
|
||||
using: dependencies
|
||||
)
|
||||
]
|
||||
)
|
||||
|
@ -135,16 +137,16 @@ public enum PushNotificationAPI {
|
|||
|
||||
public static func unsubscribe(
|
||||
token: Data,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
let hexEncodedToken: String = token.toHexString()
|
||||
|
||||
// FIXME: Remove this once legacy groups are deprecated
|
||||
/// Unsubscribe from all legacy groups (including ones the user is no longer a member of, just in case)
|
||||
Storage.shared
|
||||
.readPublisher { db -> (String, Set<String>) in
|
||||
dependencies.storage
|
||||
.readPublisher(using: dependencies) { db -> (String, Set<String>) in
|
||||
(
|
||||
getUserHexEncodedPublicKey(db),
|
||||
getUserHexEncodedPublicKey(db, using: dependencies),
|
||||
try ClosedGroup
|
||||
.select(.threadId)
|
||||
.filter(!ClosedGroup.Columns.threadId.like("\(SessionId.Prefix.group.rawValue)%"))
|
||||
|
@ -168,18 +170,18 @@ public enum PushNotificationAPI {
|
|||
.collect()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
|
||||
.subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies)
|
||||
.sinkUntilComplete()
|
||||
|
||||
// TODO: Need to generate requests for each updated group as well
|
||||
return Storage.shared
|
||||
.readPublisher { db -> UnsubscribeRequest in
|
||||
return dependencies.storage
|
||||
.readPublisher(using: dependencies) { db -> UnsubscribeRequest in
|
||||
guard let userED25519KeyPair: KeyPair = Identity.fetchUserEd25519KeyPair(db) else {
|
||||
throw SnodeAPIError.noKeyPair
|
||||
}
|
||||
|
||||
return UnsubscribeRequest(
|
||||
pubkey: getUserHexEncodedPublicKey(db),
|
||||
pubkey: getUserHexEncodedPublicKey(db, using: dependencies),
|
||||
serviceInfo: UnsubscribeRequest.ServiceInfo(
|
||||
token: hexEncodedToken
|
||||
),
|
||||
|
@ -195,17 +197,18 @@ public enum PushNotificationAPI {
|
|||
request: PushNotificationAPIRequest(
|
||||
endpoint: .unsubscribe,
|
||||
body: request
|
||||
)
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
.decoded(as: UnsubscribeResponse.self, using: dependencies)
|
||||
.retry(maxRetryCount)
|
||||
.retry(maxRetryCount, using: dependencies)
|
||||
.handleEvents(
|
||||
receiveOutput: { _, response in
|
||||
guard response.success == true else {
|
||||
return SNLog("Couldn't unsubscribe for push notifications due to error (\(response.error ?? -1)): \(response.message ?? "nil").")
|
||||
}
|
||||
|
||||
UserDefaults.standard[.deviceToken] = nil
|
||||
dependencies.standardUserDefaults[.deviceToken] = nil
|
||||
},
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
|
@ -227,7 +230,7 @@ public enum PushNotificationAPI {
|
|||
recipient: String,
|
||||
with message: String,
|
||||
maxRetryCount: Int? = nil,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
return PushNotificationAPI
|
||||
.send(
|
||||
|
@ -237,10 +240,11 @@ public enum PushNotificationAPI {
|
|||
data: message,
|
||||
sendTo: recipient
|
||||
)
|
||||
)
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
.decoded(as: LegacyPushServerResponse.self, using: dependencies)
|
||||
.retry(maxRetryCount ?? PushNotificationAPI.maxRetryCount)
|
||||
.retry(maxRetryCount ?? PushNotificationAPI.maxRetryCount, using: dependencies)
|
||||
.handleEvents(
|
||||
receiveOutput: { _, response in
|
||||
guard response.code != 0 else {
|
||||
|
@ -266,14 +270,14 @@ public enum PushNotificationAPI {
|
|||
token: String? = nil,
|
||||
currentUserPublicKey: String,
|
||||
legacyGroupIds: Set<String>,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
let isUsingFullAPNs = UserDefaults.standard[.isUsingFullAPNs]
|
||||
let isUsingFullAPNs = dependencies.standardUserDefaults[.isUsingFullAPNs]
|
||||
|
||||
// Only continue if PNs are enabled and we have a device token
|
||||
guard
|
||||
(forced || isUsingFullAPNs),
|
||||
let deviceToken: String = (token ?? UserDefaults.standard[.deviceToken])
|
||||
let deviceToken: String = (token ?? dependencies.standardUserDefaults[.deviceToken])
|
||||
else {
|
||||
return Just(())
|
||||
.setFailureType(to: Error.self)
|
||||
|
@ -290,10 +294,11 @@ public enum PushNotificationAPI {
|
|||
device: "ios",
|
||||
legacyGroupPublicKeys: legacyGroupIds
|
||||
)
|
||||
)
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
.decoded(as: LegacyPushServerResponse.self, using: dependencies)
|
||||
.retry(maxRetryCount)
|
||||
.retry(maxRetryCount, using: dependencies)
|
||||
.handleEvents(
|
||||
receiveOutput: { _, response in
|
||||
guard response.code != 0 else {
|
||||
|
@ -315,7 +320,7 @@ public enum PushNotificationAPI {
|
|||
public static func unsubscribeFromLegacyGroup(
|
||||
legacyGroupId: String,
|
||||
currentUserPublicKey: String,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
return PushNotificationAPI
|
||||
.send(
|
||||
|
@ -325,10 +330,11 @@ public enum PushNotificationAPI {
|
|||
pubKey: currentUserPublicKey,
|
||||
closedGroupPublicKey: legacyGroupId
|
||||
)
|
||||
)
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
.decoded(as: LegacyPushServerResponse.self, using: dependencies)
|
||||
.retry(maxRetryCount)
|
||||
.retry(maxRetryCount, using: dependencies)
|
||||
.handleEvents(
|
||||
receiveOutput: { _, response in
|
||||
guard response.code != 0 else {
|
||||
|
@ -350,7 +356,7 @@ public enum PushNotificationAPI {
|
|||
|
||||
public static func processNotification(
|
||||
notificationContent: UNNotificationContent,
|
||||
dependencies: SMKDependencies = SMKDependencies()
|
||||
dependencies: Dependencies = Dependencies()
|
||||
) -> (envelope: SNProtoEnvelope?, result: ProcessResult) {
|
||||
// Make sure the notification is from the updated push server
|
||||
guard notificationContent.userInfo["spns"] != nil else {
|
||||
|
@ -369,18 +375,20 @@ public enum PushNotificationAPI {
|
|||
guard
|
||||
let base64EncodedEncString: String = notificationContent.userInfo["enc_payload"] as? String,
|
||||
let encData: Data = Data(base64Encoded: base64EncodedEncString),
|
||||
let notificationsEncryptionKey: Data = try? getOrGenerateEncryptionKey(),
|
||||
encData.count > dependencies.aeadXChaCha20Poly1305Ietf.NonceBytes
|
||||
let notificationsEncryptionKey: Data = try? getOrGenerateEncryptionKey(using: dependencies),
|
||||
encData.count > dependencies.crypto.size(.aeadXChaCha20NonceBytes)
|
||||
else { return (nil, .failure) }
|
||||
|
||||
let nonce: Data = encData[0..<dependencies.aeadXChaCha20Poly1305Ietf.NonceBytes]
|
||||
let payload: Data = encData[dependencies.aeadXChaCha20Poly1305Ietf.NonceBytes...]
|
||||
let nonce: Data = encData[0..<dependencies.crypto.size(.aeadXChaCha20NonceBytes)]
|
||||
let payload: Data = encData[dependencies.crypto.size(.aeadXChaCha20NonceBytes)...]
|
||||
|
||||
guard
|
||||
let paddedData: [UInt8] = dependencies.aeadXChaCha20Poly1305Ietf.decrypt(
|
||||
authenticatedCipherText: payload.bytes,
|
||||
secretKey: notificationsEncryptionKey.bytes,
|
||||
nonce: nonce.bytes
|
||||
let paddedData: [UInt8] = try? dependencies.crypto.perform(
|
||||
.decryptAeadXChaCha20(
|
||||
authenticatedCipherText: payload.bytes,
|
||||
secretKey: notificationsEncryptionKey.bytes,
|
||||
nonce: nonce.bytes
|
||||
)
|
||||
)
|
||||
else { return (nil, .failure) }
|
||||
|
||||
|
@ -408,7 +416,7 @@ public enum PushNotificationAPI {
|
|||
|
||||
// MARK: - Security
|
||||
|
||||
@discardableResult private static func getOrGenerateEncryptionKey() throws -> Data {
|
||||
@discardableResult private static func getOrGenerateEncryptionKey(using dependencies: Dependencies) throws -> Data {
|
||||
do {
|
||||
var encryptionKey: Data = try SSKDefaultKeychainStorage.shared.data(
|
||||
forService: keychainService,
|
||||
|
@ -461,7 +469,7 @@ public enum PushNotificationAPI {
|
|||
|
||||
private static func send<T: Encodable>(
|
||||
request: PushNotificationAPIRequest<T>,
|
||||
using dependencies: SSKDependencies = SSKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<(ResponseInfoType, Data?), Error> {
|
||||
guard
|
||||
let url: URL = URL(string: "\(request.endpoint.server)/\(request.endpoint.rawValue)"),
|
||||
|
@ -487,8 +495,14 @@ public enum PushNotificationAPI {
|
|||
urlRequest.allHTTPHeaderFields = [ HTTPHeader.contentType: "application/json" ]
|
||||
urlRequest.httpBody = payload
|
||||
|
||||
return dependencies.onionApi
|
||||
.sendOnionRequest(urlRequest, to: request.endpoint.server, with: request.endpoint.serverPublicKey)
|
||||
return dependencies.network
|
||||
.send(
|
||||
.onionRequest(
|
||||
urlRequest,
|
||||
to: request.endpoint.server,
|
||||
with: request.endpoint.serverPublicKey
|
||||
)
|
||||
)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,23 +23,23 @@ public final class ClosedGroupPoller: Poller {
|
|||
|
||||
// MARK: - Public API
|
||||
|
||||
public func start() {
|
||||
public func start(using dependencies: Dependencies = Dependencies()) {
|
||||
// Fetch all closed groups (excluding any don't contain the current user as a
|
||||
// GroupMemeber as the user is no longer a member of those)
|
||||
Storage.shared
|
||||
dependencies.storage
|
||||
.read { db in
|
||||
try ClosedGroup
|
||||
.select(.threadId)
|
||||
.joining(
|
||||
required: ClosedGroup.members
|
||||
.filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db))
|
||||
.filter(GroupMember.Columns.profileId == getUserHexEncodedPublicKey(db, using: dependencies))
|
||||
)
|
||||
.asRequest(of: String.self)
|
||||
.fetchAll(db)
|
||||
}
|
||||
.defaulting(to: [])
|
||||
.forEach { [weak self] publicKey in
|
||||
self?.startIfNeeded(for: publicKey)
|
||||
self?.startIfNeeded(for: publicKey, using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,7 @@ public final class ClosedGroupPoller: Poller {
|
|||
return "closed group with public key: \(publicKey)"
|
||||
}
|
||||
|
||||
override func nextPollDelay(for publicKey: String) -> TimeInterval {
|
||||
override func nextPollDelay(for publicKey: String, using dependencies: Dependencies) -> TimeInterval {
|
||||
// Get the received date of the last message in the thread. If we don't have
|
||||
// any messages yet, pick some reasonable fake time interval to use instead
|
||||
let lastMessageDate: Date = Storage.shared
|
||||
|
@ -68,7 +68,7 @@ public final class ClosedGroupPoller: Poller {
|
|||
}
|
||||
.defaulting(to: Date().addingTimeInterval(-5 * 60))
|
||||
|
||||
let timeSinceLastMessage: TimeInterval = Date().timeIntervalSince(lastMessageDate)
|
||||
let timeSinceLastMessage: TimeInterval = dependencies.dateNow.timeIntervalSince(lastMessageDate)
|
||||
let minPollInterval: Double = ClosedGroupPoller.minPollInterval
|
||||
let limit: Double = (12 * 60 * 60)
|
||||
let a: TimeInterval = ((ClosedGroupPoller.maxPollInterval - minPollInterval) / limit)
|
||||
|
@ -77,12 +77,8 @@ public final class ClosedGroupPoller: Poller {
|
|||
|
||||
return nextPollInterval
|
||||
}
|
||||
|
||||
override func handlePollError(
|
||||
_ error: Error,
|
||||
for publicKey: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
) -> Bool {
|
||||
|
||||
override func handlePollError(_ error: Error, for publicKey: String, using dependencies: Dependencies) -> Bool {
|
||||
SNLog("Polling failed for closed group with public key: \(publicKey) due to error: \(error).")
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -33,13 +33,13 @@ public final class CurrentUserPoller: Poller {
|
|||
|
||||
// MARK: - Convenience Functions
|
||||
|
||||
public func start() {
|
||||
let publicKey: String = getUserHexEncodedPublicKey()
|
||||
public func start(using dependencies: Dependencies = Dependencies()) {
|
||||
let publicKey: String = getUserHexEncodedPublicKey(using: dependencies)
|
||||
|
||||
guard isPolling.wrappedValue[publicKey] != true else { return }
|
||||
|
||||
SNLog("Started polling.")
|
||||
super.startIfNeeded(for: publicKey)
|
||||
super.startIfNeeded(for: publicKey, using: dependencies)
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
|
@ -53,7 +53,7 @@ public final class CurrentUserPoller: Poller {
|
|||
return "Main Poller"
|
||||
}
|
||||
|
||||
override func nextPollDelay(for publicKey: String) -> TimeInterval {
|
||||
override func nextPollDelay(for publicKey: String, using dependencies: Dependencies) -> TimeInterval {
|
||||
let failureCount: TimeInterval = TimeInterval(failureCount.wrappedValue[publicKey] ?? 0)
|
||||
|
||||
// If there have been no failures then just use the 'minPollInterval'
|
||||
|
@ -65,11 +65,7 @@ public final class CurrentUserPoller: Poller {
|
|||
return min(maxRetryInterval, nextDelay)
|
||||
}
|
||||
|
||||
override func handlePollError(
|
||||
_ error: Error,
|
||||
for publicKey: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
) -> Bool {
|
||||
override func handlePollError(_ error: Error, for publicKey: String, using dependencies: Dependencies) -> Bool {
|
||||
if UserDefaults.sharedLokiProject?[.isMainAppActive] != true {
|
||||
// Do nothing when an error gets throws right after returning from the background (happens frequently)
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ extension OpenGroupAPI {
|
|||
self.server = server
|
||||
}
|
||||
|
||||
public func startIfNeeded(using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()) {
|
||||
public func startIfNeeded(using dependencies: Dependencies) {
|
||||
guard !hasStarted else { return }
|
||||
|
||||
hasStarted = true
|
||||
|
@ -49,20 +49,15 @@ extension OpenGroupAPI {
|
|||
|
||||
// MARK: - Polling
|
||||
|
||||
private func pollRecursively(
|
||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies(
|
||||
subscribeQueue: Threading.pollerQueue,
|
||||
receiveQueue: OpenGroupAPI.workQueue
|
||||
)
|
||||
) {
|
||||
private func pollRecursively(using dependencies: Dependencies) {
|
||||
guard hasStarted else { return }
|
||||
|
||||
let server: String = self.server
|
||||
let lastPollStart: TimeInterval = Date().timeIntervalSince1970
|
||||
let lastPollStart: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||
|
||||
poll(using: dependencies)
|
||||
.subscribe(on: dependencies.subscribeQueue)
|
||||
.receive(on: dependencies.receiveQueue)
|
||||
.subscribe(on: Threading.pollerQueue, using: dependencies)
|
||||
.receive(on: OpenGroupAPI.workQueue, using: dependencies)
|
||||
.sinkUntilComplete(
|
||||
receiveCompletion: { [weak self] _ in
|
||||
let minPollFailureCount: Int64 = dependencies.storage
|
||||
|
@ -76,7 +71,7 @@ extension OpenGroupAPI {
|
|||
.defaulting(to: 0)
|
||||
|
||||
// Calculate the remaining poll delay
|
||||
let currentTime: TimeInterval = Date().timeIntervalSince1970
|
||||
let currentTime: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||
let nextPollInterval: TimeInterval = Poller.getInterval(
|
||||
for: TimeInterval(minPollFailureCount),
|
||||
minInterval: Poller.minPollInterval,
|
||||
|
@ -86,12 +81,12 @@ extension OpenGroupAPI {
|
|||
|
||||
// Schedule the next poll
|
||||
guard remainingInterval > 0 else {
|
||||
return dependencies.subscribeQueue.async {
|
||||
return Threading.pollerQueue.async(using: dependencies) {
|
||||
self?.pollRecursively(using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies.subscribeQueue.asyncAfter(deadline: .now() + .milliseconds(Int(remainingInterval * 1000)), qos: .default) {
|
||||
Threading.pollerQueue.asyncAfter(deadline: .now() + .milliseconds(Int(remainingInterval * 1000)), qos: .default, using: dependencies) {
|
||||
self?.pollRecursively(using: dependencies)
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +94,7 @@ extension OpenGroupAPI {
|
|||
}
|
||||
|
||||
public func poll(
|
||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
return poll(
|
||||
calledFromBackgroundPoller: false,
|
||||
|
@ -112,7 +107,7 @@ extension OpenGroupAPI {
|
|||
calledFromBackgroundPoller: Bool,
|
||||
isBackgroundPollerValid: @escaping (() -> Bool) = { true },
|
||||
isPostCapabilitiesRetry: Bool,
|
||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Void, Error> {
|
||||
guard !self.isPolling else {
|
||||
return Just(())
|
||||
|
@ -122,10 +117,12 @@ extension OpenGroupAPI {
|
|||
|
||||
self.isPolling = true
|
||||
let server: String = self.server
|
||||
let hasPerformedInitialPoll: Bool = (dependencies.cache.hasPerformedInitialPoll[server] == true)
|
||||
let hasPerformedInitialPoll: Bool = (dependencies.caches[.openGroupManager].hasPerformedInitialPoll[server] == true)
|
||||
let timeSinceLastPoll: TimeInterval = (
|
||||
dependencies.cache.timeSinceLastPoll[server] ??
|
||||
dependencies.mutableCache.mutate { $0.getTimeSinceLastOpen(using: dependencies) }
|
||||
dependencies.caches[.openGroupManager].timeSinceLastPoll[server] ??
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||
cache.getTimeSinceLastOpen(using: dependencies)
|
||||
}
|
||||
)
|
||||
|
||||
return dependencies.storage
|
||||
|
@ -170,10 +167,11 @@ extension OpenGroupAPI {
|
|||
using: dependencies
|
||||
)
|
||||
|
||||
dependencies.mutableCache.mutate { cache in
|
||||
|
||||
dependencies.caches.mutate(cache: .openGroupManager) { cache in
|
||||
cache.hasPerformedInitialPoll[server] = true
|
||||
cache.timeSinceLastPoll[server] = Date().timeIntervalSince1970
|
||||
UserDefaults.standard[.lastOpen] = Date()
|
||||
cache.timeSinceLastPoll[server] = dependencies.dateNow.timeIntervalSince1970
|
||||
dependencies.standardUserDefaults[.lastOpen] = dependencies.dateNow
|
||||
}
|
||||
|
||||
SNLog("Open group polling finished for \(server).")
|
||||
|
@ -303,7 +301,7 @@ extension OpenGroupAPI {
|
|||
isBackgroundPollerValid: @escaping (() -> Bool) = { true },
|
||||
isPostCapabilitiesRetry: Bool,
|
||||
error: Error,
|
||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<Bool, Error> {
|
||||
/// We want to custom handle a '400' error code due to not having blinded auth as it likely means that we join the
|
||||
/// OpenGroup before blinding was enabled and need to update it's capabilities
|
||||
|
@ -376,7 +374,7 @@ extension OpenGroupAPI {
|
|||
info: ResponseInfoType,
|
||||
response: BatchResponse,
|
||||
failureCount: Int64,
|
||||
using dependencies: OpenGroupManager.OGMDependencies = OpenGroupManager.OGMDependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
let server: String = self.server
|
||||
let validResponses: [OpenGroupAPI.Endpoint: Decodable] = response.data
|
||||
|
@ -550,7 +548,7 @@ extension OpenGroupAPI {
|
|||
publicKey: nil,
|
||||
for: roomToken,
|
||||
on: server,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case .roomMessagesRecent(let roomToken), .roomMessagesBefore(let roomToken, _), .roomMessagesSince(let roomToken, _):
|
||||
|
@ -564,7 +562,7 @@ extension OpenGroupAPI {
|
|||
messages: responseBody.compactMap { $0.value },
|
||||
for: roomToken,
|
||||
on: server,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case .inbox, .inboxSince, .outbox, .outboxSince:
|
||||
|
@ -587,7 +585,7 @@ extension OpenGroupAPI {
|
|||
messages: messages,
|
||||
fromOutbox: fromOutbox,
|
||||
on: server,
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
default: break // No custom handling needed
|
||||
|
|
|
@ -53,18 +53,18 @@ public class Poller {
|
|||
}
|
||||
|
||||
/// Calculate the delay which should occur before the next poll
|
||||
internal func nextPollDelay(for publicKey: String) -> TimeInterval {
|
||||
internal func nextPollDelay(for publicKey: String, using dependencies: Dependencies) -> TimeInterval {
|
||||
preconditionFailure("abstract class - override in subclass")
|
||||
}
|
||||
|
||||
/// Perform and logic which should occur when the poll errors, will stop polling if `false` is returned
|
||||
internal func handlePollError(_ error: Error, for publicKey: String, using dependencies: SMKDependencies) -> Bool {
|
||||
internal func handlePollError(_ error: Error, for publicKey: String, using dependencies: Dependencies) -> Bool {
|
||||
preconditionFailure("abstract class - override in subclass")
|
||||
}
|
||||
|
||||
// MARK: - Private API
|
||||
|
||||
internal func startIfNeeded(for publicKey: String) {
|
||||
internal func startIfNeeded(for publicKey: String, using dependencies: Dependencies) {
|
||||
// Run on the 'pollerQueue' to ensure any 'Atomic' access doesn't block the main thread
|
||||
// on startup
|
||||
Threading.pollerQueue.async { [weak self] in
|
||||
|
@ -74,13 +74,13 @@ public class Poller {
|
|||
// and the timer is not created, if we mark the group as is polling
|
||||
// after setUpPolling. So the poller may not work, thus misses messages
|
||||
self?.isPolling.mutate { $0[publicKey] = true }
|
||||
self?.pollRecursively(for: publicKey)
|
||||
self?.pollRecursively(for: publicKey, using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
internal func getSnodeForPolling(
|
||||
for publicKey: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) -> AnyPublisher<Snode, Error> {
|
||||
// If we don't want to poll a snode multiple times then just grab a random one from the swarm
|
||||
guard maxNodePollCount > 0 else {
|
||||
|
@ -135,14 +135,14 @@ public class Poller {
|
|||
|
||||
private func pollRecursively(
|
||||
for publicKey: String,
|
||||
using dependencies: SMKDependencies = SMKDependencies()
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
guard isPolling.wrappedValue[publicKey] == true else { return }
|
||||
|
||||
let namespaces: [SnodeAPI.Namespace] = self.namespaces
|
||||
let lastPollStart: TimeInterval = Date().timeIntervalSince1970
|
||||
let lastPollInterval: TimeInterval = nextPollDelay(for: publicKey)
|
||||
let getSnodePublisher: AnyPublisher<Snode, Error> = getSnodeForPolling(for: publicKey)
|
||||
let lastPollStart: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||
let lastPollInterval: TimeInterval = nextPollDelay(for: publicKey, using: dependencies)
|
||||
let getSnodePublisher: AnyPublisher<Snode, Error> = getSnodeForPolling(for: publicKey, using: dependencies)
|
||||
|
||||
// Store the publisher intp the cancellables dictionary
|
||||
cancellables.mutate { [weak self] cancellables in
|
||||
|
@ -156,8 +156,8 @@ public class Poller {
|
|||
using: dependencies
|
||||
)
|
||||
}
|
||||
.subscribe(on: dependencies.subscribeQueue)
|
||||
.receive(on: dependencies.receiveQueue)
|
||||
.subscribe(on: Threading.pollerQueue, using: dependencies)
|
||||
.receive(on: Threading.pollerQueue, using: dependencies)
|
||||
.sink(
|
||||
receiveCompletion: { result in
|
||||
switch result {
|
||||
|
@ -174,21 +174,21 @@ public class Poller {
|
|||
self?.incrementPollCount(publicKey: publicKey)
|
||||
|
||||
// Calculate the remaining poll delay
|
||||
let currentTime: TimeInterval = Date().timeIntervalSince1970
|
||||
let currentTime: TimeInterval = dependencies.dateNow.timeIntervalSince1970
|
||||
let nextPollInterval: TimeInterval = (
|
||||
self?.nextPollDelay(for: publicKey) ??
|
||||
self?.nextPollDelay(for: publicKey, using: dependencies) ??
|
||||
lastPollInterval
|
||||
)
|
||||
let remainingInterval: TimeInterval = max(0, nextPollInterval - (currentTime - lastPollStart))
|
||||
|
||||
// Schedule the next poll
|
||||
guard remainingInterval > 0 else {
|
||||
return dependencies.subscribeQueue.async {
|
||||
return Threading.pollerQueue.async(using: dependencies) {
|
||||
self?.pollRecursively(for: publicKey, using: dependencies)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies.subscribeQueue.asyncAfter(deadline: .now() + .milliseconds(Int(remainingInterval * 1000)), qos: .default) {
|
||||
Threading.pollerQueue.asyncAfter(deadline: .now() + .milliseconds(Int(remainingInterval * 1000)), qos: .default, using: dependencies) {
|
||||
self?.pollRecursively(for: publicKey, using: dependencies)
|
||||
}
|
||||
},
|
||||
|
@ -209,10 +209,7 @@ public class Poller {
|
|||
calledFromBackgroundPoller: Bool = false,
|
||||
isBackgroundPollValid: @escaping (() -> Bool) = { true },
|
||||
poller: Poller? = nil,
|
||||
using dependencies: SMKDependencies = SMKDependencies(
|
||||
subscribeQueue: Threading.pollerQueue,
|
||||
receiveQueue: Threading.pollerQueue
|
||||
)
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) -> AnyPublisher<[Message], Error> {
|
||||
// If the polling has been cancelled then don't continue
|
||||
guard
|
||||
|
@ -276,7 +273,7 @@ public class Poller {
|
|||
var standardMessageJobsToRun: [Job] = []
|
||||
var pollerLogOutput: String = "\(pollerName) failed to process any messages"
|
||||
|
||||
Storage.shared.write { db in
|
||||
dependencies.storage.write { db in
|
||||
let allProcessedMessages: [ProcessedMessage] = allMessages
|
||||
.compactMap { message -> ProcessedMessage? in
|
||||
do {
|
||||
|
@ -333,8 +330,13 @@ public class Poller {
|
|||
// If we are force-polling then add to the JobRunner so they are
|
||||
// persistent and will retry on the next app run if they fail but
|
||||
// don't let them auto-start
|
||||
let updatedJob: Job? = JobRunner
|
||||
.add(db, job: jobToRun, canStartJob: !calledFromBackgroundPoller)
|
||||
let updatedJob: Job? = dependencies.jobRunner
|
||||
.add(
|
||||
db,
|
||||
job: jobToRun,
|
||||
canStartJob: !calledFromBackgroundPoller,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
return updatedJob?.id
|
||||
}
|
||||
|
@ -362,8 +364,13 @@ public class Poller {
|
|||
// If we are force-polling then add to the JobRunner so they are
|
||||
// persistent and will retry on the next app run if they fail but
|
||||
// don't let them auto-start
|
||||
let updatedJob: Job? = JobRunner
|
||||
.add(db, job: jobToRun, canStartJob: !calledFromBackgroundPoller)
|
||||
let updatedJob: Job? = dependencies.jobRunner
|
||||
.add(
|
||||
db,
|
||||
job: jobToRun,
|
||||
canStartJob: !calledFromBackgroundPoller,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Create the dependency between the jobs
|
||||
if let updatedJobId: Int64 = updatedJob?.id {
|
||||
|
@ -419,10 +426,11 @@ public class Poller {
|
|||
// Note: In the background we just want jobs to fail silently
|
||||
ConfigMessageReceiveJob.run(
|
||||
job,
|
||||
queue: dependencies.receiveQueue,
|
||||
success: { _, _ in resolver(Result.success(())) },
|
||||
failure: { _, _, _ in resolver(Result.success(())) },
|
||||
deferred: { _ in resolver(Result.success(())) }
|
||||
queue: Threading.pollerQueue,
|
||||
success: { _, _, _ in resolver(Result.success(())) },
|
||||
failure: { _, _, _, _ in resolver(Result.success(())) },
|
||||
deferred: { _, _ in resolver(Result.success(())) },
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -439,10 +447,11 @@ public class Poller {
|
|||
// Note: In the background we just want jobs to fail silently
|
||||
MessageReceiveJob.run(
|
||||
job,
|
||||
queue: dependencies.receiveQueue,
|
||||
success: { _, _ in resolver(Result.success(())) },
|
||||
failure: { _, _, _ in resolver(Result.success(())) },
|
||||
deferred: { _ in resolver(Result.success(())) }
|
||||
queue: Threading.pollerQueue,
|
||||
success: { _, _, _ in resolver(Result.success(())) },
|
||||
failure: { _, _, _, _ in resolver(Result.success(())) },
|
||||
deferred: { _, _ in resolver(Result.success(())) },
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,11 +54,11 @@ public class TypingIndicators {
|
|||
self.timestampMs = (timestampMs ?? SnodeAPI.currentOffsetTimestampMs())
|
||||
}
|
||||
|
||||
fileprivate func start(_ db: Database) {
|
||||
fileprivate func start(_ db: Database, using dependencies: Dependencies = Dependencies()) {
|
||||
// Start the typing indicator
|
||||
switch direction {
|
||||
case .outgoing:
|
||||
scheduleRefreshCallback(db, shouldSend: (refreshTimer == nil))
|
||||
scheduleRefreshCallback(db, shouldSend: (refreshTimer == nil), using: dependencies)
|
||||
|
||||
case .incoming:
|
||||
try? ThreadTypingIndicator(
|
||||
|
@ -72,7 +72,7 @@ public class TypingIndicators {
|
|||
refreshTimeout()
|
||||
}
|
||||
|
||||
fileprivate func stop(_ db: Database) {
|
||||
fileprivate func stop(_ db: Database, using dependencies: Dependencies = Dependencies()) {
|
||||
self.refreshTimer?.invalidate()
|
||||
self.refreshTimer = nil
|
||||
self.stopTimer?.invalidate()
|
||||
|
@ -85,7 +85,8 @@ public class TypingIndicators {
|
|||
message: TypingIndicator(kind: .stopped),
|
||||
interactionId: nil,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
case .incoming:
|
||||
|
@ -111,14 +112,19 @@ public class TypingIndicators {
|
|||
}
|
||||
}
|
||||
|
||||
private func scheduleRefreshCallback(_ db: Database, shouldSend: Bool = true) {
|
||||
private func scheduleRefreshCallback(
|
||||
_ db: Database,
|
||||
shouldSend: Bool = true,
|
||||
using dependencies: Dependencies
|
||||
) {
|
||||
if shouldSend {
|
||||
try? MessageSender.send(
|
||||
db,
|
||||
message: TypingIndicator(kind: .started),
|
||||
interactionId: nil,
|
||||
threadId: threadId,
|
||||
threadVariant: threadVariant
|
||||
threadVariant: threadVariant,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -127,8 +133,8 @@ public class TypingIndicators {
|
|||
withTimeInterval: 10,
|
||||
repeats: false
|
||||
) { [weak self] _ in
|
||||
Storage.shared.writeAsync { db in
|
||||
self?.scheduleRefreshCallback(db)
|
||||
dependencies.storage.writeAsync { db in
|
||||
self?.scheduleRefreshCallback(db, using: dependencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,8 @@ internal extension SessionUtil {
|
|||
_ db: Database,
|
||||
in conf: UnsafeMutablePointer<config_object>?,
|
||||
mergeNeedsDump: Bool,
|
||||
latestConfigSentTimestampMs: Int64
|
||||
latestConfigSentTimestampMs: Int64,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
guard mergeNeedsDump else { return }
|
||||
guard conf != nil else { throw SessionUtilError.nilConfigObject }
|
||||
|
@ -236,7 +237,8 @@ internal extension SessionUtil {
|
|||
admins: updatedAdmins.map { $0.profileId },
|
||||
expirationTimer: UInt32(group.disappearingConfig?.durationSeconds ?? 0),
|
||||
formationTimestampMs: UInt64((group.joinedAt.map { $0 * 1000 } ?? latestConfigSentTimestampMs)),
|
||||
calledFromConfigHandling: true
|
||||
calledFromConfigHandling: true,
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -18,7 +18,8 @@ internal extension SessionUtil {
|
|||
_ db: Database,
|
||||
in conf: UnsafeMutablePointer<config_object>?,
|
||||
mergeNeedsDump: Bool,
|
||||
latestConfigSentTimestampMs: Int64
|
||||
latestConfigSentTimestampMs: Int64,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) throws {
|
||||
typealias ProfileData = (profileName: String, profilePictureUrl: String?, profilePictureKey: Data?)
|
||||
|
||||
|
@ -51,7 +52,8 @@ internal extension SessionUtil {
|
|||
)
|
||||
}(),
|
||||
sentTimestamp: (TimeInterval(latestConfigSentTimestampMs) / 1000),
|
||||
calledFromConfigHandling: true
|
||||
calledFromConfigHandling: true,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
// Update the 'Note to Self' visibility and priority
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
#import "AppReadiness.h"
|
||||
#import "AppContext.h"
|
||||
#import <SignalCoreKit/SignalCoreKit.h>
|
||||
#import <SignalCoreKit/Threading.h>
|
||||
#import <SessionUtilitiesKit/SessionUtilitiesKit.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
@ -63,7 +66,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (void)runNowOrWhenAppWillBecomeReady:(AppReadyBlock)block
|
||||
{
|
||||
if (CurrentAppContext().isRunningTests) {
|
||||
if ([SNUtilitiesKitConfiguration isRunningTests]) {
|
||||
// We don't need to do any "on app ready" work in the tests.
|
||||
return;
|
||||
}
|
||||
|
@ -90,7 +93,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (void)runNowOrWhenAppDidBecomeReady:(AppReadyBlock)block
|
||||
{
|
||||
if (CurrentAppContext().isRunningTests) {
|
||||
if ([SNUtilitiesKitConfiguration isRunningTests]) {
|
||||
// We don't need to do any "on app ready" work in the tests.
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
// 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: - AeadXChaCha20Poly1305Ietf
|
||||
|
||||
public extension Crypto.Size {
|
||||
static let aeadXChaCha20NonceBytes: Crypto.Size = Crypto.Size(id: "aeadXChaCha20NonceBytes") {
|
||||
Sodium().aead.xchacha20poly1305ietf.NonceBytes
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Ed25519
|
||||
|
||||
public extension Crypto.Action {
|
||||
static func signEd25519(data: Bytes, keyPair: KeyPair) -> Crypto.Action {
|
||||
return Crypto.Action(id: "signEd25519", args: [data, keyPair]) {
|
||||
let ecKeyPair: ECKeyPair = try ECKeyPair(
|
||||
publicKeyData: Data(keyPair.publicKey),
|
||||
privateKeyData: Data(keyPair.secretKey)
|
||||
)
|
||||
|
||||
return try Ed25519.sign(Data(data), with: ecKeyPair).bytes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Crypto.Verification {
|
||||
static func signatureEd25519(_ signature: Data, publicKey: Data, data: Data) -> Crypto.Verification {
|
||||
return Crypto.Verification(id: "signatureEd25519", args: [signature, publicKey, data]) {
|
||||
return ((try? Ed25519.verifySignature(signature, publicKey: publicKey, data: data)) == true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension Crypto.KeyPairType {
|
||||
static func x25519KeyPair() -> Crypto.KeyPairType {
|
||||
return Crypto.KeyPairType(id: "x25519KeyPair") {
|
||||
let keyPair: ECKeyPair = Curve25519.generateKeyPair()
|
||||
|
||||
return KeyPair(publicKey: Array(keyPair.publicKey), secretKey: Array(keyPair.privateKey))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -286,9 +286,10 @@ public struct ProfileManager {
|
|||
profileName: String,
|
||||
avatarUpdate: AvatarUpdate = .none,
|
||||
success: ((Database) throws -> ())? = nil,
|
||||
failure: ((ProfileManagerError) -> ())? = nil
|
||||
failure: ((ProfileManagerError) -> ())? = nil,
|
||||
using dependencies: Dependencies = Dependencies()
|
||||
) {
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey()
|
||||
let userPublicKey: String = getUserHexEncodedPublicKey(using: dependencies)
|
||||
let isRemovingAvatar: Bool = {
|
||||
switch avatarUpdate {
|
||||
case .remove: return true
|
||||
|
@ -298,7 +299,7 @@ public struct ProfileManager {
|
|||
|
||||
switch avatarUpdate {
|
||||
case .none, .remove, .updateTo:
|
||||
Storage.shared.writeAsync { db in
|
||||
dependencies.storage.writeAsync { db in
|
||||
if isRemovingAvatar {
|
||||
let existingProfileUrl: String? = try Profile
|
||||
.filter(id: userPublicKey)
|
||||
|
@ -327,7 +328,8 @@ public struct ProfileManager {
|
|||
publicKey: userPublicKey,
|
||||
name: profileName,
|
||||
avatarUpdate: avatarUpdate,
|
||||
sentTimestamp: Date().timeIntervalSince1970
|
||||
sentTimestamp: dependencies.dateNow.timeIntervalSince1970,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
SNLog("Successfully updated service with profile.")
|
||||
|
@ -345,7 +347,8 @@ public struct ProfileManager {
|
|||
publicKey: userPublicKey,
|
||||
name: profileName,
|
||||
avatarUpdate: .updateTo(url: downloadUrl, key: newProfileKey, fileName: fileName),
|
||||
sentTimestamp: Date().timeIntervalSince1970
|
||||
sentTimestamp: dependencies.dateNow.timeIntervalSince1970,
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
SNLog("Successfully updated service with profile.")
|
||||
|
@ -498,9 +501,9 @@ public struct ProfileManager {
|
|||
avatarUpdate: AvatarUpdate,
|
||||
sentTimestamp: TimeInterval,
|
||||
calledFromConfigHandling: Bool = false,
|
||||
dependencies: Dependencies = Dependencies()
|
||||
using dependencies: Dependencies
|
||||
) throws {
|
||||
let isCurrentUser = (publicKey == getUserHexEncodedPublicKey(db, dependencies: dependencies))
|
||||
let isCurrentUser = (publicKey == getUserHexEncodedPublicKey(db, using: dependencies))
|
||||
let profile: Profile = Profile.fetchOrCreate(db, id: publicKey)
|
||||
var profileChanges: [ConfigColumnAssignment] = []
|
||||
|
||||
|
@ -604,7 +607,7 @@ public struct ProfileManager {
|
|||
let targetProfile: Profile = Profile.fetchOrCreate(db, id: publicKey)
|
||||
|
||||
// FIXME: Refactor avatar downloading to be a proper Job so we can avoid this
|
||||
JobRunner.afterBlockingQueue {
|
||||
dependencies.jobRunner.afterBlockingQueue {
|
||||
ProfileManager.downloadAvatar(for: targetProfile)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,285 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import CryptoKit
|
||||
import Clibsodium
|
||||
import Sodium
|
||||
import Curve25519Kit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
/// These extenion methods are used to generate a sign "blinded" messages
|
||||
///
|
||||
/// According to the Swift engineers the only situation when `UnsafeRawBufferPointer.baseAddress` is nil is when it's an
|
||||
/// empty collection; as such our guard cases wihch return `-1` when unwrapping this value should never be hit and we can ignore
|
||||
/// them as possible results.
|
||||
///
|
||||
/// For more information see:
|
||||
/// https://forums.swift.org/t/when-is-unsafemutablebufferpointer-baseaddress-nil/32136/5
|
||||
/// https://github.com/apple/swift-evolution/blob/master/proposals/0055-optional-unsafe-pointers.md#unsafebufferpointer
|
||||
extension Sodium {
|
||||
private static let scalarLength: Int = Int(crypto_core_ed25519_scalarbytes()) // 32
|
||||
private static let noClampLength: Int = Int(Sodium.lib_crypto_scalarmult_ed25519_bytes()) // 32
|
||||
private static let scalarMultLength: Int = Int(crypto_scalarmult_bytes()) // 32
|
||||
private static let publicKeyLength: Int = Int(crypto_scalarmult_bytes()) // 32
|
||||
private static let secretKeyLength: Int = Int(crypto_sign_secretkeybytes()) // 64
|
||||
|
||||
/// 64-byte blake2b hash then reduce to get the blinding factor
|
||||
public func generateBlindingFactor(serverPublicKey: String, genericHash: GenericHashType) -> Bytes? {
|
||||
/// k = salt.crypto_core_ed25519_scalar_reduce(blake2b(server_pk, digest_size=64).digest())
|
||||
let serverPubKeyData: Data = Data(hex: serverPublicKey)
|
||||
|
||||
guard !serverPubKeyData.isEmpty, let serverPublicKeyHashBytes: Bytes = genericHash.hash(message: [UInt8](serverPubKeyData), outputLength: 64) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Reduce the server public key into an ed25519 scalar (`k`)
|
||||
let kPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
|
||||
|
||||
_ = serverPublicKeyHashBytes.withUnsafeBytes { (serverPublicKeyHashPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let serverPublicKeyHashBaseAddress: UnsafePointer<UInt8> = serverPublicKeyHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_reduce(kPtr, serverPublicKeyHashBaseAddress)
|
||||
return 0
|
||||
}
|
||||
|
||||
return Data(bytes: kPtr, count: Sodium.scalarLength).bytes
|
||||
}
|
||||
|
||||
/// Calculate k*a. To get 'a' (the Ed25519 private key scalar) we call the sodium function to
|
||||
/// convert to an *x* secret key, which seems wrong--but isn't because converted keys use the
|
||||
/// same secret scalar secret (and so this is just the most convenient way to get 'a' out of
|
||||
/// a sodium Ed25519 secret key)
|
||||
func generatePrivateKeyScalar(secretKey: Bytes) -> Bytes {
|
||||
/// a = s.to_curve25519_private_key().encode()
|
||||
let aPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarMultLength)
|
||||
|
||||
/// Looks like the `crypto_sign_ed25519_sk_to_curve25519` function can't actually fail so no need to verify the result
|
||||
/// See: https://github.com/jedisct1/libsodium/blob/master/src/libsodium/crypto_sign/ed25519/ref10/keypair.c#L70
|
||||
_ = secretKey.withUnsafeBytes { (secretKeyPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let secretKeyBaseAddress: UnsafePointer<UInt8> = secretKeyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
return crypto_sign_ed25519_sk_to_curve25519(aPtr, secretKeyBaseAddress)
|
||||
}
|
||||
|
||||
return Data(bytes: aPtr, count: Sodium.scalarMultLength).bytes
|
||||
}
|
||||
|
||||
/// Constructs a "blinded" key pair (`ka, kA`) based on an open group server `publicKey` and an ed25519 `keyPair`
|
||||
public func blindedKeyPair(serverPublicKey: String, edKeyPair: KeyPair, genericHash: GenericHashType) -> KeyPair? {
|
||||
guard edKeyPair.publicKey.count == Sodium.publicKeyLength && edKeyPair.secretKey.count == Sodium.secretKeyLength else {
|
||||
return nil
|
||||
}
|
||||
guard let kBytes: Bytes = generateBlindingFactor(serverPublicKey: serverPublicKey, genericHash: genericHash) else {
|
||||
return nil
|
||||
}
|
||||
let aBytes: Bytes = generatePrivateKeyScalar(secretKey: edKeyPair.secretKey)
|
||||
|
||||
/// Generate the blinded key pair `ka`, `kA`
|
||||
let kaPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.secretKeyLength)
|
||||
let kAPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.publicKeyLength)
|
||||
|
||||
_ = aBytes.withUnsafeBytes { (aPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
return kBytes.withUnsafeBytes { (kPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let kBaseAddress: UnsafePointer<UInt8> = kPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
guard let aBaseAddress: UnsafePointer<UInt8> = aPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_mul(kaPtr, kBaseAddress, aBaseAddress)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
guard crypto_scalarmult_ed25519_base_noclamp(kAPtr, kaPtr) == 0 else { return nil }
|
||||
|
||||
return KeyPair(
|
||||
publicKey: Data(bytes: kAPtr, count: Sodium.publicKeyLength).bytes,
|
||||
secretKey: Data(bytes: kaPtr, count: Sodium.secretKeyLength).bytes
|
||||
)
|
||||
}
|
||||
|
||||
/// Constructs an Ed25519 signature from a root Ed25519 key and a blinded scalar/pubkey pair, with one tweak to the
|
||||
/// construction: we add kA into the hashed value that yields r so that we have domain separation for different blinded
|
||||
/// pubkeys (this doesn't affect verification at all)
|
||||
public func sogsSignature(message: Bytes, secretKey: Bytes, blindedSecretKey ka: Bytes, blindedPublicKey kA: Bytes) -> Bytes? {
|
||||
/// H_rh = sha512(s.encode()).digest()[32:]
|
||||
let H_rh: Bytes = Bytes(SHA512.hash(data: secretKey).suffix(32))
|
||||
|
||||
/// r = salt.crypto_core_ed25519_scalar_reduce(sha512_multipart(H_rh, kA, message_parts))
|
||||
let combinedHashBytes: Bytes = SHA512.hash(data: H_rh + kA + message).bytes
|
||||
let rPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
|
||||
|
||||
_ = combinedHashBytes.withUnsafeBytes { (combinedHashPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let combinedHashBaseAddress: UnsafePointer<UInt8> = combinedHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_reduce(rPtr, combinedHashBaseAddress)
|
||||
return 0
|
||||
}
|
||||
|
||||
/// sig_R = salt.crypto_scalarmult_ed25519_base_noclamp(r)
|
||||
let sig_RPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.noClampLength)
|
||||
guard crypto_scalarmult_ed25519_base_noclamp(sig_RPtr, rPtr) == 0 else { return nil }
|
||||
|
||||
/// HRAM = salt.crypto_core_ed25519_scalar_reduce(sha512_multipart(sig_R, kA, message_parts))
|
||||
let sig_RBytes: Bytes = Data(bytes: sig_RPtr, count: Sodium.noClampLength).bytes
|
||||
let HRAMHashBytes: Bytes = SHA512.hash(data: sig_RBytes + kA + message).bytes
|
||||
let HRAMPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
|
||||
|
||||
_ = HRAMHashBytes.withUnsafeBytes { (HRAMHashPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let HRAMHashBaseAddress: UnsafePointer<UInt8> = HRAMHashPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_reduce(HRAMPtr, HRAMHashBaseAddress)
|
||||
return 0
|
||||
}
|
||||
|
||||
/// sig_s = salt.crypto_core_ed25519_scalar_add(r, salt.crypto_core_ed25519_scalar_mul(HRAM, ka))
|
||||
let sig_sMulPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
|
||||
let sig_sPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.scalarLength)
|
||||
|
||||
_ = ka.withUnsafeBytes { (kaPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let kaBaseAddress: UnsafePointer<UInt8> = kaPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
Sodium.lib_crypto_core_ed25519_scalar_mul(sig_sMulPtr, HRAMPtr, kaBaseAddress)
|
||||
Sodium.lib_crypto_core_ed25519_scalar_add(sig_sPtr, rPtr, sig_sMulPtr)
|
||||
return 0
|
||||
}
|
||||
|
||||
/// full_sig = sig_R + sig_s
|
||||
return (Data(bytes: sig_RPtr, count: Sodium.noClampLength).bytes + Data(bytes: sig_sPtr, count: Sodium.scalarLength).bytes)
|
||||
}
|
||||
|
||||
/// Combines two keys (`kA`)
|
||||
public func combineKeys(lhsKeyBytes: Bytes, rhsKeyBytes: Bytes) -> Bytes? {
|
||||
let combinedPtr: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>.allocate(capacity: Sodium.noClampLength)
|
||||
|
||||
let result = rhsKeyBytes.withUnsafeBytes { (rhsKeyBytesPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
return lhsKeyBytes.withUnsafeBytes { (lhsKeyBytesPtr: UnsafeRawBufferPointer) -> Int32 in
|
||||
guard let lhsKeyBytesBaseAddress: UnsafePointer<UInt8> = lhsKeyBytesPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
guard let rhsKeyBytesBaseAddress: UnsafePointer<UInt8> = rhsKeyBytesPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
||||
return -1 // Impossible case (refer to comments at top of extension)
|
||||
}
|
||||
|
||||
return Sodium.lib_crypto_scalarmult_ed25519_noclamp(combinedPtr, lhsKeyBytesBaseAddress, rhsKeyBytesBaseAddress)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure the above worked
|
||||
guard result == 0 else { return nil }
|
||||
|
||||
return Data(bytes: combinedPtr, count: Sodium.noClampLength).bytes
|
||||
}
|
||||
|
||||
/// Calculate a shared secret for a message from A to B:
|
||||
///
|
||||
/// BLAKE2b(a kB || kA || kB)
|
||||
///
|
||||
/// The receiver can calulate the same value via:
|
||||
///
|
||||
/// BLAKE2b(b kA || kA || kB)
|
||||
public func sharedBlindedEncryptionKey(secretKey: Bytes, otherBlindedPublicKey: Bytes, fromBlindedPublicKey kA: Bytes, toBlindedPublicKey kB: Bytes, genericHash: GenericHashType) -> Bytes? {
|
||||
let aBytes: Bytes = generatePrivateKeyScalar(secretKey: secretKey)
|
||||
|
||||
guard let combinedKeyBytes: Bytes = combineKeys(lhsKeyBytes: aBytes, rhsKeyBytes: otherBlindedPublicKey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return genericHash.hash(message: (combinedKeyBytes + kA + kB), outputLength: 32)
|
||||
}
|
||||
|
||||
/// This method should be used to check if a users standard sessionId matches a blinded one
|
||||
public func sessionId(_ standardSessionId: String, matchesBlindedId blindedSessionId: String, serverPublicKey: String, genericHash: GenericHashType) -> Bool {
|
||||
// Only support generating blinded keys for standard session ids
|
||||
guard
|
||||
let sessionId: SessionId = SessionId(from: standardSessionId),
|
||||
sessionId.prefix == .standard,
|
||||
let blindedId: SessionId = SessionId(from: blindedSessionId),
|
||||
(
|
||||
blindedId.prefix == .blinded15 ||
|
||||
blindedId.prefix == .blinded25
|
||||
),
|
||||
let kBytes: Bytes = generateBlindingFactor(serverPublicKey: serverPublicKey, genericHash: genericHash)
|
||||
else { return false }
|
||||
|
||||
/// From the session id (ignoring 05 prefix) we have two possible ed25519 pubkeys; the first is the positive (which is what
|
||||
/// Signal's XEd25519 conversion always uses)
|
||||
///
|
||||
/// Note: The below method is code we have exposed from the `curve25519_verify` method within the Curve25519 library
|
||||
/// rather than custom code we have written
|
||||
guard let xEd25519Key: Data = try? Ed25519.publicKey(from: Data(hex: sessionId.publicKey)) else { return false }
|
||||
|
||||
/// Blind the positive public key
|
||||
guard let pk1: Bytes = combineKeys(lhsKeyBytes: kBytes, rhsKeyBytes: xEd25519Key.bytes) else { return false }
|
||||
|
||||
/// For the negative, what we're going to get out of the above is simply the negative of pk1, so flip the sign bit to get pk2
|
||||
/// pk2 = pk1[0:31] + bytes([pk1[31] ^ 0b1000_0000])
|
||||
let pk2: Bytes = (pk1[0..<31] + [(pk1[31] ^ 0b1000_0000)])
|
||||
|
||||
return (
|
||||
SessionId(.blinded15, publicKey: pk1).publicKey == blindedId.publicKey ||
|
||||
SessionId(.blinded15, publicKey: pk2).publicKey == blindedId.publicKey
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension GenericHash {
|
||||
public func hashSaltPersonal(
|
||||
message: Bytes,
|
||||
outputLength: Int,
|
||||
key: Bytes? = nil,
|
||||
salt: Bytes,
|
||||
personal: Bytes
|
||||
) -> Bytes? {
|
||||
var output: [UInt8] = [UInt8](repeating: 0, count: outputLength)
|
||||
|
||||
let result = crypto_generichash_blake2b_salt_personal(
|
||||
&output,
|
||||
outputLength,
|
||||
message,
|
||||
UInt64(message.count),
|
||||
key,
|
||||
(key?.count ?? 0),
|
||||
salt,
|
||||
personal
|
||||
)
|
||||
|
||||
guard result == 0 else { return nil }
|
||||
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
extension AeadXChaCha20Poly1305IetfType {
|
||||
/// This method is the same as the standard AeadXChaCha20Poly1305IetfType `encrypt` method except it allows the
|
||||
/// specification of a nonce which allows for deterministic behaviour with unit testing
|
||||
public func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes? = nil) -> Bytes? {
|
||||
guard secretKey.count == KeyBytes else { return nil }
|
||||
|
||||
var authenticatedCipherText = Bytes(repeating: 0, count: message.count + ABytes)
|
||||
var authenticatedCipherTextLen: UInt64 = 0
|
||||
|
||||
let result = crypto_aead_xchacha20poly1305_ietf_encrypt(
|
||||
&authenticatedCipherText, &authenticatedCipherTextLen,
|
||||
message, UInt64(message.count),
|
||||
additionalData, UInt64(additionalData?.count ?? 0),
|
||||
nil, nonce, secretKey
|
||||
)
|
||||
|
||||
guard result == 0 else { return nil }
|
||||
|
||||
return authenticatedCipherText
|
||||
}
|
||||
}
|
|
@ -0,0 +1,419 @@
|
|||
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
@testable import SessionUtilitiesKit
|
||||
|
||||
class MessageSendJobSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
var job: Job!
|
||||
var interaction: Interaction!
|
||||
var attachment: Attachment!
|
||||
var interactionAttachment: InteractionAttachment!
|
||||
var mockStorage: Storage!
|
||||
var mockJobRunner: MockJobRunner!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
// MARK: - JobRunner
|
||||
|
||||
describe("a MessageSendJob") {
|
||||
// MARK: - Configuration
|
||||
|
||||
beforeEach {
|
||||
mockStorage = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
SNMessagingKit.self
|
||||
]
|
||||
)
|
||||
mockJobRunner = MockJobRunner()
|
||||
dependencies = Dependencies(
|
||||
storage: mockStorage,
|
||||
jobRunner: mockJobRunner,
|
||||
dateNow: Date(timeIntervalSince1970: 1234567890)
|
||||
)
|
||||
attachment = Attachment(
|
||||
id: "200",
|
||||
variant: .standard,
|
||||
state: .failedDownload,
|
||||
contentType: "text/plain",
|
||||
byteCount: 200
|
||||
)
|
||||
|
||||
mockStorage.write { db in
|
||||
try SessionThread.fetchOrCreate(db, id: "Test1", variant: .contact, shouldBeVisible: true)
|
||||
}
|
||||
|
||||
mockJobRunner
|
||||
.when {
|
||||
$0.jobInfoFor(
|
||||
jobs: nil,
|
||||
state: .running,
|
||||
variant: .attachmentUpload
|
||||
)
|
||||
}
|
||||
.thenReturn([:])
|
||||
mockJobRunner
|
||||
.when { $0.insert(any(), job: any(), before: any()) }
|
||||
.then { args in
|
||||
let db: Database = args[0] as! Database
|
||||
var job: Job = args[1] as! Job
|
||||
job.id = 1000
|
||||
|
||||
try! job.insert(db)
|
||||
}
|
||||
.thenReturn((1000, Job(variant: .messageSend)))
|
||||
}
|
||||
|
||||
afterEach {
|
||||
job = nil
|
||||
mockStorage = nil
|
||||
dependencies = nil
|
||||
}
|
||||
|
||||
// MARK: - fails when not given any details
|
||||
it("fails when not given any details") {
|
||||
job = Job(variant: .messageSend)
|
||||
|
||||
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 },
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
||||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: - fails when given incorrect details
|
||||
it("fails when given incorrect details") {
|
||||
job = Job(
|
||||
variant: .messageSend,
|
||||
details: MessageReceiveJob.Details(messages: [], calledFromBackgroundPoller: false)
|
||||
)
|
||||
|
||||
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 },
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
||||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: - of VisibleMessage
|
||||
context("of VisibleMessage") {
|
||||
beforeEach {
|
||||
interaction = Interaction(
|
||||
id: 100,
|
||||
serverHash: nil,
|
||||
messageUuid: nil,
|
||||
threadId: "Test1",
|
||||
authorId: "Test",
|
||||
variant: .standardOutgoing,
|
||||
body: "Test",
|
||||
timestampMs: 1234567890,
|
||||
receivedAtTimestampMs: 1234567900,
|
||||
wasRead: false,
|
||||
hasMention: false,
|
||||
expiresInSeconds: nil,
|
||||
expiresStartedAtMs: nil,
|
||||
linkPreviewUrl: nil,
|
||||
openGroupServerMessageId: nil,
|
||||
openGroupWhisperMods: false,
|
||||
openGroupWhisperTo: nil
|
||||
)
|
||||
job = Job(
|
||||
variant: .messageSend,
|
||||
interactionId: interaction.id!,
|
||||
details: MessageSendJob.Details(
|
||||
destination: .contact(publicKey: "Test"),
|
||||
message: VisibleMessage(
|
||||
text: "Test"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
mockStorage.write { db in
|
||||
try interaction.insert(db)
|
||||
try job.insert(db)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- fails when there is no job id
|
||||
it("fails when there is no job id") {
|
||||
job = Job(
|
||||
variant: .messageSend,
|
||||
interactionId: interaction.id!,
|
||||
details: MessageSendJob.Details(
|
||||
destination: .contact(publicKey: "Test"),
|
||||
message: VisibleMessage(
|
||||
text: "Test"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
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 },
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
||||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: -- fails when there is no interaction id
|
||||
it("fails when there is no interaction id") {
|
||||
job = Job(
|
||||
variant: .messageSend,
|
||||
details: MessageSendJob.Details(
|
||||
destination: .contact(publicKey: "Test"),
|
||||
message: VisibleMessage(
|
||||
text: "Test"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
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 },
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(error).to(matchError(JobRunnerError.missingRequiredDetails))
|
||||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: -- fails when there is no interaction for the provided interaction id
|
||||
it("fails when there is no interaction for the provided interaction id") {
|
||||
job = Job(
|
||||
variant: .messageSend,
|
||||
interactionId: 12345,
|
||||
details: MessageSendJob.Details(
|
||||
destination: .contact(publicKey: "Test"),
|
||||
message: VisibleMessage(
|
||||
text: "Test"
|
||||
)
|
||||
)
|
||||
)
|
||||
mockStorage.write { db in try job.insert(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 },
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(error).to(matchError(StorageError.objectNotFound))
|
||||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: -- with an attachment
|
||||
context("with an attachment") {
|
||||
beforeEach {
|
||||
interactionAttachment = InteractionAttachment(
|
||||
albumIndex: 0,
|
||||
interactionId: interaction.id!,
|
||||
attachmentId: attachment.id
|
||||
)
|
||||
|
||||
mockStorage.write { db in
|
||||
try attachment.insert(db)
|
||||
try interactionAttachment.insert(db)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ---- it fails when trying to send with an attachment which previously failed to download
|
||||
it("it fails when trying to send with an attachment which previously failed to download") {
|
||||
mockStorage.write { db in
|
||||
try attachment.with(state: .failedDownload).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 },
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(error).to(matchError(AttachmentError.notUploaded))
|
||||
expect(permanentFailure).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ---- with a pending upload
|
||||
context("with a pending upload") {
|
||||
beforeEach {
|
||||
mockStorage.write { db in
|
||||
try attachment.with(state: .uploading).save(db)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: ------ it defers when trying to send with an attachment which is still pending upload
|
||||
it("it defers when trying to send with an attachment which is still pending upload") {
|
||||
var didDefer: Bool = false
|
||||
|
||||
mockStorage.write { db in
|
||||
try attachment.with(state: .uploading).save(db)
|
||||
}
|
||||
|
||||
MessageSendJob.run(
|
||||
job,
|
||||
queue: .main,
|
||||
success: { _, _, _ in },
|
||||
failure: { _, _, _, _ in },
|
||||
deferred: { _, _ in didDefer = true },
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(didDefer).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ it defers when trying to send with an uploaded attachment that has an invalid downloadUrl
|
||||
it("it defers when trying to send with an uploaded attachment that has an invalid downloadUrl") {
|
||||
var didDefer: Bool = false
|
||||
|
||||
mockStorage.write { db in
|
||||
try attachment
|
||||
.with(
|
||||
state: .uploaded,
|
||||
downloadUrl: nil
|
||||
)
|
||||
.save(db)
|
||||
}
|
||||
|
||||
MessageSendJob.run(
|
||||
job,
|
||||
queue: .main,
|
||||
success: { _, _, _ in },
|
||||
failure: { _, _, _, _ in },
|
||||
deferred: { _, _ in didDefer = true },
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(didDefer).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: ------ inserts an attachment upload job before the message send job
|
||||
it("inserts an attachment upload job before the message send job") {
|
||||
mockJobRunner
|
||||
.when {
|
||||
$0.jobInfoFor(
|
||||
jobs: nil,
|
||||
state: .running,
|
||||
variant: .attachmentUpload
|
||||
)
|
||||
}
|
||||
.thenReturn([:])
|
||||
|
||||
MessageSendJob.run(
|
||||
job,
|
||||
queue: .main,
|
||||
success: { _, _, _ in },
|
||||
failure: { _, _, _, _ in },
|
||||
deferred: { _, _ in },
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(mockJobRunner)
|
||||
.to(call(.exactly(times: 1), matchingParameters: true) {
|
||||
$0.insert(
|
||||
any(),
|
||||
job: Job(
|
||||
variant: .attachmentUpload,
|
||||
behaviour: .runOnce,
|
||||
shouldBlock: false,
|
||||
shouldSkipLaunchBecomeActive: false,
|
||||
interactionId: 100,
|
||||
details: AttachmentUploadJob.Details(
|
||||
messageSendJobId: 1,
|
||||
attachmentId: "200"
|
||||
)
|
||||
),
|
||||
before: job
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// MARK: ------ creates a dependency between the new job and the existing one
|
||||
it("creates a dependency between the new job and the existing one") {
|
||||
MessageSendJob.run(
|
||||
job,
|
||||
queue: .main,
|
||||
success: { _, _, _ in },
|
||||
failure: { _, _, _, _ in },
|
||||
deferred: { _, _ in },
|
||||
using: dependencies
|
||||
)
|
||||
|
||||
expect(mockStorage.read { db in try JobDependencies.fetchOne(db) })
|
||||
.to(equal(JobDependencies(jobId: 9, dependantId: 1000)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,11 +44,13 @@ class BatchRequestInfoSpec: QuickSpec {
|
|||
)
|
||||
]
|
||||
)
|
||||
let requestData: Data = try! JSONEncoder().encode(request)
|
||||
let requestString: String? = String(data: requestData, encoding: .utf8)
|
||||
|
||||
expect(requestString)
|
||||
.to(equal("[{\"path\":\"\\/batch\",\"method\":\"GET\",\"b64\":\"testBody\"}]"))
|
||||
let requestData: Data? = try? JSONEncoder().encode(request)
|
||||
let requestJson: [[String: Any]]? = requestData
|
||||
.map { try? JSONSerialization.jsonObject(with: $0) as? [[String: Any]] }
|
||||
expect(requestJson?.first?["path"] as? String).to(equal("/batch"))
|
||||
expect(requestJson?.first?["method"] as? String).to(equal("GET"))
|
||||
expect(requestJson?.first?["b64"] as? String).to(equal("testBody"))
|
||||
}
|
||||
|
||||
it("successfully encodes a byte body") {
|
||||
|
@ -70,11 +72,13 @@ class BatchRequestInfoSpec: QuickSpec {
|
|||
)
|
||||
]
|
||||
)
|
||||
let requestData: Data = try! JSONEncoder().encode(request)
|
||||
let requestString: String? = String(data: requestData, encoding: .utf8)
|
||||
|
||||
expect(requestString)
|
||||
.to(equal("[{\"path\":\"\\/batch\",\"method\":\"GET\",\"bytes\":[1,2,3]}]"))
|
||||
let requestData: Data? = try? JSONEncoder().encode(request)
|
||||
let requestJson: [[String: Any]]? = requestData
|
||||
.map { try? JSONSerialization.jsonObject(with: $0) as? [[String: Any]] }
|
||||
expect(requestJson?.first?["path"] as? String).to(equal("/batch"))
|
||||
expect(requestJson?.first?["method"] as? String).to(equal("GET"))
|
||||
expect(requestJson?.first?["bytes"] as? [Int]).to(equal([1, 2, 3]))
|
||||
}
|
||||
|
||||
it("successfully encodes a JSON body") {
|
||||
|
@ -96,11 +100,13 @@ class BatchRequestInfoSpec: QuickSpec {
|
|||
)
|
||||
]
|
||||
)
|
||||
let requestData: Data = try! JSONEncoder().encode(request)
|
||||
let requestString: String? = String(data: requestData, encoding: .utf8)
|
||||
|
||||
expect(requestString)
|
||||
.to(equal("[{\"path\":\"\\/batch\",\"method\":\"GET\",\"json\":{\"stringValue\":\"testValue\"}}]"))
|
||||
let requestData: Data? = try? JSONEncoder().encode(request)
|
||||
let requestJson: [[String: Any]]? = requestData
|
||||
.map { try? JSONSerialization.jsonObject(with: $0) as? [[String: Any]] }
|
||||
expect(requestJson?.first?["path"] as? String).to(equal("/batch"))
|
||||
expect(requestJson?.first?["method"] as? String).to(equal("GET"))
|
||||
expect(requestJson?.first?["json"] as? [String: String]).to(equal(["stringValue": "testValue"]))
|
||||
}
|
||||
|
||||
it("strips authentication headers") {
|
||||
|
|
|
@ -16,9 +16,8 @@ class SOGSMessageSpec: QuickSpec {
|
|||
var messageJson: String!
|
||||
var messageData: Data!
|
||||
var decoder: JSONDecoder!
|
||||
var mockSign: MockSign!
|
||||
var mockEd25519: MockEd25519!
|
||||
var dependencies: SMKDependencies!
|
||||
var mockCrypto: MockCrypto!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
beforeEach {
|
||||
messageJson = """
|
||||
|
@ -35,18 +34,16 @@ class SOGSMessageSpec: QuickSpec {
|
|||
}
|
||||
"""
|
||||
messageData = messageJson.data(using: .utf8)!
|
||||
mockSign = MockSign()
|
||||
mockEd25519 = MockEd25519()
|
||||
dependencies = SMKDependencies(
|
||||
sign: mockSign,
|
||||
ed25519: mockEd25519
|
||||
mockCrypto = MockCrypto()
|
||||
dependencies = Dependencies(
|
||||
crypto: mockCrypto
|
||||
)
|
||||
decoder = JSONDecoder()
|
||||
decoder.userInfo = [ Dependencies.userInfoKey: dependencies as Any ]
|
||||
}
|
||||
|
||||
afterEach {
|
||||
mockSign = nil
|
||||
mockCrypto = nil
|
||||
}
|
||||
|
||||
context("when decoding") {
|
||||
|
@ -204,8 +201,10 @@ class SOGSMessageSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("succeeds if it succeeds verification") {
|
||||
mockSign
|
||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
$0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray()))
|
||||
}
|
||||
.thenReturn(true)
|
||||
|
||||
expect {
|
||||
|
@ -215,25 +214,31 @@ class SOGSMessageSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("provides the correct values as parameters") {
|
||||
mockSign
|
||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
$0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray()))
|
||||
}
|
||||
.thenReturn(true)
|
||||
|
||||
_ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
||||
|
||||
expect(mockSign)
|
||||
expect(mockCrypto)
|
||||
.to(call(matchingParameters: true) {
|
||||
$0.verify(
|
||||
message: Data(base64Encoded: "VGVzdERhdGE=")!.bytes,
|
||||
publicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
signature: Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!.bytes
|
||||
.signature(
|
||||
message: Data(base64Encoded: "VGVzdERhdGE=")!.bytes,
|
||||
publicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
signature: Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!.bytes
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
it("throws if it fails verification") {
|
||||
mockSign
|
||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
$0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray()))
|
||||
}
|
||||
.thenReturn(false)
|
||||
|
||||
expect {
|
||||
|
@ -245,7 +250,9 @@ class SOGSMessageSpec: QuickSpec {
|
|||
|
||||
context("that is unblinded") {
|
||||
it("succeeds if it succeeds verification") {
|
||||
mockEd25519.when { try $0.verifySignature(any(), publicKey: any(), data: any()) }.thenReturn(true)
|
||||
mockCrypto
|
||||
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
||||
.thenReturn(true)
|
||||
|
||||
expect {
|
||||
try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
||||
|
@ -254,22 +261,28 @@ class SOGSMessageSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("provides the correct values as parameters") {
|
||||
mockEd25519.when { try $0.verifySignature(any(), publicKey: any(), data: any()) }.thenReturn(true)
|
||||
mockCrypto
|
||||
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
||||
.thenReturn(true)
|
||||
|
||||
_ = try? decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
||||
|
||||
expect(mockEd25519)
|
||||
expect(mockCrypto)
|
||||
.to(call(matchingParameters: true) {
|
||||
try $0.verifySignature(
|
||||
Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!,
|
||||
publicKey: Data(hex: TestConstants.publicKey),
|
||||
data: Data(base64Encoded: "VGVzdERhdGE=")!
|
||||
$0.verify(
|
||||
.signatureEd25519(
|
||||
Data(base64Encoded: "VGVzdFNpZ25hdHVyZQ==")!,
|
||||
publicKey: Data(hex: TestConstants.publicKey),
|
||||
data: Data(base64Encoded: "VGVzdERhdGE=")!
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
it("throws if it fails verification") {
|
||||
mockEd25519.when { try $0.verifySignature(any(), publicKey: any(), data: any()) }.thenReturn(false)
|
||||
mockCrypto
|
||||
.when { $0.verify(.signatureEd25519(any(), publicKey: any(), data: any())) }
|
||||
.thenReturn(false)
|
||||
|
||||
expect {
|
||||
try decoder.decode(OpenGroupAPI.Message.self, from: messageData)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,98 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class SodiumProtocolsSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
describe("an AeadXChaCha20Poly1305IetfType") {
|
||||
let testValue: [UInt8] = [1, 2, 3]
|
||||
|
||||
it("provides the default values in it's extensions") {
|
||||
let mockAead: MockAeadXChaCha20Poly1305Ietf = MockAeadXChaCha20Poly1305Ietf()
|
||||
mockAead
|
||||
.when {
|
||||
$0.encrypt(
|
||||
message: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray(),
|
||||
additionalData: anyArray()
|
||||
)
|
||||
}
|
||||
.thenReturn(testValue)
|
||||
mockAead
|
||||
.when {
|
||||
$0.decrypt(
|
||||
authenticatedCipherText: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray(),
|
||||
additionalData: anyArray()
|
||||
)
|
||||
}
|
||||
.thenReturn(testValue)
|
||||
|
||||
_ = mockAead.encrypt(message: [], secretKey: [], nonce: [])
|
||||
_ = mockAead.decrypt(authenticatedCipherText: [], secretKey: [], nonce: [])
|
||||
|
||||
expect(mockAead)
|
||||
.to(call {
|
||||
$0.encrypt(message: anyArray(), secretKey: anyArray(), nonce: anyArray(), additionalData: anyArray())
|
||||
})
|
||||
|
||||
expect(mockAead)
|
||||
.to(call {
|
||||
$0.decrypt(
|
||||
authenticatedCipherText: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray(),
|
||||
additionalData: anyArray()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
describe("a GenericHashType") {
|
||||
let testValue: [UInt8] = [1, 2, 3]
|
||||
|
||||
it("provides the default values in it's extensions") {
|
||||
let mockGenericHash: MockGenericHash = MockGenericHash()
|
||||
mockGenericHash
|
||||
.when { $0.hash(message: anyArray(), key: anyArray()) }
|
||||
.thenReturn(testValue)
|
||||
mockGenericHash
|
||||
.when {
|
||||
$0.hashSaltPersonal(
|
||||
message: anyArray(),
|
||||
outputLength: any(),
|
||||
key: anyArray(),
|
||||
salt: anyArray(),
|
||||
personal: anyArray()
|
||||
)
|
||||
}
|
||||
.thenReturn(testValue)
|
||||
|
||||
_ = mockGenericHash.hash(message: [])
|
||||
_ = mockGenericHash.hashSaltPersonal(message: [], outputLength: 0, salt: [], personal: [])
|
||||
|
||||
expect(mockGenericHash)
|
||||
.to(call { $0.hash(message: anyArray(), key: anyArray()) })
|
||||
expect(mockGenericHash)
|
||||
.to(call {
|
||||
$0.hashSaltPersonal(
|
||||
message: anyArray(),
|
||||
outputLength: any(),
|
||||
key: anyArray(),
|
||||
salt: anyArray(),
|
||||
personal: anyArray()
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,93 +15,110 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
|
||||
override func spec() {
|
||||
var mockStorage: Storage!
|
||||
var mockSodium: MockSodium!
|
||||
var mockBox: MockBox!
|
||||
var mockGenericHash: MockGenericHash!
|
||||
var mockSign: MockSign!
|
||||
var mockAeadXChaCha: MockAeadXChaCha20Poly1305Ietf!
|
||||
var mockNonce24Generator: MockNonce24Generator!
|
||||
var dependencies: SMKDependencies!
|
||||
var mockCrypto: MockCrypto!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
describe("a MessageReceiver") {
|
||||
beforeEach {
|
||||
mockStorage = Storage(
|
||||
mockStorage = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
SNMessagingKit.self
|
||||
]
|
||||
)
|
||||
mockSodium = MockSodium()
|
||||
mockBox = MockBox()
|
||||
mockGenericHash = MockGenericHash()
|
||||
mockSign = MockSign()
|
||||
mockAeadXChaCha = MockAeadXChaCha20Poly1305Ietf()
|
||||
mockNonce24Generator = MockNonce24Generator()
|
||||
|
||||
mockAeadXChaCha
|
||||
.when { $0.encrypt(message: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
||||
.thenReturn(nil)
|
||||
|
||||
dependencies = SMKDependencies(
|
||||
mockCrypto = MockCrypto()
|
||||
dependencies = Dependencies(
|
||||
storage: mockStorage,
|
||||
sodium: mockSodium,
|
||||
box: mockBox,
|
||||
genericHash: mockGenericHash,
|
||||
sign: mockSign,
|
||||
aeadXChaCha20Poly1305Ietf: mockAeadXChaCha,
|
||||
nonceGenerator24: mockNonce24Generator
|
||||
crypto: mockCrypto
|
||||
)
|
||||
|
||||
mockStorage.write { db in
|
||||
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
||||
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
|
||||
}
|
||||
mockBox
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
message: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
mockCrypto
|
||||
.when {
|
||||
$0.open(
|
||||
anonymousCipherText: anyArray(),
|
||||
recipientPublicKey: anyArray(),
|
||||
recipientSecretKey: anyArray()
|
||||
try $0.perform(
|
||||
.open(
|
||||
anonymousCipherText: anyArray(),
|
||||
recipientPublicKey: anyArray(),
|
||||
recipientSecretKey: anyArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn([UInt8](repeating: 0, count: 100))
|
||||
mockSodium
|
||||
.when { $0.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), genericHash: mockGenericHash) }
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: any(),
|
||||
edKeyPair: any(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(
|
||||
KeyPair(
|
||||
publicKey: Data(hex: TestConstants.blindedPublicKey).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
)
|
||||
)
|
||||
mockSodium
|
||||
.when {
|
||||
$0.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
genericHash: mockGenericHash
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn([])
|
||||
mockSodium
|
||||
.when { $0.generateBlindingFactor(serverPublicKey: any(), genericHash: mockGenericHash) }
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(.generateBlindingFactor(serverPublicKey: any(), using: dependencies))
|
||||
}
|
||||
.thenReturn([])
|
||||
mockSodium
|
||||
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
||||
mockCrypto
|
||||
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||
.thenReturn(Data(hex: TestConstants.blindedPublicKey).bytes)
|
||||
mockSign
|
||||
.when { $0.toX25519(ed25519PublicKey: anyArray()) }
|
||||
mockCrypto
|
||||
.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }
|
||||
.thenReturn(Data(hex: TestConstants.publicKey).bytes)
|
||||
mockSign
|
||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
||||
mockCrypto
|
||||
.when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) }
|
||||
.thenReturn(true)
|
||||
mockAeadXChaCha
|
||||
.when { $0.decrypt(authenticatedCipherText: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
try $0.perform(
|
||||
.decryptAeadXChaCha20(
|
||||
authenticatedCipherText: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn("TestMessage".data(using: .utf8)!.bytes + [UInt8](repeating: 0, count: 32))
|
||||
mockNonce24Generator
|
||||
.when { $0.nonce() }
|
||||
mockCrypto.when { $0.size(.nonce24) }.thenReturn(24)
|
||||
mockCrypto.when { $0.size(.publicKey) }.thenReturn(32)
|
||||
mockCrypto.when { $0.size(.signature) }.thenReturn(64)
|
||||
mockCrypto
|
||||
.when { try $0.perform(.generateNonce24()) }
|
||||
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
||||
}
|
||||
|
||||
|
@ -117,7 +134,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||
),
|
||||
dependencies: SMKDependencies()
|
||||
using: Dependencies()
|
||||
)
|
||||
|
||||
expect(String(data: (result?.plaintext ?? Data()), encoding: .utf8)).to(equal("TestMessage"))
|
||||
|
@ -126,12 +143,14 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot open the message") {
|
||||
mockBox
|
||||
mockCrypto
|
||||
.when {
|
||||
$0.open(
|
||||
anonymousCipherText: anyArray(),
|
||||
recipientPublicKey: anyArray(),
|
||||
recipientSecretKey: anyArray()
|
||||
try $0.perform(
|
||||
.open(
|
||||
anonymousCipherText: anyArray(),
|
||||
recipientPublicKey: anyArray(),
|
||||
recipientSecretKey: anyArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
@ -143,19 +162,21 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||
),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
}
|
||||
|
||||
it("throws an error if the open message is too short") {
|
||||
mockBox
|
||||
mockCrypto
|
||||
.when {
|
||||
$0.open(
|
||||
anonymousCipherText: anyArray(),
|
||||
recipientPublicKey: anyArray(),
|
||||
recipientSecretKey: anyArray()
|
||||
try $0.perform(
|
||||
.open(
|
||||
anonymousCipherText: anyArray(),
|
||||
recipientPublicKey: anyArray(),
|
||||
recipientSecretKey: anyArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn([1, 2, 3])
|
||||
|
@ -167,15 +188,15 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||
),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
}
|
||||
|
||||
it("throws an error if it cannot verify the message") {
|
||||
mockSign
|
||||
.when { $0.verify(message: anyArray(), publicKey: anyArray(), signature: anyArray()) }
|
||||
mockCrypto
|
||||
.when { $0.verify(.signature(message: anyArray(), publicKey: anyArray(), signature: anyArray())) }
|
||||
.thenReturn(false)
|
||||
|
||||
expect {
|
||||
|
@ -185,14 +206,14 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||
),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.to(throwError(MessageReceiverError.invalidSignature))
|
||||
}
|
||||
|
||||
it("throws an error if it cannot get the senders x25519 public key") {
|
||||
mockSign.when { $0.toX25519(ed25519PublicKey: anyArray()) }.thenReturn(nil)
|
||||
mockCrypto.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }.thenReturn(nil)
|
||||
|
||||
expect {
|
||||
try MessageReceiver.decryptWithSessionProtocol(
|
||||
|
@ -201,7 +222,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.privateKey)!.bytes
|
||||
),
|
||||
dependencies: dependencies
|
||||
using: dependencies
|
||||
)
|
||||
}
|
||||
.to(throwError(MessageReceiverError.decryptionFailed))
|
||||
|
@ -223,7 +244,7 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
publicKey: Data.data(fromHex: TestConstants.edPublicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
||||
),
|
||||
using: SMKDependencies()
|
||||
using: Dependencies()
|
||||
)
|
||||
|
||||
expect(String(data: (result?.plaintext ?? Data()), encoding: .utf8)).to(equal("TestMessage"))
|
||||
|
@ -271,8 +292,16 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot get the blinded keyPair") {
|
||||
mockSodium
|
||||
.when { $0.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), genericHash: mockGenericHash) }
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: any(),
|
||||
edKeyPair: any(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
||||
expect {
|
||||
|
@ -296,14 +325,16 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot get the decryption key") {
|
||||
mockSodium
|
||||
.when {
|
||||
$0.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
genericHash: mockGenericHash
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
@ -350,8 +381,16 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot decrypt the data") {
|
||||
mockAeadXChaCha
|
||||
.when { $0.decrypt(authenticatedCipherText: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
try $0.perform(
|
||||
.decryptAeadXChaCha20(
|
||||
authenticatedCipherText: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
||||
expect {
|
||||
|
@ -375,8 +414,16 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if the inner bytes are too short") {
|
||||
mockAeadXChaCha
|
||||
.when { $0.decrypt(authenticatedCipherText: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
try $0.perform(
|
||||
.decryptAeadXChaCha20(
|
||||
authenticatedCipherText: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn([1, 2, 3])
|
||||
|
||||
expect {
|
||||
|
@ -400,8 +447,10 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot generate the blinding factor") {
|
||||
mockSodium
|
||||
.when { $0.generateBlindingFactor(serverPublicKey: any(), genericHash: mockGenericHash) }
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(.generateBlindingFactor(serverPublicKey: any(), using: dependencies))
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
||||
expect {
|
||||
|
@ -425,8 +474,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot generate the combined key") {
|
||||
mockSodium
|
||||
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
||||
mockCrypto
|
||||
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||
.thenReturn(nil)
|
||||
|
||||
expect {
|
||||
|
@ -450,8 +499,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if the combined key does not match kA") {
|
||||
mockSodium
|
||||
.when { $0.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray()) }
|
||||
mockCrypto
|
||||
.when { try $0.perform(.combineKeys(lhsKeyBytes: anyArray(), rhsKeyBytes: anyArray())) }
|
||||
.thenReturn(Data(hex: TestConstants.publicKey).bytes)
|
||||
|
||||
expect {
|
||||
|
@ -475,8 +524,8 @@ class MessageReceiverDecryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
it("throws an error if it cannot get the senders x25519 public key") {
|
||||
mockSign
|
||||
.when { $0.toX25519(ed25519PublicKey: anyArray()) }
|
||||
mockCrypto
|
||||
.when { try $0.perform(.toX25519(ed25519PublicKey: anyArray())) }
|
||||
.thenReturn(nil)
|
||||
|
||||
expect {
|
||||
|
|
|
@ -15,53 +15,55 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
|
||||
override func spec() {
|
||||
var mockStorage: Storage!
|
||||
var mockBox: MockBox!
|
||||
var mockSign: MockSign!
|
||||
var mockNonce24Generator: MockNonce24Generator!
|
||||
var dependencies: SMKDependencies!
|
||||
var mockCrypto: MockCrypto!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
describe("a MessageSender") {
|
||||
// MARK: - Configuration
|
||||
|
||||
beforeEach {
|
||||
mockStorage = Storage(
|
||||
mockStorage = SynchronousStorage(
|
||||
customWriter: try! DatabaseQueue(),
|
||||
customMigrationTargets: [
|
||||
SNUtilitiesKit.self,
|
||||
SNMessagingKit.self
|
||||
]
|
||||
)
|
||||
mockBox = MockBox()
|
||||
mockSign = MockSign()
|
||||
mockNonce24Generator = MockNonce24Generator()
|
||||
mockCrypto = MockCrypto()
|
||||
|
||||
dependencies = SMKDependencies(
|
||||
dependencies = Dependencies(
|
||||
storage: mockStorage,
|
||||
box: mockBox,
|
||||
sign: mockSign,
|
||||
nonceGenerator24: mockNonce24Generator
|
||||
crypto: mockCrypto
|
||||
)
|
||||
|
||||
mockStorage.write { db in
|
||||
try Identity(variant: .ed25519PublicKey, data: Data(hex: TestConstants.edPublicKey)).insert(db)
|
||||
try Identity(variant: .ed25519SecretKey, data: Data(hex: TestConstants.edSecretKey)).insert(db)
|
||||
}
|
||||
mockNonce24Generator
|
||||
.when { $0.nonce() }
|
||||
mockCrypto
|
||||
.when { try $0.perform(.generateNonce24()) }
|
||||
.thenReturn(Data(base64Encoded: "pbTUizreT0sqJ2R2LloseQDyVL2RYztD")!.bytes)
|
||||
}
|
||||
|
||||
// MARK: - when encrypting with the session protocol
|
||||
context("when encrypting with the session protocol") {
|
||||
beforeEach {
|
||||
mockBox.when { $0.seal(message: anyArray(), recipientPublicKey: anyArray()) }.thenReturn([1, 2, 3])
|
||||
mockSign.when { $0.signature(message: anyArray(), secretKey: anyArray()) }.thenReturn([])
|
||||
mockCrypto
|
||||
.when { try $0.perform(.seal(message: anyArray(), recipientPublicKey: anyArray())) }
|
||||
.thenReturn([1, 2, 3])
|
||||
mockCrypto
|
||||
.when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) }
|
||||
.thenReturn([])
|
||||
}
|
||||
|
||||
// MARK: -- can encrypt correctly
|
||||
it("can encrypt correctly") {
|
||||
let result = mockStorage.write { db in
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
plaintext: "TestMessage".data(using: .utf8)!,
|
||||
for: "05\(TestConstants.publicKey)",
|
||||
using: SMKDependencies(storage: mockStorage)
|
||||
using: Dependencies() // Don't mock
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -70,8 +72,9 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
expect(result?.count).to(equal(155))
|
||||
}
|
||||
|
||||
// MARK: -- returns the correct value when mocked
|
||||
it("returns the correct value when mocked") {
|
||||
let result = mockStorage.write { db in
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
plaintext: "TestMessage".data(using: .utf8)!,
|
||||
|
@ -83,13 +86,14 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
expect(result?.bytes).to(equal([1, 2, 3]))
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if there is no ed25519 keyPair
|
||||
it("throws an error if there is no ed25519 keyPair") {
|
||||
mockStorage.write { db in
|
||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||
_ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db)
|
||||
}
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
|
@ -102,10 +106,13 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if the signature generation fails
|
||||
it("throws an error if the signature generation fails") {
|
||||
mockSign.when { $0.signature(message: anyArray(), secretKey: anyArray()) }.thenReturn(nil)
|
||||
mockCrypto
|
||||
.when { try $0.perform(.signature(message: anyArray(), secretKey: anyArray())) }
|
||||
.thenReturn(nil)
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
|
@ -118,10 +125,13 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if the encryption fails
|
||||
it("throws an error if the encryption fails") {
|
||||
mockBox.when { $0.seal(message: anyArray(), recipientPublicKey: anyArray()) }.thenReturn(nil)
|
||||
mockCrypto
|
||||
.when { try $0.perform(.seal(message: anyArray(), recipientPublicKey: anyArray())) }
|
||||
.thenReturn(nil)
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionProtocol(
|
||||
db,
|
||||
|
@ -135,9 +145,67 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - when encrypting with the blinded session protocol
|
||||
context("when encrypting with the blinded session protocol") {
|
||||
it("successfully encrypts") {
|
||||
let result = mockStorage.write { db in
|
||||
beforeEach {
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
crypto.generate(.blindedKeyPair(serverPublicKey: any(), edKeyPair: any(), using: dependencies))
|
||||
}
|
||||
.thenReturn(
|
||||
KeyPair(
|
||||
publicKey: Data.data(fromHex: TestConstants.publicKey)!.bytes,
|
||||
secretKey: Data.data(fromHex: TestConstants.edSecretKey)!.bytes
|
||||
)
|
||||
)
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn([1, 2, 3])
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
message: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray(),
|
||||
additionalData: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn([2, 3, 4])
|
||||
}
|
||||
|
||||
// MARK: -- can encrypt correctly
|
||||
it("can encrypt correctly") {
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
plaintext: "TestMessage".data(using: .utf8)!,
|
||||
for: "15\(TestConstants.blindedPublicKey)",
|
||||
openGroupPublicKey: TestConstants.serverPublicKey,
|
||||
using: Dependencies() // Don't mock
|
||||
)
|
||||
}
|
||||
|
||||
// Note: A Nonce is used for this so we can't compare the exact value when not mocked
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(84))
|
||||
}
|
||||
|
||||
// MARK: -- returns the correct value when mocked
|
||||
it("returns the correct value when mocked") {
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
plaintext: "TestMessage".data(using: .utf8)!,
|
||||
|
@ -148,15 +216,12 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal(
|
||||
"00db16b6687382811d69875a5376f66acad9c49fe5e26bcf770c7e6e9c230299" +
|
||||
"f61b315299dd1fa700dd7f34305c0465af9e64dc791d7f4123f1eeafa5b4d48b" +
|
||||
"3ade4f4b2a2764762e5a2c7900f254bd91633b43"
|
||||
))
|
||||
.to(equal("00020304a5b4d48b3ade4f4b2a2764762e5a2c7900f254bd91633b43"))
|
||||
}
|
||||
|
||||
// MARK: -- includes a version at the start of the encrypted value
|
||||
it("includes a version at the start of the encrypted value") {
|
||||
let result = mockStorage.write { db in
|
||||
let result: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
plaintext: "TestMessage".data(using: .utf8)!,
|
||||
|
@ -169,8 +234,9 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
expect(result?.toHexString().prefix(2)).to(equal("00"))
|
||||
}
|
||||
|
||||
// MARK: -- includes the nonce at the end of the encrypted value
|
||||
it("includes the nonce at the end of the encrypted value") {
|
||||
let maybeResult = mockStorage.write { db in
|
||||
let maybeResult: Data? = mockStorage.read { db in
|
||||
try? MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
plaintext: "TestMessage".data(using: .utf8)!,
|
||||
|
@ -186,8 +252,9 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
.to(equal("pbTUizreT0sqJ2R2LloseQDyVL2RYztD"))
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if the recipient isn't a blinded id
|
||||
it("throws an error if the recipient isn't a blinded id") {
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
|
@ -201,13 +268,14 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if there is no ed25519 keyPair
|
||||
it("throws an error if there is no ed25519 keyPair") {
|
||||
mockStorage.write { db in
|
||||
_ = try Identity.filter(id: .ed25519PublicKey).deleteAll(db)
|
||||
_ = try Identity.filter(id: .ed25519SecretKey).deleteAll(db)
|
||||
}
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
|
@ -221,22 +289,21 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if it fails to generate a blinded keyPair
|
||||
it("throws an error if it fails to generate a blinded keyPair") {
|
||||
let mockSodium: MockSodium = MockSodium()
|
||||
let mockGenericHash: MockGenericHash = MockGenericHash()
|
||||
dependencies = dependencies.with(sodium: mockSodium, genericHash: mockGenericHash)
|
||||
|
||||
mockSodium
|
||||
.when {
|
||||
$0.blindedKeyPair(
|
||||
serverPublicKey: any(),
|
||||
edKeyPair: any(),
|
||||
genericHash: mockGenericHash
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: any(),
|
||||
edKeyPair: any(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
|
@ -250,38 +317,23 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if it fails to generate an encryption key
|
||||
it("throws an error if it fails to generate an encryption key") {
|
||||
let mockSodium: MockSodium = MockSodium()
|
||||
let mockGenericHash: MockGenericHash = MockGenericHash()
|
||||
dependencies = dependencies.with(sodium: mockSodium, genericHash: mockGenericHash)
|
||||
|
||||
mockSodium
|
||||
.when {
|
||||
$0.blindedKeyPair(
|
||||
serverPublicKey: any(),
|
||||
edKeyPair: any(),
|
||||
genericHash: mockGenericHash
|
||||
)
|
||||
}
|
||||
.thenReturn(
|
||||
KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
)
|
||||
)
|
||||
mockSodium
|
||||
.when {
|
||||
$0.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
genericHash: mockGenericHash
|
||||
mockCrypto
|
||||
.when { [dependencies = dependencies!] crypto in
|
||||
try crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: anyArray(),
|
||||
otherBlindedPublicKey: anyArray(),
|
||||
fromBlindedPublicKey: anyArray(),
|
||||
toBlindedPublicKey: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
|
@ -295,15 +347,23 @@ class MessageSenderEncryptionSpec: QuickSpec {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: -- throws an error if it fails to encrypt
|
||||
it("throws an error if it fails to encrypt") {
|
||||
let mockAeadXChaCha: MockAeadXChaCha20Poly1305Ietf = MockAeadXChaCha20Poly1305Ietf()
|
||||
dependencies = dependencies.with(aeadXChaCha20Poly1305Ietf: mockAeadXChaCha)
|
||||
|
||||
mockAeadXChaCha
|
||||
.when { $0.encrypt(message: anyArray(), secretKey: anyArray(), nonce: anyArray()) }
|
||||
mockCrypto
|
||||
.when {
|
||||
try $0.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
message: anyArray(),
|
||||
secretKey: anyArray(),
|
||||
nonce: anyArray(),
|
||||
additionalData: anyArray(),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
}
|
||||
.thenReturn(nil)
|
||||
|
||||
mockStorage.write { db in
|
||||
mockStorage.read { db in
|
||||
expect {
|
||||
try MessageSender.encryptWithSessionBlindingProtocol(
|
||||
db,
|
||||
|
|
|
@ -0,0 +1,402 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import SessionUtilitiesKit
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class CryptoSMKSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
var crypto: Crypto!
|
||||
var mockCrypto: MockCrypto!
|
||||
var dependencies: Dependencies!
|
||||
|
||||
beforeEach {
|
||||
crypto = Crypto()
|
||||
mockCrypto = MockCrypto()
|
||||
dependencies = Dependencies(crypto: crypto)
|
||||
}
|
||||
|
||||
describe("Crypto for SessionMessagingKit") {
|
||||
|
||||
// MARK: - when extending Sign
|
||||
context("when extending Sign") {
|
||||
// MARK: -- can convert an ed25519 public key into an x25519 public key
|
||||
it("can convert an ed25519 public key into an x25519 public key") {
|
||||
let result = try? crypto.perform(.toX25519(ed25519PublicKey: TestConstants.edPublicKey.bytes))
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("95ffb559d4e804e9b414a5178454c426f616b4a61089b217b41165dbb7c9fe2d"))
|
||||
}
|
||||
|
||||
// MARK: -- can convert an ed25519 private key into an x25519 private key
|
||||
it("can convert an ed25519 private key into an x25519 private key") {
|
||||
let result = try? crypto.perform(.toX25519(ed25519SecretKey: TestConstants.edSecretKey.bytes))
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("c83f9a1479b103c275d2db2d6c199fdc6f589b29b742f6405e01cc5a9a1d135d"))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - when extending Sodium
|
||||
context("when extending Sodium") {
|
||||
// MARK: -- and generating a blinding factor
|
||||
context("and generating a blinding factor") {
|
||||
// MARK: --- successfully generates a blinding factor
|
||||
it("successfully generates a blinding factor") {
|
||||
let result = try? crypto.perform(
|
||||
.generateBlindingFactor(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("84e3eb75028a9b73fec031b7448e322a68ca6485fad81ab1bead56f759ebeb0f"))
|
||||
}
|
||||
|
||||
// MARK: --- fails if the serverPublicKey is not a hex string
|
||||
it("fails if the serverPublicKey is not a hex string") {
|
||||
let result = try? crypto.perform(
|
||||
.generateBlindingFactor(
|
||||
serverPublicKey: "Test",
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: --- fails if it cannot hash the serverPublicKey bytes
|
||||
it("fails if it cannot hash the serverPublicKey bytes") {
|
||||
dependencies = Dependencies(crypto: mockCrypto)
|
||||
mockCrypto
|
||||
.when { try $0.perform(.hash(message: anyArray(), outputLength: any())) }
|
||||
.thenReturn(nil)
|
||||
|
||||
let result = try? crypto.perform(
|
||||
.generateBlindingFactor(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- and generating a blinded key pair
|
||||
context("and generating a blinded key pair") {
|
||||
// MARK: --- successfully generates a blinded key pair
|
||||
it("successfully generates a blinded key pair") {
|
||||
let result = crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
// Note: The first 64 characters of the secretKey are consistent but the chars after that always differ
|
||||
expect(result?.publicKey.toHexString()).to(equal(TestConstants.blindedPublicKey))
|
||||
expect(String(result?.secretKey.toHexString().prefix(64) ?? ""))
|
||||
.to(equal("16663322d6b684e1c9dcc02b9e8642c3affd3bc431a9ea9e63dbbac88ce7a305"))
|
||||
}
|
||||
|
||||
// MARK: --- fails if the edKeyPair public key length wrong
|
||||
it("fails if the edKeyPair public key length wrong") {
|
||||
let result = crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: String(TestConstants.edPublicKey.prefix(4))).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: --- fails if the edKeyPair secret key length wrong
|
||||
it("fails if the edKeyPair secret key length wrong") {
|
||||
let result = crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: String(TestConstants.edSecretKey.prefix(4))).bytes
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
// MARK: --- fails if it cannot generate a blinding factor
|
||||
it("fails if it cannot generate a blinding factor") {
|
||||
let result = crypto.generate(
|
||||
.blindedKeyPair(
|
||||
serverPublicKey: "Test",
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
),
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- and generating a sogsSignature
|
||||
context("and generating a sogsSignature") {
|
||||
// MARK: --- generates a correct signature
|
||||
it("generates a correct signature") {
|
||||
let result = try? crypto.perform(
|
||||
.sogsSignature(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
blindedSecretKey: Data(hex: "44d82cc15c0a5056825cae7520b6b52d000a23eb0c5ed94c4be2d9dc41d2d409").bytes,
|
||||
blindedPublicKey: Data(hex: "0bb7815abb6ba5142865895f3e5286c0527ba4d31dbb75c53ce95e91ffe025a2").bytes
|
||||
)
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal(
|
||||
"dcc086abdd2a740d9260b008fb37e12aa0ff47bd2bd9e177bbbec37fd46705a9" +
|
||||
"072ce747bda66c788c3775cdd7ad60ad15a478e0886779aad5d795fd7bf8350d"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- and combining keys
|
||||
context("and combining keys") {
|
||||
// MARK: --- generates a correct combined key
|
||||
it("generates a correct combined key") {
|
||||
let result = try? crypto.perform(
|
||||
.combineKeys(
|
||||
lhsKeyBytes: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
rhsKeyBytes: Data(hex: TestConstants.edPublicKey).bytes
|
||||
)
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("1159b5d0fcfba21228eb2121a0f59712fa8276fc6e5547ff519685a40b9819e6"))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- and creating a shared blinded encryption key
|
||||
context("and creating a shared blinded encryption key") {
|
||||
// MARK: --- generates a correct combined key
|
||||
it("generates a correct combined key") {
|
||||
let result = try? crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
otherBlindedPublicKey: Data(hex: TestConstants.blindedPublicKey).bytes,
|
||||
fromBlindedPublicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
toBlindedPublicKey: Data(hex: TestConstants.blindedPublicKey).bytes,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("388ee09e4c356b91f1cce5cc0aa0cf59e8e8cade69af61685d09c2d2731bc99e"))
|
||||
}
|
||||
|
||||
// MARK: --- fails if the scalar multiplication fails
|
||||
it("fails if the scalar multiplication fails") {
|
||||
let result = try? crypto.perform(
|
||||
.sharedBlindedEncryptionKey(
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
otherBlindedPublicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
fromBlindedPublicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
toBlindedPublicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result?.toHexString()).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -- and checking if a session id matches a blinded id
|
||||
context("and checking if a session id matches a blinded id") {
|
||||
// MARK: --- returns true when they match
|
||||
it("returns true when they match") {
|
||||
let result = crypto.verify(
|
||||
.sessionId(
|
||||
"05\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "15\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beTrue())
|
||||
}
|
||||
|
||||
// MARK: --- returns false if given an invalid session id
|
||||
it("returns false if given an invalid session id") {
|
||||
let result = crypto.verify(
|
||||
.sessionId(
|
||||
"AB\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "15\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: --- returns false if given an invalid blinded id
|
||||
it("returns false if given an invalid blinded id") {
|
||||
let result = crypto.verify(
|
||||
.sessionId(
|
||||
"05\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "AB\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beFalse())
|
||||
}
|
||||
|
||||
// MARK: --- returns false if it fails to generate the blinding factor
|
||||
it("returns false if it fails to generate the blinding factor") {
|
||||
let result = crypto.verify(
|
||||
.sessionId(
|
||||
"05\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "15\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: "Test",
|
||||
using: dependencies
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beFalse())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - when extending GenericHash
|
||||
describe("when extending GenericHash") {
|
||||
// MARK: -- and generating a hash with salt and personal values
|
||||
context("and generating a hash with salt and personal values") {
|
||||
// MARK: --- generates a hash correctly
|
||||
it("generates a hash correctly") {
|
||||
let result = try? crypto.perform(
|
||||
.hashSaltPersonal(
|
||||
message: "TestMessage".bytes,
|
||||
outputLength: 32,
|
||||
key: "Key".bytes,
|
||||
salt: "Salt".bytes,
|
||||
personal: "Personal".bytes
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(32))
|
||||
}
|
||||
|
||||
// MARK: --- generates a hash correctly with no key
|
||||
it("generates a hash correctly with no key") {
|
||||
let result = try? crypto.perform(
|
||||
.hashSaltPersonal(
|
||||
message: "TestMessage".bytes,
|
||||
outputLength: 32,
|
||||
key: nil,
|
||||
salt: "Salt".bytes,
|
||||
personal: "Personal".bytes
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(32))
|
||||
}
|
||||
|
||||
// MARK: --- fails if given invalid options
|
||||
it("fails if given invalid options") {
|
||||
let result = try? crypto.perform(
|
||||
.hashSaltPersonal(
|
||||
message: "TestMessage".bytes,
|
||||
outputLength: 65, // Max of 64
|
||||
key: "Key".bytes,
|
||||
salt: "Salt".bytes,
|
||||
personal: "Personal".bytes
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - when extending AeadXChaCha20Poly1305Ietf
|
||||
context("when extending AeadXChaCha20Poly1305Ietf") {
|
||||
// MARK: -- when encrypting
|
||||
context("when encrypting") {
|
||||
// MARK: --- encrypts correctly
|
||||
it("encrypts correctly") {
|
||||
let result = try? crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
nonce: "TestNonce".bytes,
|
||||
additionalData: nil,
|
||||
using: Dependencies()
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(27))
|
||||
}
|
||||
|
||||
// MARK: --- encrypts correctly with additional data
|
||||
it("encrypts correctly with additional data") {
|
||||
let result = try? crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
nonce: "TestNonce".bytes,
|
||||
additionalData: "TestData".bytes,
|
||||
using: Dependencies()
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(27))
|
||||
}
|
||||
|
||||
// MARK: --- fails if given an invalid key
|
||||
it("fails if given an invalid key") {
|
||||
let result = try? crypto.perform(
|
||||
.encryptAeadXChaCha20(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: "TestKey".bytes,
|
||||
nonce: "TestNonce".bytes,
|
||||
additionalData: "TestData".bytes,
|
||||
using: Dependencies()
|
||||
)
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,352 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import SessionUtilitiesKit
|
||||
|
||||
import Quick
|
||||
import Nimble
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class SodiumUtilitiesSpec: QuickSpec {
|
||||
// MARK: - Spec
|
||||
|
||||
override func spec() {
|
||||
// MARK: - Sign
|
||||
|
||||
describe("an extended Sign") {
|
||||
var sign: Sign!
|
||||
|
||||
beforeEach {
|
||||
sign = Sodium().sign
|
||||
}
|
||||
|
||||
it("can convert an ed25519 public key into an x25519 public key") {
|
||||
let result = sign.toX25519(ed25519PublicKey: TestConstants.edPublicKey.bytes)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("95ffb559d4e804e9b414a5178454c426f616b4a61089b217b41165dbb7c9fe2d"))
|
||||
}
|
||||
|
||||
it("can convert an ed25519 private key into an x25519 private key") {
|
||||
let result = sign.toX25519(ed25519SecretKey: TestConstants.edSecretKey.bytes)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("c83f9a1479b103c275d2db2d6c199fdc6f589b29b742f6405e01cc5a9a1d135d"))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Sodium
|
||||
|
||||
describe("an extended Sodium") {
|
||||
var sodium: Sodium!
|
||||
var genericHash: GenericHashType!
|
||||
|
||||
beforeEach {
|
||||
sodium = Sodium()
|
||||
genericHash = sodium.genericHash
|
||||
}
|
||||
|
||||
context("when generating a blinding factor") {
|
||||
it("successfully generates a blinding factor") {
|
||||
let result = sodium.generateBlindingFactor(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("84e3eb75028a9b73fec031b7448e322a68ca6485fad81ab1bead56f759ebeb0f"))
|
||||
}
|
||||
|
||||
it("fails if the serverPublicKey is not a hex string") {
|
||||
let result = sodium.generateBlindingFactor(
|
||||
serverPublicKey: "Test",
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
it("fails if it cannot hash the serverPublicKey bytes") {
|
||||
genericHash = MockGenericHash()
|
||||
(genericHash as? MockGenericHash)?
|
||||
.when { $0.hash(message: anyArray(), outputLength: any()) }
|
||||
.thenReturn(nil)
|
||||
|
||||
let result = sodium.generateBlindingFactor(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("when generating a blinded key pair") {
|
||||
it("successfully generates a blinded key pair") {
|
||||
let result = sodium.blindedKeyPair(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
),
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
// Note: The first 64 characters of the secretKey are consistent but the chars after that always differ
|
||||
expect(result?.publicKey.toHexString()).to(equal(TestConstants.blindedPublicKey))
|
||||
expect(String(result?.secretKey.toHexString().prefix(64) ?? ""))
|
||||
.to(equal("16663322d6b684e1c9dcc02b9e8642c3affd3bc431a9ea9e63dbbac88ce7a305"))
|
||||
}
|
||||
|
||||
it("fails if the edKeyPair public key length wrong") {
|
||||
let result = sodium.blindedKeyPair(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: String(TestConstants.edPublicKey.prefix(4))).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
),
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
it("fails if the edKeyPair secret key length wrong") {
|
||||
let result = sodium.blindedKeyPair(
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: String(TestConstants.edSecretKey.prefix(4))).bytes
|
||||
),
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
|
||||
it("fails if it cannot generate a blinding factor") {
|
||||
let result = sodium.blindedKeyPair(
|
||||
serverPublicKey: "Test",
|
||||
edKeyPair: KeyPair(
|
||||
publicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes
|
||||
),
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("when generating a sogsSignature") {
|
||||
it("generates a correct signature") {
|
||||
let result = sodium.sogsSignature(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
blindedSecretKey: Data(hex: "44d82cc15c0a5056825cae7520b6b52d000a23eb0c5ed94c4be2d9dc41d2d409").bytes,
|
||||
blindedPublicKey: Data(hex: "0bb7815abb6ba5142865895f3e5286c0527ba4d31dbb75c53ce95e91ffe025a2").bytes
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal(
|
||||
"dcc086abdd2a740d9260b008fb37e12aa0ff47bd2bd9e177bbbec37fd46705a9" +
|
||||
"072ce747bda66c788c3775cdd7ad60ad15a478e0886779aad5d795fd7bf8350d"
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
context("when combining keys") {
|
||||
it("generates a correct combined key") {
|
||||
let result = sodium.combineKeys(
|
||||
lhsKeyBytes: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
rhsKeyBytes: Data(hex: TestConstants.edPublicKey).bytes
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("1159b5d0fcfba21228eb2121a0f59712fa8276fc6e5547ff519685a40b9819e6"))
|
||||
}
|
||||
|
||||
it("fails if the scalar multiplication fails") {
|
||||
let result = sodium.combineKeys(
|
||||
lhsKeyBytes: sodium.generatePrivateKeyScalar(secretKey: Data(hex: TestConstants.edSecretKey).bytes),
|
||||
rhsKeyBytes: Data(hex: TestConstants.publicKey).bytes
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("when creating a shared blinded encryption key") {
|
||||
it("generates a correct combined key") {
|
||||
let result = sodium.sharedBlindedEncryptionKey(
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
otherBlindedPublicKey: Data(hex: TestConstants.blindedPublicKey).bytes,
|
||||
fromBlindedPublicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
toBlindedPublicKey: Data(hex: TestConstants.blindedPublicKey).bytes,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result?.toHexString())
|
||||
.to(equal("388ee09e4c356b91f1cce5cc0aa0cf59e8e8cade69af61685d09c2d2731bc99e"))
|
||||
}
|
||||
|
||||
it("fails if the scalar multiplication fails") {
|
||||
let result = sodium.sharedBlindedEncryptionKey(
|
||||
secretKey: Data(hex: TestConstants.edSecretKey).bytes,
|
||||
otherBlindedPublicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
fromBlindedPublicKey: Data(hex: TestConstants.edPublicKey).bytes,
|
||||
toBlindedPublicKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result?.toHexString()).to(beNil())
|
||||
}
|
||||
}
|
||||
|
||||
context("when checking if a session id matches a blinded id") {
|
||||
it("returns true when they match") {
|
||||
let result = sodium.sessionId(
|
||||
"05\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "15\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beTrue())
|
||||
}
|
||||
|
||||
it("returns false if given an invalid session id") {
|
||||
let result = sodium.sessionId(
|
||||
"AB\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "15\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beFalse())
|
||||
}
|
||||
|
||||
it("returns false if given an invalid blinded id") {
|
||||
let result = sodium.sessionId(
|
||||
"05\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "AB\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: TestConstants.serverPublicKey,
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beFalse())
|
||||
}
|
||||
|
||||
it("returns false if it fails to generate the blinding factor") {
|
||||
let result = sodium.sessionId(
|
||||
"05\(TestConstants.publicKey)",
|
||||
matchesBlindedId: "15\(TestConstants.blindedPublicKey)",
|
||||
serverPublicKey: "Test",
|
||||
genericHash: genericHash
|
||||
)
|
||||
|
||||
expect(result).to(beFalse())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - GenericHash
|
||||
|
||||
describe("an extended GenericHash") {
|
||||
var genericHash: GenericHashType!
|
||||
|
||||
beforeEach {
|
||||
genericHash = Sodium().genericHash
|
||||
}
|
||||
|
||||
context("when generating a hash with salt and personal values") {
|
||||
it("generates a hash correctly") {
|
||||
let result = genericHash.hashSaltPersonal(
|
||||
message: "TestMessage".bytes,
|
||||
outputLength: 32,
|
||||
key: "Key".bytes,
|
||||
salt: "Salt".bytes,
|
||||
personal: "Personal".bytes
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(32))
|
||||
}
|
||||
|
||||
it("generates a hash correctly with no key") {
|
||||
let result = genericHash.hashSaltPersonal(
|
||||
message: "TestMessage".bytes,
|
||||
outputLength: 32,
|
||||
key: nil,
|
||||
salt: "Salt".bytes,
|
||||
personal: "Personal".bytes
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(32))
|
||||
}
|
||||
|
||||
it("fails if given invalid options") {
|
||||
let result = genericHash.hashSaltPersonal(
|
||||
message: "TestMessage".bytes,
|
||||
outputLength: 65, // Max of 64
|
||||
key: "Key".bytes,
|
||||
salt: "Salt".bytes,
|
||||
personal: "Personal".bytes
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - AeadXChaCha20Poly1305IetfType
|
||||
|
||||
describe("an extended AeadXChaCha20Poly1305IetfType") {
|
||||
var aeadXchacha20poly1305ietf: AeadXChaCha20Poly1305IetfType!
|
||||
|
||||
beforeEach {
|
||||
aeadXchacha20poly1305ietf = Sodium().aead.xchacha20poly1305ietf
|
||||
}
|
||||
|
||||
context("when encrypting") {
|
||||
it("encrypts correctly") {
|
||||
let result = aeadXchacha20poly1305ietf.encrypt(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
nonce: "TestNonce".bytes,
|
||||
additionalData: nil
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(27))
|
||||
}
|
||||
|
||||
it("encrypts correctly with additional data") {
|
||||
let result = aeadXchacha20poly1305ietf.encrypt(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: Data(hex: TestConstants.publicKey).bytes,
|
||||
nonce: "TestNonce".bytes,
|
||||
additionalData: "TestData".bytes
|
||||
)
|
||||
|
||||
expect(result).toNot(beNil())
|
||||
expect(result?.count).to(equal(27))
|
||||
}
|
||||
|
||||
it("fails if given an invalid key") {
|
||||
let result = aeadXchacha20poly1305ietf.encrypt(
|
||||
message: "TestMessage".bytes,
|
||||
secretKey: "TestKey".bytes,
|
||||
nonce: "TestNonce".bytes,
|
||||
additionalData: "TestData".bytes
|
||||
)
|
||||
|
||||
expect(result).to(beNil())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import GRDB
|
||||
import SessionSnodeKit
|
||||
import SessionUtilitiesKit
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
extension SMKDependencies {
|
||||
public func with(
|
||||
onionApi: OnionRequestAPIType.Type? = nil,
|
||||
generalCache: MutableGeneralCacheType? = nil,
|
||||
storage: Storage? = nil,
|
||||
scheduler: ValueObservationScheduler? = nil,
|
||||
sodium: SodiumType? = nil,
|
||||
box: BoxType? = nil,
|
||||
genericHash: GenericHashType? = nil,
|
||||
sign: SignType? = nil,
|
||||
aeadXChaCha20Poly1305Ietf: AeadXChaCha20Poly1305IetfType? = nil,
|
||||
ed25519: Ed25519Type? = nil,
|
||||
nonceGenerator16: NonceGenerator16ByteType? = nil,
|
||||
nonceGenerator24: NonceGenerator24ByteType? = nil,
|
||||
standardUserDefaults: UserDefaultsType? = nil,
|
||||
date: Date? = nil
|
||||
) -> SMKDependencies {
|
||||
return SMKDependencies(
|
||||
onionApi: (onionApi ?? self._onionApi.wrappedValue),
|
||||
generalCache: (generalCache ?? self._mutableGeneralCache.wrappedValue),
|
||||
storage: (storage ?? self._storage.wrappedValue),
|
||||
scheduler: (scheduler ?? self._scheduler.wrappedValue),
|
||||
sodium: (sodium ?? self._sodium.wrappedValue),
|
||||
box: (box ?? self._box.wrappedValue),
|
||||
genericHash: (genericHash ?? self._genericHash.wrappedValue),
|
||||
sign: (sign ?? self._sign.wrappedValue),
|
||||
aeadXChaCha20Poly1305Ietf: (aeadXChaCha20Poly1305Ietf ?? self._aeadXChaCha20Poly1305Ietf.wrappedValue),
|
||||
ed25519: (ed25519 ?? self._ed25519.wrappedValue),
|
||||
nonceGenerator16: (nonceGenerator16 ?? self._nonceGenerator16.wrappedValue),
|
||||
nonceGenerator24: (nonceGenerator24 ?? self._nonceGenerator24.wrappedValue),
|
||||
standardUserDefaults: (standardUserDefaults ?? self._standardUserDefaults.wrappedValue),
|
||||
date: (date ?? self._date.wrappedValue)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,20 +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
|
||||
var NonceBytes: Int = 24
|
||||
|
||||
func encrypt(message: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes? {
|
||||
return accept(args: [message, secretKey, nonce, additionalData]) as? Bytes
|
||||
}
|
||||
|
||||
func decrypt(authenticatedCipherText: Bytes, secretKey: Bytes, nonce: Bytes, additionalData: Bytes?) -> Bytes? {
|
||||
return accept(args: [authenticatedCipherText, secretKey, nonce, additionalData]) as? Bytes
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockBox: Mock<BoxType>, BoxType {
|
||||
func seal(message: Bytes, recipientPublicKey: Bytes) -> Bytes? {
|
||||
return accept(args: [message, recipientPublicKey]) as? Bytes
|
||||
}
|
||||
|
||||
func open(anonymousCipherText: Bytes, recipientPublicKey: Bytes, recipientSecretKey: Bytes) -> Bytes? {
|
||||
return accept(args: [anonymousCipherText, recipientPublicKey, recipientSecretKey]) as? Bytes
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
import SessionUtilitiesKit
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockEd25519: Mock<Ed25519Type>, Ed25519Type {
|
||||
func sign(data: Bytes, keyPair: KeyPair) throws -> Bytes? {
|
||||
return accept(args: [data, keyPair]) as? Bytes
|
||||
}
|
||||
|
||||
func verifySignature(_ signature: Data, publicKey: Data, data: Data) throws -> Bool {
|
||||
return accept(args: [signature, publicKey, data]) as! Bool
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockGenericHash: Mock<GenericHashType>, GenericHashType {
|
||||
func hash(message: Bytes, key: Bytes?) -> Bytes? {
|
||||
return accept(args: [message, key]) as? Bytes
|
||||
}
|
||||
|
||||
func hash(message: Bytes, outputLength: Int) -> Bytes? {
|
||||
return accept(args: [message, outputLength]) as? Bytes
|
||||
}
|
||||
|
||||
func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes? {
|
||||
return accept(args: [message, outputLength, key, salt, personal]) as? Bytes
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockNonce16Generator: Mock<NonceGenerator16ByteType>, NonceGenerator16ByteType {
|
||||
var NonceBytes: Int = 16
|
||||
|
||||
func nonce() -> Array<UInt8> { return accept() as! [UInt8] }
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockNonce24Generator: Mock<NonceGenerator24ByteType>, NonceGenerator24ByteType {
|
||||
var NonceBytes: Int = 24
|
||||
|
||||
func nonce() -> Array<UInt8> { return accept() as! [UInt8] }
|
||||
}
|
|
@ -6,7 +6,7 @@ import SessionUtilitiesKit
|
|||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockOGMCache: Mock<OGMMutableCacheType>, OGMMutableCacheType {
|
||||
class MockOGMCache: Mock<OGMCacheType>, OGMCacheType {
|
||||
var defaultRoomsPublisher: AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error>? {
|
||||
get { return accept() as? AnyPublisher<[OpenGroupManager.DefaultRoomInfo], Error> }
|
||||
set { accept(args: [newValue]) }
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Sodium
|
||||
|
||||
@testable import SessionMessagingKit
|
||||
|
||||
class MockSign: Mock<SignType>, SignType {
|
||||
var Bytes: Int = 64
|
||||
var PublicKeyBytes: Int = 32
|
||||
|
||||
func signature(message: Bytes, secretKey: Bytes) -> Bytes? {
|
||||
return accept(args: [message, secretKey]) as? Bytes
|
||||
}
|
||||
|
||||
func verify(message: Bytes, publicKey: Bytes, signature: Bytes) -> Bool {
|
||||
return accept(args: [message, publicKey, signature]) as! Bool
|
||||
}
|
||||
|
||||
func toX25519(ed25519PublicKey: Bytes) -> Bytes? {
|
||||
return accept(args: [ed25519PublicKey]) as? Bytes
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue