Added more unit tests for the OpenGroupAPI (fixed a couple bugs as well)

pull/592/head
Morgan Pretty 3 years ago
parent eb927c36a9
commit 4963a84ddd

@ -151,6 +151,7 @@
A1C32D5017A06538000A904E /* AddressBookUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4F17A06537000A904E /* AddressBookUI.framework */; };
A1C32D5117A06544000A904E /* AddressBook.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A1C32D4D17A0652C000A904E /* AddressBook.framework */; };
A5509ECA1A69AB8B00ABA4BC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A5509EC91A69AB8B00ABA4BC /* Main.storyboard */; };
B5FE70D512E75D659386BAD4 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F1E0F51F17E4443731B94D32 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */; };
B66DBF4A19D5BBC8006EA940 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B66DBF4919D5BBC8006EA940 /* Images.xcassets */; };
B67EBF5D19194AC60084CCFD /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B67EBF5C19194AC60084CCFD /* Settings.bundle */; };
B6B226971BE4B7D200860F4D /* ContactsUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B6B226961BE4B7D200860F4D /* ContactsUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
@ -767,7 +768,6 @@
D24B5BD5169F568C00681372 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D24B5BD4169F568C00681372 /* AudioToolbox.framework */; };
D2AEACDC16C426DA00C364C0 /* CFNetwork.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D2AEACDB16C426DA00C364C0 /* CFNetwork.framework */; };
D48CEFD2222D323FEFEFC6CC /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8962372EEC51D3F56FE3A68A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */; };
E197F4A653289312F13926E6 /* Pods_SessionMessagingKitTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CF061FF98D2BFA3ECCF8C6F6 /* Pods_SessionMessagingKitTests.framework */; };
EF764C351DB67CC5000D9A87 /* UIViewController+Permissions.m in Sources */ = {isa = PBXBuildFile; fileRef = EF764C341DB67CC5000D9A87 /* UIViewController+Permissions.m */; };
F5765D284BC6ECAC0C1D33F0 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E4A93ECA93B3DE800CC7D7F6 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_SessionNotificationServiceExtension.framework */; };
FC3BD9881A30A790005B96BB /* Social.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FC3BD9871A30A790005B96BB /* Social.framework */; };
@ -842,6 +842,8 @@
FDC438B727BB160000C60D73 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438B627BB160000C60D73 /* Error.swift */; };
FDC438B927BB161E00C60D73 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438B827BB161E00C60D73 /* Version.swift */; };
FDC438BD27BB2AB400C60D73 /* Mockable.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438BC27BB2AB400C60D73 /* Mockable.swift */; };
FDC438C127BB4E6800C60D73 /* Dependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C027BB4E6800C60D73 /* Dependencies.swift */; };
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDC438C227BB512200C60D73 /* SodiumProtocols.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -1022,6 +1024,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0208C84C4D15048D699BEC10 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig"; sourceTree = "<group>"; };
038A3BABD5BA0CE41D8C17F5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
0840117FDFD286D1CC14A2E1 /* Pods-SessionTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionTests/Pods-SessionTests.debug.xcconfig"; sourceTree = "<group>"; };
0D3D13FEE4FF6A2E2ED85322 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.app store release.xcconfig"; sourceTree = "<group>"; };
@ -1227,6 +1230,7 @@
8981C8F64D94D3C52EB67A2C /* Pods-SignalTests.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalTests.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests.test.xcconfig"; sourceTree = "<group>"; };
8EEE74B0753448C085B48721 /* Pods-SignalMessaging.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.app store release.xcconfig"; sourceTree = "<group>"; };
948239851C08032C842937CC /* Pods-SignalMessaging.test.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.test.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.test.xcconfig"; sourceTree = "<group>"; };
949F269926ABA08C125DCA9D /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig"; sourceTree = "<group>"; };
9AE1058A3BB2148A279432B2 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit.debug.xcconfig"; sourceTree = "<group>"; };
9B3329176C10E9640865E65B /* Pods-GlobalDependencies-Session.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-Session.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-Session/Pods-GlobalDependencies-Session.debug.xcconfig"; sourceTree = "<group>"; };
9B533A9FA46206D3D99C9ADA /* Pods-SignalMessaging.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SignalMessaging.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SignalMessaging/Pods-SignalMessaging.debug.xcconfig"; sourceTree = "<group>"; };
@ -1869,7 +1873,6 @@
C8153B96A292A25045BE2C54 /* Pods-SessionTests.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionTests.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionTests/Pods-SessionTests.app store release.xcconfig"; sourceTree = "<group>"; };
C88965DE4F4EC4FC919BEC4E /* Pods-SessionUIKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionUIKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionUIKit/Pods-SessionUIKit.debug.xcconfig"; sourceTree = "<group>"; };
C98441E849C3CA7FE8220D33 /* Pods-SessionNotificationServiceExtension.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionNotificationServiceExtension.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionNotificationServiceExtension/Pods-SessionNotificationServiceExtension.app store release.xcconfig"; sourceTree = "<group>"; };
CF061FF98D2BFA3ECCF8C6F6 /* Pods_SessionMessagingKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SessionMessagingKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D2179CFB16BB0B3A0006F3AB /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = System/Library/Frameworks/CoreTelephony.framework; sourceTree = SDKROOT; };
D2179CFD16BB0B480006F3AB /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
D221A089169C9E5E00537ABF /* Session.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Session.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -1894,6 +1897,7 @@
EF764C331DB67CC5000D9A87 /* UIViewController+Permissions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIViewController+Permissions.h"; sourceTree = "<group>"; };
EF764C341DB67CC5000D9A87 /* UIViewController+Permissions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+Permissions.m"; sourceTree = "<group>"; };
F121FB43E2A1C1CF7F2AFC23 /* Pods-SessionPushNotificationExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionPushNotificationExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionPushNotificationExtension/Pods-SessionPushNotificationExtension.debug.xcconfig"; sourceTree = "<group>"; };
F1E0F51F17E4443731B94D32 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F4DC483F404B65C59D9C2CF8 /* Pods-SessionMessagingKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionMessagingKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionMessagingKitTests/Pods-SessionMessagingKitTests.debug.xcconfig"; sourceTree = "<group>"; };
F62ECF7B8AF4F8089AA705B3 /* Pods-LokiPushNotificationService.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LokiPushNotificationService.debug.xcconfig"; path = "Pods/Target Support Files/Pods-LokiPushNotificationService/Pods-LokiPushNotificationService.debug.xcconfig"; sourceTree = "<group>"; };
F9BBF530D71905BA9007675F /* Pods-SessionShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionShareExtension.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SessionShareExtension/Pods-SessionShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
@ -1968,6 +1972,8 @@
FDC438B627BB160000C60D73 /* Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = "<group>"; };
FDC438B827BB161E00C60D73 /* Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = "<group>"; };
FDC438BC27BB2AB400C60D73 /* Mockable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mockable.swift; sourceTree = "<group>"; };
FDC438C027BB4E6800C60D73 /* Dependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dependencies.swift; sourceTree = "<group>"; };
FDC438C227BB512200C60D73 /* SodiumProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SodiumProtocols.swift; sourceTree = "<group>"; };
FEDBAE1B98C49BBE8C87F575 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionUtilitiesKit.debug.xcconfig"; sourceTree = "<group>"; };
FF9BA33D021B115B1F5B4E46 /* Pods-SessionMessagingKit.app store release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SessionMessagingKit.app store release.xcconfig"; path = "Pods/Target Support Files/Pods-SessionMessagingKit/Pods-SessionMessagingKit.app store release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@ -2089,7 +2095,7 @@
buildActionMask = 2147483647;
files = (
FDC4389227B9FFC700C60D73 /* SessionMessagingKit.framework in Frameworks */,
E197F4A653289312F13926E6 /* Pods_SessionMessagingKitTests.framework in Frameworks */,
B5FE70D512E75D659386BAD4 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -2303,6 +2309,8 @@
A0FB43B511403A5FAFAC88B8 /* Pods-SessionMessagingKitTests.app store release.xcconfig */,
0840117FDFD286D1CC14A2E1 /* Pods-SessionTests.debug.xcconfig */,
C8153B96A292A25045BE2C54 /* Pods-SessionTests.app store release.xcconfig */,
949F269926ABA08C125DCA9D /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig */,
0208C84C4D15048D699BEC10 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
@ -3761,8 +3769,8 @@
038A3BABD5BA0CE41D8C17F5 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionShareExtension.framework */,
C5060C3B36A848B71CCE4685 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionUtilitiesKit.framework */,
8962372EEC51D3F56FE3A68A /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SignalUtilitiesKit.framework */,
CF061FF98D2BFA3ECCF8C6F6 /* Pods_SessionMessagingKitTests.framework */,
D2C155B76C8483CB9A6EA9B4 /* Pods_SessionTests.framework */,
F1E0F51F17E4443731B94D32 /* Pods_GlobalDependencies_FrameworkAndExtensionDependencies_ExtendedDependencies_SessionMessagingKit_SessionMessagingKitTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -3814,6 +3822,8 @@
FDC4380827B31D4E00C60D73 /* Error.swift */,
FDC4381627B32EC700C60D73 /* Personalization.swift */,
FDC4381427B329CE00C60D73 /* NonceGenerator16Byte.swift */,
FDC438C027BB4E6800C60D73 /* Dependencies.swift */,
FDC438C227BB512200C60D73 /* SodiumProtocols.swift */,
);
path = Types;
sourceTree = "<group>";
@ -4666,15 +4676,15 @@
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SessionMessagingKitTests/Pods-SessionMessagingKitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SessionMessagingKitTests/Pods-SessionMessagingKitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SessionMessagingKitTests/Pods-SessionMessagingKitTests-frameworks.sh\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
7E2D14F857C70F98DED3B8E9 /* [CP] Check Pods Manifest.lock */ = {
@ -4736,7 +4746,7 @@
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-SessionMessagingKitTests-checkManifestLockResult.txt",
"$(DERIVED_FILE_DIR)/Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@ -5210,6 +5220,7 @@
FDC4384827B47F4D00C60D73 /* LegacyRoomInfo.swift in Sources */,
C32C598A256D0664003C73A2 /* SNProtoEnvelope+Conversion.swift in Sources */,
C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */,
FDC438C327BB512200C60D73 /* SodiumProtocols.swift in Sources */,
B8856D11256F112A001CE70E /* OWSAudioSession.swift in Sources */,
C3DB66C3260ACCE6001EFC55 /* OpenGroupPollerV2.swift in Sources */,
FDC438AE27BB148700C60D73 /* UserDeleteMessagesResponse.swift in Sources */,
@ -5230,6 +5241,7 @@
C32C5FD6256E0346003C73A2 /* Notification+Thread.swift in Sources */,
FDC438A827BB11CD00C60D73 /* UserPermissionsRequest.swift in Sources */,
C3BBE0C72554F1570050F1E3 /* FixedWidthInteger+BigEndian.swift in Sources */,
FDC438C127BB4E6800C60D73 /* Dependencies.swift in Sources */,
FDC4383827B3863200C60D73 /* VersionResponse.swift in Sources */,
C32C5C88256DD0D2003C73A2 /* Storage+Messaging.swift in Sources */,
C32C59C7256DB41F003C73A2 /* TSThread.m in Sources */,
@ -6878,7 +6890,7 @@
};
FDC4389627B9FFC700C60D73 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = F4DC483F404B65C59D9C2CF8 /* Pods-SessionMessagingKitTests.debug.xcconfig */;
baseConfigurationReference = 949F269926ABA08C125DCA9D /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.debug.xcconfig */;
buildSettings = {
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@ -6919,7 +6931,7 @@
};
FDC4389727B9FFC700C60D73 /* App Store Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = A0FB43B511403A5FAFAC88B8 /* Pods-SessionMessagingKitTests.app store release.xcconfig */;
baseConfigurationReference = 0208C84C4D15048D699BEC10 /* Pods-GlobalDependencies-FrameworkAndExtensionDependencies-ExtendedDependencies-SessionMessagingKit-SessionMessagingKitTests.app store release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;

@ -27,7 +27,17 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
codeCoverageEnabled = "YES"
onlyGenerateCoverageForSpecifiedTargets = "YES">
<CodeCoverageTargets>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "C3C2A6EF25539DE700C340D1"
BuildableName = "SessionMessagingKit.framework"
BlueprintName = "SessionMessagingKit"
ReferencedContainer = "container:Session.xcodeproj">
</BuildableReference>
</CodeCoverageTargets>
<Testables>
<TestableReference
skipped = "NO">

@ -7,6 +7,7 @@ enum Header: String {
case contentType = "Content-Type"
case room = "Room" // TODO: Confirm this is needed
case fileName = "X-Filename"
case sogsPubKey = "X-SOGS-Pubkey"
case sogsNonce = "X-SOGS-Nonce"

@ -7,6 +7,5 @@ enum QueryParam: String {
case fromServerId = "from_server_id"
case required = "required"
case fileName = "X-Filename"
case limit // For messages - number between 1 and 256 (default is 100)
}

@ -1,5 +1,10 @@
extension Storage {
public protocol SessionMessagingKitOpenGroupStorageProtocol {
func getOpenGroupImage(for room: String, on server: String) -> Data?
func setOpenGroupImage(to data: Data, for room: String, on server: String, using transaction: Any)
}
extension Storage: SessionMessagingKitOpenGroupStorageProtocol {
// MARK: - Open Groups

@ -68,7 +68,7 @@ public extension Decodable {
}
extension Promise where T == (OnionRequestResponseInfoType, Data?) {
func decoded(as types: OpenGroupAPIV2.BatchResponseTypes, on queue: DispatchQueue? = nil, error: Error? = nil) -> Promise<OpenGroupAPIV2.BatchResponse> {
func decoded(as types: OpenGroupAPIV2.BatchResponseTypes, on queue: DispatchQueue? = nil, error: Error) -> Promise<OpenGroupAPIV2.BatchResponse> {
self.map(on: queue) { responseInfo, maybeData -> OpenGroupAPIV2.BatchResponse in
// Need to split the data into an array of data so each item can be Decoded correctly
guard let data: Data = maybeData else { throw OpenGroupAPIV2.Error.parsingFailed }
@ -85,8 +85,8 @@ extension Promise where T == (OnionRequestResponseInfoType, Data?) {
.map { data, type in try type.decoded(from: data) }
.map { data in (responseInfo, data) }
}
catch let thrownError {
throw (error ?? thrownError)
catch _ {
throw error
}
}
}

@ -197,7 +197,7 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - Capabilities
public static func capabilities(on server: String) -> Promise<(OnionRequestResponseInfoType, Capabilities)> {
public static func capabilities(on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Capabilities)> {
let request: Request = Request(
server: server,
endpoint: .capabilities,
@ -205,45 +205,39 @@ public final class OpenGroupAPIV2: NSObject {
)
// TODO: Handle a `412` response (ie. a required capability isn't supported).
return send(request)
return send(request, using: dependencies)
.decoded(as: Capabilities.self, on: OpenGroupAPIV2.workQueue, error: Error.parsingFailed)
}
// MARK: - Room
public static func rooms(
for server: String,
through api: OnionRequestAPIType.Type = OnionRequestAPI.self,
using storage: SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration.shared.storage,
nonceGenerator: NonceGenerator16ByteType = NonceGenerator16Byte(),
date: Date = Date()
) -> Promise<(OnionRequestResponseInfoType, [Room])> {
public static func rooms(for server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Room])> {
let request: Request = Request(
server: server,
endpoint: .rooms
)
return send(request, through: api, using: storage, nonceGenerator: nonceGenerator, date: date)
return send(request, using: dependencies)
.decoded(as: [Room].self, on: OpenGroupAPIV2.workQueue, error: Error.parsingFailed)
}
public static func room(for roomToken: String, on server: String) -> Promise<(OnionRequestResponseInfoType, Room)> {
public static func room(for roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Room)> {
let request: Request = Request(
server: server,
endpoint: .room(roomToken)
)
return send(request)
return send(request, using: dependencies)
.decoded(as: Room.self, on: OpenGroupAPIV2.workQueue, error: Error.parsingFailed)
}
public static func roomPollInfo(lastUpdated: Int64, for roomToken: String, on server: String) -> Promise<(OnionRequestResponseInfoType, RoomPollInfo)> {
public static func roomPollInfo(lastUpdated: Int64, for roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, RoomPollInfo)> {
let request: Request = Request(
server: server,
endpoint: .roomPollInfo(roomToken, lastUpdated)
)
return send(request)
return send(request, using: dependencies)
.decoded(as: RoomPollInfo.self, on: OpenGroupAPIV2.workQueue, error: Error.parsingFailed)
}
@ -255,7 +249,8 @@ public final class OpenGroupAPIV2: NSObject {
on server: String,
whisperTo: String?,
whisperMods: Bool,
with serverPublicKey: String
with serverPublicKey: String,
using dependencies: Dependencies = Dependencies()
) -> Promise<(OnionRequestResponseInfoType, Message)> {
// TODO: Change this to use '.blinded' once it's working.
guard let signedRequest: (data: Data, signature: Data) = SendMessageRequest.sign(message: plaintext, for: .standard, with: serverPublicKey) else {
@ -281,12 +276,11 @@ public final class OpenGroupAPIV2: NSObject {
body: body
)
return send(request)
return send(request, using: dependencies)
.decoded(as: Message.self, on: OpenGroupAPIV2.workQueue, error: Error.parsingFailed)
}
public static func recentMessages(in roomToken: String, on server: String) -> Promise<(OnionRequestResponseInfoType, [Message])> {
// TODO: Recent vs. Since?
public static func recentMessages(in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
let request: Request = Request(
server: server,
endpoint: .roomMessagesRecent(roomToken)
@ -296,15 +290,15 @@ public final class OpenGroupAPIV2: NSObject {
// ].compactMapValues { $0 }
)
return send(request)
return send(request, using: dependencies)
.decoded(as: [Message].self, on: OpenGroupAPIV2.workQueue, error: Error.parsingFailed)
.then(on: OpenGroupAPIV2.workQueue) { responseInfo, messages -> Promise<(OnionRequestResponseInfoType, [Message])> in
process(messages: messages, for: roomToken, on: server)
process(messages: messages, for: roomToken, on: server, using: dependencies)
.map { processedMessages in (responseInfo, processedMessages) }
}
}
public static func messagesBefore(messageId: Int64, in roomToken: String, on server: String) -> Promise<(OnionRequestResponseInfoType, [Message])> {
public static func messagesBefore(messageId: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, [Message])> {
// TODO: Recent vs. Since?
let request: Request = Request(
server: server,
@ -315,10 +309,10 @@ public final class OpenGroupAPIV2: NSObject {
// ].compactMapValues { $0 }
)
return send(request)
return send(request, using: dependencies)
.decoded(as: [Message].self, on: OpenGroupAPIV2.workQueue, error: Error.parsingFailed)
.then(on: OpenGroupAPIV2.workQueue) { responseInfo, messages -> Promise<(OnionRequestResponseInfoType, [Message])> in
process(messages: messages, for: roomToken, on: server)
process(messages: messages, for: roomToken, on: server, using: dependencies)
.map { processedMessages in (responseInfo, processedMessages) }
}
}
@ -334,53 +328,53 @@ public final class OpenGroupAPIV2: NSObject {
// ].compactMapValues { $0 }
)
return send(request)
return send(request, using: dependencies)
.decoded(as: [Message].self, on: OpenGroupAPIV2.workQueue, error: Error.parsingFailed)
.then(on: OpenGroupAPIV2.workQueue) { responseInfo, messages -> Promise<(OnionRequestResponseInfoType, [Message])> in
process(messages: messages, for: roomToken, on: server)
process(messages: messages, for: roomToken, on: server, using: dependencies)
.map { processedMessages in (responseInfo, processedMessages) }
}
}
// MARK: - Pinning
public static func pinMessage(id: Int64, in roomToken: String, on server: String) -> Promise<OnionRequestResponseInfoType> {
public static func pinMessage(id: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<OnionRequestResponseInfoType> {
let request: Request = Request(
method: .post,
server: server,
endpoint: .roomPinMessage(roomToken, id: id)
)
return send(request)
return send(request, using: dependencies)
.map { responseInfo, _ in responseInfo }
}
public static func unpinMessage(id: Int64, in roomToken: String, on server: String) -> Promise<OnionRequestResponseInfoType> {
public static func unpinMessage(id: Int64, in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<OnionRequestResponseInfoType> {
let request: Request = Request(
method: .post,
server: server,
endpoint: .roomUnpinMessage(roomToken, id: id)
)
return send(request)
return send(request, using: dependencies)
.map { responseInfo, _ in responseInfo }
}
public static func unpinAll(in roomToken: String, on server: String) -> Promise<OnionRequestResponseInfoType> {
public static func unpinAll(in roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<OnionRequestResponseInfoType> {
let request: Request = Request(
method: .post,
server: server,
endpoint: .roomUnpinAll(roomToken)
)
return send(request)
return send(request, using: dependencies)
.map { responseInfo, _ in responseInfo }
}
// MARK: - Files
// TODO: Shift this logic to the `OpenGroupManager` (makes more sense since it's not API logic)
public static func roomImage(_ fileId: Int64, for roomToken: String, on server: String) -> Promise<Data> {
public static func roomImage(_ fileId: Int64, for roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<Data> {
// 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
// user * hasn't * joined yet. We don't want to re-fetch these images every time the
@ -391,11 +385,11 @@ public final class OpenGroupAPIV2: NSObject {
// don't double up on fetch requests by storing the existing request as a promise if
// there is one.
let lastOpenGroupImageUpdate: Date? = UserDefaults.standard[.lastOpenGroupImageUpdate]
let now: Date = Date()
let now: Date = dependencies.date
let timeSinceLastUpdate: TimeInterval = (given(lastOpenGroupImageUpdate) { now.timeIntervalSince($0) } ?? .greatestFiniteMagnitude)
let updateInterval: TimeInterval = (7 * 24 * 60 * 60)
if let data = Storage.shared.getOpenGroupImage(for: roomToken, on: server), server == defaultServer, timeSinceLastUpdate < updateInterval {
if let data = dependencies.storage.getOpenGroupImage(for: roomToken, on: server), server == defaultServer, timeSinceLastUpdate < updateInterval {
return Promise.value(data)
}
@ -403,12 +397,12 @@ public final class OpenGroupAPIV2: NSObject {
return promise
}
let promise: Promise<Data> = downloadFile(fileId, from: roomToken, on: server)
let promise: Promise<Data> = downloadFile(fileId, from: roomToken, on: server, using: dependencies)
.map { _, data in data }
_ = promise.done(on: OpenGroupAPIV2.workQueue) { imageData in
if server == defaultServer {
Storage.shared.write { transaction in
Storage.shared.setOpenGroupImage(to: imageData, for: roomToken, on: server, using: transaction)
dependencies.storage.write { transaction in
dependencies.storage.setOpenGroupImage(to: imageData, for: roomToken, on: server, using: transaction)
}
UserDefaults.standard[.lastOpenGroupImageUpdate] = now
}
@ -418,41 +412,41 @@ public final class OpenGroupAPIV2: NSObject {
return promise
}
public static func uploadFile(_ bytes: [UInt8], fileName: String? = nil, to roomToken: String, on server: String) -> Promise<(OnionRequestResponseInfoType, FileUploadResponse)> {
public static func uploadFile(_ bytes: [UInt8], fileName: String? = nil, to roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, FileUploadResponse)> {
let request: Request = Request(
method: .post,
server: server,
endpoint: .roomFile(roomToken),
queryParameters: [ .fileName: fileName ].compactMapValues { $0 },
headers: [ .fileName: fileName ].compactMapValues { $0 },
body: Data(bytes)
)
return send(request)
return send(request, using: dependencies)
.decoded(as: FileUploadResponse.self, on: OpenGroupAPIV2.workQueue, error: Error.parsingFailed)
}
/// Warning: This approach is less efficient as it expects the data to be base64Encoded (with is 33% larger than binary), please use the binary approach
/// whenever possible
public static func uploadFile(_ base64EncodedString: String, fileName: String? = nil, to roomToken: String, on server: String) -> Promise<(OnionRequestResponseInfoType, FileUploadResponse)> {
public static func uploadFile(_ base64EncodedString: String, fileName: String? = nil, to roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, FileUploadResponse)> {
let request: Request = Request(
method: .post,
server: server,
endpoint: .roomFileJson(roomToken),
queryParameters: [ .fileName: fileName ].compactMapValues { $0 },
headers: [ .fileName: fileName ].compactMapValues { $0 },
body: Data(base64Encoded: base64EncodedString)
)
return send(request)
return send(request, using: dependencies)
.decoded(as: FileUploadResponse.self, on: OpenGroupAPIV2.workQueue, error: Error.parsingFailed)
}
public static func downloadFile(_ fileId: Int64, from roomToken: String, on server: String) -> Promise<(OnionRequestResponseInfoType, Data)> {
public static func downloadFile(_ fileId: Int64, from roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Data)> {
let request: Request = Request(
server: server,
endpoint: .roomFileIndividual(roomToken, fileId)
)
return send(request)
return send(request, using: dependencies)
.map { responseInfo, maybeData in
guard let data: Data = maybeData else { throw Error.parsingFailed }
@ -460,19 +454,19 @@ public final class OpenGroupAPIV2: NSObject {
}
}
public static func downloadFileJson(_ fileId: Int64, from roomToken: String, on server: String) -> Promise<(OnionRequestResponseInfoType, FileDownloadResponse)> {
public static func downloadFileJson(_ fileId: Int64, from roomToken: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, FileDownloadResponse)> {
let request: Request = Request(
server: server,
endpoint: .roomFileIndividualJson(roomToken, fileId)
)
// TODO: This endpoint is getting rewritten to return just data (properties would come through as headers)
return send(request)
return send(request, using: dependencies)
.decoded(as: FileDownloadResponse.self, on: OpenGroupAPIV2.workQueue, error: Error.parsingFailed)
}
// MARK: - Users
public static func userBan(_ sessionId: String, for timeout: TimeInterval? = nil, from roomTokens: [String]? = nil, on server: String) -> Promise<(OnionRequestResponseInfoType, Data?)> {
public static func userBan(_ sessionId: String, for timeout: TimeInterval? = nil, from roomTokens: [String]? = nil, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Data?)> {
let requestBody: UserBanRequest = UserBanRequest(
rooms: roomTokens,
global: (roomTokens == nil ? true : nil),
@ -490,10 +484,10 @@ public final class OpenGroupAPIV2: NSObject {
body: body
)
return send(request)
return send(request, using: dependencies)
}
public static func userUnban(_ sessionId: String, from roomTokens: [String]? = nil, on server: String) -> Promise<(OnionRequestResponseInfoType, Data?)> {
public static func userUnban(_ sessionId: String, from roomTokens: [String]? = nil, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Data?)> {
let requestBody: UserUnbanRequest = UserUnbanRequest(
rooms: roomTokens,
global: (roomTokens == nil ? true : nil)
@ -510,10 +504,10 @@ public final class OpenGroupAPIV2: NSObject {
body: body
)
return send(request)
return send(request, using: dependencies)
}
public static func userPermissionUpdate(_ sessionId: String, read: Bool, write: Bool, upload: Bool, for roomTokens: [String], timeout: TimeInterval, on server: String) -> Promise<(OnionRequestResponseInfoType, Data?)> {
public static func userPermissionUpdate(_ sessionId: String, read: Bool, write: Bool, upload: Bool, for roomTokens: [String], timeout: TimeInterval, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Data?)> {
let requestBody: UserPermissionsRequest = UserPermissionsRequest(
rooms: roomTokens,
timeout: timeout,
@ -533,10 +527,10 @@ public final class OpenGroupAPIV2: NSObject {
body: body
)
return send(request)
return send(request, using: dependencies)
}
public static func userModeratorUpdate(_ sessionId: String, moderator: Bool, admin: Bool, visible: Bool, for roomTokens: [String]? = nil, on server: String) -> Promise<(OnionRequestResponseInfoType, Data?)> {
public static func userModeratorUpdate(_ sessionId: String, moderator: Bool, admin: Bool, visible: Bool, for roomTokens: [String]? = nil, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Data?)> {
let requestBody: UserModeratorRequest = UserModeratorRequest(
rooms: roomTokens,
global: (roomTokens == nil ? true : nil),
@ -556,10 +550,10 @@ public final class OpenGroupAPIV2: NSObject {
body: body
)
return send(request)
return send(request, using: dependencies)
}
public static func userDeleteMessages(_ sessionId: String, for roomTokens: [String]? = nil, on server: String) -> Promise<(OnionRequestResponseInfoType, UserDeleteMessagesResponse)> {
public static func userDeleteMessages(_ sessionId: String, for roomTokens: [String]? = nil, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, UserDeleteMessagesResponse)> {
let requestBody: UserDeleteMessagesRequest = UserDeleteMessagesRequest(
rooms: roomTokens,
global: (roomTokens == nil ? true : nil)
@ -576,26 +570,25 @@ public final class OpenGroupAPIV2: NSObject {
body: body
)
return send(request)
return send(request, using: dependencies)
.decoded(as: UserDeleteMessagesResponse.self, on: OpenGroupAPIV2.workQueue, error: Error.parsingFailed)
}
// MARK: - Processing
// TODO: Move these methods to the OpenGroupManager? (seems odd for them to be in the API)
private static func process(messages: [Message]?, for room: String, on server: String) -> Promise<[Message]> {
private static func process(messages: [Message]?, for room: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<[Message]> {
guard let messages: [Message] = messages, !messages.isEmpty else { return Promise.value([]) }
let storage = SNMessagingKitConfiguration.shared.storage
let seqNo: Int64 = (messages.compactMap { $0.seqNo }.max() ?? 0)
let lastMessageSeqNo: Int64 = (storage.getLastMessageServerID(for: room, on: server) ?? 0)
let lastMessageSeqNo: Int64 = (dependencies.storage.getLastMessageServerID(for: room, on: server) ?? 0)
if seqNo > lastMessageSeqNo {
let (promise, seal) = Promise<[Message]>.pending()
storage.write(
dependencies.storage.write(
with: { transaction in
storage.setLastMessageServerID(for: room, on: server, to: seqNo, using: transaction)
dependencies.storage.setLastMessageServerID(for: room, on: server, to: seqNo, using: transaction)
},
completion: {
seal.fulfill(messages)
@ -608,19 +601,18 @@ public final class OpenGroupAPIV2: NSObject {
return Promise.value(messages)
}
private static func process(deletions: [Deletion]?, for room: String, on server: String) -> Promise<[Deletion]> {
private static func process(deletions: [Deletion]?, for room: String, on server: String, using dependencies: Dependencies = Dependencies()) -> Promise<[Deletion]> {
guard let deletions: [Deletion] = deletions else { return Promise.value([]) }
let storage = SNMessagingKitConfiguration.shared.storage
let serverID: Int64 = (deletions.compactMap { $0.id }.max() ?? 0)
let lastDeletionServerID: Int64 = (storage.getLastDeletionServerID(for: room, on: server) ?? 0)
let lastDeletionServerID: Int64 = (dependencies.storage.getLastDeletionServerID(for: room, on: server) ?? 0)
if serverID > lastDeletionServerID {
let (promise, seal) = Promise<[Deletion]>.pending()
storage.write(
dependencies.storage.write(
with: { transaction in
storage.setLastDeletionServerID(for: room, on: server, to: serverID, using: transaction)
dependencies.storage.setLastDeletionServerID(for: room, on: server, to: serverID, using: transaction)
},
completion: {
seal.fulfill(deletions)
@ -639,14 +631,15 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - General
public static func getDefaultRoomsIfNeeded() {
// TODO: Shift this to the OpenGroupManagerV2? (seems more at place there than in the API)
public static func getDefaultRoomsIfNeeded(using dependencies: Dependencies = Dependencies()) {
Storage.shared.write(
with: { transaction in
Storage.shared.setOpenGroupPublicKey(for: defaultServer, to: defaultServerPublicKey, using: transaction)
dependencies.storage.setOpenGroupPublicKey(for: defaultServer, to: defaultServerPublicKey, using: transaction)
},
completion: {
let promise = attempt(maxRetryCount: 8, recoveringOn: DispatchQueue.main) {
OpenGroupAPIV2.rooms(for: defaultServer)
OpenGroupAPIV2.rooms(for: defaultServer, using: dependencies)
.map { _, data in data }
}
_ = promise.done(on: OpenGroupAPIV2.workQueue) { items in
@ -657,7 +650,7 @@ public final class OpenGroupAPIV2: NSObject {
return (imageId, room.token)
}
.forEach { imageId, roomToken in
roomImage(imageId, for: roomToken, on: defaultServer)
roomImage(imageId, for: roomToken, on: defaultServer, using: dependencies)
.retainUntilComplete()
}
}
@ -671,26 +664,18 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - Authentication
// TODO: Turn 'Sodium' into a protocol for unit testing
static func sign(
_ request: URLRequest,
with publicKey: String,
using storage: SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration.shared.storage,
sodium: Sodium = Sodium(),
nonceGenerator: NonceGenerator16ByteType = NonceGenerator16Byte(),
date: Date = Date()
) -> URLRequest? {
private static func sign(_ request: URLRequest, with publicKey: String, using dependencies: Dependencies = Dependencies()) -> URLRequest? {
guard let url: URL = request.url else { return nil }
var updatedRequest: URLRequest = request
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(nonceGenerator.nonce())
let timestamp: Int = Int(floor(dependencies.date.timeIntervalSince1970))
let nonce: Data = Data(dependencies.nonceGenerator.nonce())
guard let publicKeyData: Data = publicKey.dataFromHex() else { return nil }
guard let userKeyPair: ECKeyPair = storage.getUserKeyPair() else {
guard let userKeyPair: ECKeyPair = dependencies.storage.getUserKeyPair() else {
return nil
}
// guard let blindedKeyPair: ECKeyPair = try? userKeyPair.convert(to: .blinded, with: publicKey) else {
@ -702,7 +687,7 @@ public final class OpenGroupAPIV2: NSObject {
/// Generate the sharedSecret by "aB || A || B" where
/// a, A are the users private and public keys respectively,
/// B is the SOGS public key
let maybeSharedSecret: Data? = sodium.sharedSecret(blindedKeyPair.privateKey.bytes, publicKeyData.bytes)?
let maybeSharedSecret: Data? = dependencies.sodium.sharedSecret(blindedKeyPair.privateKey.bytes, publicKeyData.bytes)?
.appending(blindedKeyPair.publicKey)
.appending(publicKeyData.bytes)
@ -721,10 +706,10 @@ public final class OpenGroupAPIV2: NSObject {
.appending(request.httpBody?.bytes ?? []) // TODO: Might need to do the 'httpBodyStream' as well???
guard let sharedSecret: Data = maybeSharedSecret else { return nil }
guard let intermediateHash: Bytes = sodium.genericHash.hashSaltPersonal(message: sharedSecret.bytes, outputLength: 42, key: nil, salt: nonce.bytes, personal: Personalization.sharedKeys.bytes) else {
guard let intermediateHash: Bytes = dependencies.genericHash.hashSaltPersonal(message: sharedSecret.bytes, outputLength: 42, key: nil, salt: nonce.bytes, personal: Personalization.sharedKeys.bytes) else {
return nil
}
guard let secretHash: Bytes = sodium.genericHash.hashSaltPersonal(message: secretHashMessage, outputLength: 42, key: intermediateHash, salt: nonce.bytes, personal: Personalization.authHeader.bytes) else {
guard let secretHash: Bytes = dependencies.genericHash.hashSaltPersonal(message: secretHashMessage, outputLength: 42, key: intermediateHash, salt: nonce.bytes, personal: Personalization.authHeader.bytes) else {
return nil
}
@ -741,13 +726,7 @@ public final class OpenGroupAPIV2: NSObject {
// MARK: - Convenience
private static func send(
_ request: Request,
through api: OnionRequestAPIType.Type = OnionRequestAPI.self,
using storage: SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration.shared.storage,
nonceGenerator: NonceGenerator16ByteType = NonceGenerator16Byte(),
date: Date = Date()
) -> Promise<(OnionRequestResponseInfoType, Data?)> {
private static func send(_ request: Request, using dependencies: Dependencies = Dependencies()) -> Promise<(OnionRequestResponseInfoType, Data?)> {
guard let url: URL = request.url else { return Promise(error: Error.invalidURL) }
var urlRequest: URLRequest = URLRequest(url: url)
@ -758,21 +737,21 @@ public final class OpenGroupAPIV2: NSObject {
urlRequest.httpBody = request.body
if request.useOnionRouting {
guard let publicKey = storage.getOpenGroupPublicKey(for: request.server) else {
guard let publicKey = dependencies.storage.getOpenGroupPublicKey(for: request.server) else {
return Promise(error: Error.noPublicKey)
}
if request.isAuthRequired {
// Attempt to sign the request with the new auth
guard let signedRequest: URLRequest = sign(urlRequest, with: publicKey, using: storage, nonceGenerator: nonceGenerator, date: date) else {
guard let signedRequest: URLRequest = sign(urlRequest, with: publicKey, using: dependencies) else {
return Promise(error: Error.signingFailed)
}
// TODO: 'removeAuthToken' as a migration??? (would previously do this when getting a `401`).
return api.sendOnionRequest(signedRequest, to: request.server, with: publicKey)
return dependencies.api.sendOnionRequest(signedRequest, to: request.server, with: publicKey)
}
return api.sendOnionRequest(urlRequest, to: request.server, with: publicKey)
return dependencies.api.sendOnionRequest(urlRequest, to: request.server, with: publicKey)
}
preconditionFailure("It's currently not allowed to send non onion routed requests.")
@ -866,11 +845,11 @@ public final class OpenGroupAPIV2: NSObject {
server: server,
room: room,
endpoint: .legacyAuthTokenClaim(legacyAuth: true),
body: body,
headers: [
// Set explicitly here because is isn't in the database yet at this point
.authorization: authToken
],
body: body,
isAuthRequired: false
)

@ -0,0 +1,52 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import Sodium
import SessionSnodeKit
extension OpenGroupAPIV2 {
public struct Dependencies {
let api: OnionRequestAPIType.Type
let storage: SessionMessagingKitStorageProtocol
let sodium: SodiumType
let genericHash: GenericHashType
let nonceGenerator: NonceGenerator16ByteType
let date: Date
public init(
api: OnionRequestAPIType.Type = OnionRequestAPI.self,
storage: SessionMessagingKitStorageProtocol = SNMessagingKitConfiguration.shared.storage,
sodium: SodiumType = Sodium(),
genericHash: GenericHashType? = nil,
nonceGenerator: NonceGenerator16ByteType = NonceGenerator16Byte(),
date: Date = Date()
) {
self.api = api
self.storage = storage
self.sodium = sodium
self.genericHash = (genericHash ?? sodium.getGenericHash())
self.nonceGenerator = nonceGenerator
self.date = date
}
// MARK: - Convenience
public func with(
api: OnionRequestAPIType.Type? = nil,
storage: SessionMessagingKitStorageProtocol? = nil,
sodium: SodiumType? = nil,
genericHash: GenericHashType? = nil,
nonceGenerator: NonceGenerator16ByteType? = nil,
date: Date? = nil
) -> Dependencies {
return Dependencies(
api: (api ?? self.api),
storage: (storage ?? self.storage),
sodium: (sodium ?? self.sodium),
genericHash: (genericHash ?? self.genericHash),
nonceGenerator: (nonceGenerator ?? self.nonceGenerator),
date: (date ?? self.date)
)
}
}
}

@ -10,8 +10,8 @@ extension OpenGroupAPIV2 {
let room: String? // TODO: Remove this?
let endpoint: Endpoint
let queryParameters: [QueryParam: String]
let body: Data?
let headers: [Header: String]
let body: Data?
let isAuthRequired: Bool
/// Always `true` under normal circumstances. You might want to disable
/// this when running over Lokinet.
@ -23,8 +23,8 @@ extension OpenGroupAPIV2 {
room: String? = nil,
endpoint: Endpoint,
queryParameters: [QueryParam: String] = [:],
body: Data? = nil,
headers: [Header: String] = [:],
body: Data? = nil,
isAuthRequired: Bool = true,
useOnionRouting: Bool = true
) {
@ -33,8 +33,8 @@ extension OpenGroupAPIV2 {
self.room = room
self.endpoint = endpoint
self.queryParameters = queryParameters
self.body = body
self.headers = headers
self.body = body
self.isAuthRequired = isAuthRequired
self.useOnionRouting = useOnionRouting
}
@ -44,8 +44,6 @@ extension OpenGroupAPIV2 {
}
var urlPathAndParamsString: String {
guard method == .get else { return "/\(endpoint.path)" }
return [
"/\(endpoint.path)",
queryParameters

@ -0,0 +1,27 @@
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import Sodium
public protocol SodiumType {
func getGenericHash() -> GenericHashType
func sharedSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Sodium.SharedSecret?
}
public protocol GenericHashType {
func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes?
}
extension GenericHashType {
func hashSaltPersonal(message: Bytes, outputLength: Int, salt: Bytes, personal: Bytes) -> Bytes? {
return hashSaltPersonal(message: message, outputLength: outputLength, key: nil, salt: salt, personal: personal)
}
}
extension Sodium: SodiumType {
public func getGenericHash() -> GenericHashType { return genericHash }
}
extension GenericHash: GenericHashType {}

@ -1,7 +1,7 @@
import PromiseKit
import Sodium
public protocol SessionMessagingKitStorageProtocol {
public protocol SessionMessagingKitStorageProtocol: SessionMessagingKitOpenGroupStorageProtocol {
// MARK: - Shared

@ -3,6 +3,7 @@
import XCTest
import Nimble
import PromiseKit
import Sodium
import SessionSnodeKit
@testable import SessionMessagingKit
@ -66,14 +67,21 @@ class OpenGroupAPIV2Tests: XCTestCase {
}
var testStorage: TestStorage!
var dependencies: OpenGroupAPIV2.Dependencies!
// MARK: - Configuration
override func setUpWithError() throws {
testStorage = TestStorage()
dependencies = OpenGroupAPIV2.Dependencies(
api: TestApi.self,
storage: testStorage,
nonceGenerator: TestNonceGenerator(),
date: Date(timeIntervalSince1970: 1234567890)
)
testStorage.mockData[.allV2OpenGroups] = [
"0": OpenGroupV2(server: "testServer", room: "test1", name: "Test", publicKey: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d", imageID: nil)
"0": OpenGroupV2(server: "testServer", room: "testRoom", name: "Test", publicKey: "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d", imageID: nil)
]
testStorage.mockData[.openGroupPublicKeys] = ["testServer": "7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d"]
@ -85,14 +93,14 @@ class OpenGroupAPIV2Tests: XCTestCase {
}
override func tearDownWithError() throws {
dependencies = nil
testStorage = nil
}
// MARK: - Batching & Polling
func testPollGeneratesTheCorrectRequest() throws {
// Define a custom TestApi class so we can override the response
class TestApi1: TestApi {
class LocalTestApi: TestApi {
override class var mockResponse: Data? {
let responses: [Data] = [
try! JSONEncoder().encode(
@ -106,36 +114,7 @@ class OpenGroupAPIV2Tests: XCTestCase {
OpenGroupAPIV2.BatchSubResponse(
code: 200,
headers: [:],
body: OpenGroupAPIV2.RoomPollInfo(
token: nil,
created: nil,
name: nil,
description: nil,
imageId: nil,
infoUpdates: nil,
messageSequence: nil,
activeUsers: nil,
activeUsersCutoff: nil,
pinnedMessages: nil,
admin: nil,
globalAdmin: nil,
admins: nil,
hiddenAdmins: nil,
moderator: nil,
globalModerator: nil,
moderators: nil,
hiddenModerators: nil,
read: nil,
defaultRead: nil,
write: nil,
defaultWrite: nil,
upload: nil,
defaultUpload: nil,
details: nil
)
body: try! JSONDecoder().decode(OpenGroupAPIV2.RoomPollInfo.self, from: "{}".data(using: .utf8)!)
)
),
try! JSONEncoder().encode(
@ -150,60 +129,304 @@ class OpenGroupAPIV2Tests: XCTestCase {
return "[\(responses.map { String(data: $0, encoding: .utf8)! }.joined(separator: ","))]".data(using: .utf8)
}
}
dependencies = dependencies.with(api: LocalTestApi.self)
var pollResponse: [Endpoint: (OnionRequestResponseInfoType, Codable)]? = nil
var response: [Endpoint: (OnionRequestResponseInfoType, Codable)]? = nil
var error: Error? = nil
OpenGroupAPIV2.poll("testServer", through: TestApi1.self, using: testStorage, nonceGenerator: TestNonceGenerator(), date: Date(timeIntervalSince1970: 1234567890))
.map { result -> [Endpoint: (OnionRequestResponseInfoType, Codable)] in
pollResponse = result
return result
}
OpenGroupAPIV2.poll("testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(pollResponse)
expect(response)
.toEventuallyNot(
beNil(),
timeout: .milliseconds(10000)
timeout: .milliseconds(100)
)
expect(error?.localizedDescription).to(beNil())
// Validate the response data
expect(pollResponse?.values).to(haveCount(3))
expect(pollResponse?.keys).to(contain(.capabilities))
expect(pollResponse?.keys).to(contain(.roomPollInfo("test1", 0)))
expect(pollResponse?.keys).to(contain(.roomMessagesRecent("test1")))
expect(pollResponse?[.capabilities]?.0).to(beAKindOf(TestResponseInfo.self))
expect(response?.values).to(haveCount(3))
expect(response?.keys).to(contain(.capabilities))
expect(response?.keys).to(contain(.roomPollInfo("testRoom", 0)))
expect(response?.keys).to(contain(.roomMessagesRecent("testRoom")))
expect(response?[.capabilities]?.0).to(beAKindOf(TestResponseInfo.self))
// Validate request data
let requestData: TestApi.RequestData? = (pollResponse?[.capabilities]?.0 as? TestResponseInfo)?.requestData
let requestData: TestApi.RequestData? = (response?[.capabilities]?.0 as? TestResponseInfo)?.requestData
expect(requestData?.urlString).to(equal("testServer/batch"))
expect(requestData?.httpMethod).to(equal("POST"))
expect(requestData?.server).to(equal("testServer"))
expect(requestData?.publicKey).to(equal("7aecdcade88d881d2327ab011afd2e04c2ec6acffc9e9df45aaf78a151bd2f7d"))
}
func testPollReturnsAnErrorWhenGivenNoData() throws {
var response: [Endpoint: (OnionRequestResponseInfoType, Codable)]? = nil
var error: Error? = nil
OpenGroupAPIV2.poll("testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(error?.localizedDescription)
.toEventually(
equal(OpenGroupAPIV2.Error.parsingFailed.localizedDescription),
timeout: .milliseconds(100)
)
expect(response).to(beNil())
}
func testPollReturnsAnErrorWhenGivenInvalidData() throws {
class LocalTestApi: TestApi {
override class var mockResponse: Data? { return Data() }
}
dependencies = dependencies.with(api: LocalTestApi.self)
var response: [Endpoint: (OnionRequestResponseInfoType, Codable)]? = nil
var error: Error? = nil
OpenGroupAPIV2.poll("testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(error?.localizedDescription)
.toEventually(
equal(OpenGroupAPIV2.Error.parsingFailed.localizedDescription),
timeout: .milliseconds(100)
)
expect(response).to(beNil())
}
func testPollReturnsAnErrorWhenGivenAnEmptyResponse() throws {
class LocalTestApi: TestApi {
override class var mockResponse: Data? { return "[]".data(using: .utf8) }
}
dependencies = dependencies.with(api: LocalTestApi.self)
var response: [Endpoint: (OnionRequestResponseInfoType, Codable)]? = nil
var error: Error? = nil
OpenGroupAPIV2.poll("testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(error?.localizedDescription)
.toEventually(
equal(OpenGroupAPIV2.Error.parsingFailed.localizedDescription),
timeout: .milliseconds(100)
)
expect(response).to(beNil())
}
func testPollReturnsAnErrorWhenGivenAnObjectResponse() throws {
class LocalTestApi: TestApi {
override class var mockResponse: Data? { return "{}".data(using: .utf8) }
}
dependencies = dependencies.with(api: LocalTestApi.self)
var response: [Endpoint: (OnionRequestResponseInfoType, Codable)]? = nil
var error: Error? = nil
OpenGroupAPIV2.poll("testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(error?.localizedDescription)
.toEventually(
equal(OpenGroupAPIV2.Error.parsingFailed.localizedDescription),
timeout: .milliseconds(100)
)
expect(response).to(beNil())
}
func testPollReturnsAnErrorWhenGivenAnDifferentNumberOfResponses() throws {
class LocalTestApi: TestApi {
override class var mockResponse: Data? {
let responses: [Data] = [
try! JSONEncoder().encode(
OpenGroupAPIV2.BatchSubResponse(
code: 200,
headers: [:],
body: OpenGroupAPIV2.Capabilities(capabilities: [], missing: nil)
)
),
try! JSONEncoder().encode(
OpenGroupAPIV2.BatchSubResponse(
code: 200,
headers: [:],
body: try! JSONDecoder().decode(OpenGroupAPIV2.RoomPollInfo.self, from: "{}".data(using: .utf8)!)
)
)
]
return "[\(responses.map { String(data: $0, encoding: .utf8)! }.joined(separator: ","))]".data(using: .utf8)
}
}
dependencies = dependencies.with(api: LocalTestApi.self)
var response: [Endpoint: (OnionRequestResponseInfoType, Codable)]? = nil
var error: Error? = nil
OpenGroupAPIV2.poll("testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(error?.localizedDescription)
.toEventually(
equal(OpenGroupAPIV2.Error.parsingFailed.localizedDescription),
timeout: .milliseconds(100)
)
expect(response).to(beNil())
}
func testPollReturnsAnErrorWhenGivenAnUnexpectedResponse() throws {
class LocalTestApi: TestApi {
override class var mockResponse: Data? {
let responses: [Data] = [
try! JSONEncoder().encode(
OpenGroupAPIV2.BatchSubResponse(
code: 200,
headers: [:],
body: OpenGroupAPIV2.PinnedMessage(id: 1, pinnedAt: 1, pinnedBy: "")
)
),
try! JSONEncoder().encode(
OpenGroupAPIV2.BatchSubResponse(
code: 200,
headers: [:],
body: OpenGroupAPIV2.PinnedMessage(id: 1, pinnedAt: 1, pinnedBy: "")
)
),
try! JSONEncoder().encode(
OpenGroupAPIV2.BatchSubResponse(
code: 200,
headers: [:],
body: OpenGroupAPIV2.PinnedMessage(id: 1, pinnedAt: 1, pinnedBy: "")
)
)
]
return "[\(responses.map { String(data: $0, encoding: .utf8)! }.joined(separator: ","))]".data(using: .utf8)
}
}
dependencies = dependencies.with(api: LocalTestApi.self)
var response: [Endpoint: (OnionRequestResponseInfoType, Codable)]? = nil
var error: Error? = nil
OpenGroupAPIV2.poll("testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(error?.localizedDescription)
.toEventually(
equal(OpenGroupAPIV2.Error.parsingFailed.localizedDescription),
timeout: .milliseconds(100)
)
expect(response).to(beNil())
}
// MARK: - Files
func testItDoesNotAddAFileNameHeaderWhenNotProvided() throws {
class LocalTestApi: TestApi {
override class var mockResponse: Data? {
return try! JSONEncoder().encode(FileUploadResponse(id: 1))
}
}
dependencies = dependencies.with(api: LocalTestApi.self)
var response: (OnionRequestResponseInfoType, FileUploadResponse)? = nil
var error: Error? = nil
OpenGroupAPIV2.uploadFile([], to: "testRoom", on: "testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(response)
.toEventuallyNot(
beNil(),
timeout: .milliseconds(100)
)
expect(error?.localizedDescription).to(beNil())
// Validate signature headers
let requestData: TestApi.RequestData? = (response?.0 as? TestResponseInfo)?.requestData
expect(requestData?.urlString).to(equal("testServer/room/testRoom/file"))
expect(requestData?.httpMethod).to(equal("POST"))
expect(requestData?.headers).to(haveCount(4))
expect(requestData?.headers.keys).toNot(contain(Header.fileName.rawValue))
}
func testItAddsAFileNameHeaderWhenProvided() throws {
class LocalTestApi: TestApi {
override class var mockResponse: Data? {
return try! JSONEncoder().encode(FileUploadResponse(id: 1))
}
}
dependencies = dependencies.with(api: LocalTestApi.self)
var response: (OnionRequestResponseInfoType, FileUploadResponse)? = nil
var error: Error? = nil
OpenGroupAPIV2.uploadFile([], fileName: "TestFileName", to: "testRoom", on: "testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(response)
.toEventuallyNot(
beNil(),
timeout: .milliseconds(100)
)
expect(error?.localizedDescription).to(beNil())
// Validate signature headers
let requestData: TestApi.RequestData? = (response?.0 as? TestResponseInfo)?.requestData
expect(requestData?.urlString).to(equal("testServer/room/testRoom/file"))
expect(requestData?.httpMethod).to(equal("POST"))
expect(requestData?.headers).to(haveCount(5))
expect(requestData?.headers[Header.fileName.rawValue]).to(equal("TestFileName"))
}
// MARK: - Authentication
func testItSignsTheRequestCorrectly() throws {
class TestApi1: TestApi {
class LocalTestApi: TestApi {
override class var mockResponse: Data? {
return try! JSONEncoder().encode([OpenGroupAPIV2.Room]())
}
}
dependencies = dependencies.with(api: LocalTestApi.self)
var response: (OnionRequestResponseInfoType, [OpenGroupAPIV2.Room])? = nil
var error: Error? = nil
OpenGroupAPIV2.rooms(for: "testServer", through: TestApi1.self, using: testStorage, nonceGenerator: TestNonceGenerator(), date: Date(timeIntervalSince1970: 1234567890))
.map { result -> (OnionRequestResponseInfoType, [OpenGroupAPIV2.Room]) in
response = result
return result
}
OpenGroupAPIV2.rooms(for: "testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(response)
.toEventuallyNot(
beNil(),
timeout: .milliseconds(10000)
timeout: .milliseconds(100)
)
expect(error?.localizedDescription).to(beNil())
// Validate signature headers
let requestData: TestApi.RequestData? = (response?.0 as? TestResponseInfo)?.requestData
@ -217,4 +440,128 @@ class OpenGroupAPIV2Tests: XCTestCase {
expect(requestData?.headers[Header.sogsHash.rawValue]).to(equal("fxqLy5ZDWCsLQpwLw0Dax+4xe7cG2vPRk1NlHORIm0DPd3o9UA24KLZY"))
expect(requestData?.headers[Header.sogsTimestamp.rawValue]).to(equal("1234567890"))
}
func testItFailsToSignIfTheServerPublicKeyIsInvalid() throws {
testStorage.mockData[.openGroupPublicKeys] = ["testServer": ""]
var response: Any? = nil
var error: Error? = nil
OpenGroupAPIV2.rooms(for: "testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(error?.localizedDescription)
.toEventually(
equal(OpenGroupAPIV2.Error.signingFailed.localizedDescription),
timeout: .milliseconds(100)
)
expect(response).to(beNil())
}
func testItFailsToSignIfThereIsNoUserKeyPair() throws {
testStorage.mockData[.userKeyPair] = nil
var response: Any? = nil
var error: Error? = nil
OpenGroupAPIV2.rooms(for: "testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(error?.localizedDescription)
.toEventually(
equal(OpenGroupAPIV2.Error.signingFailed.localizedDescription),
timeout: .milliseconds(100)
)
expect(response).to(beNil())
}
func testItFailsToSignIfTheSharedSecretDoesNotGetGenerated() throws {
class InvalidSodium: SodiumType {
func getGenericHash() -> GenericHashType { return Sodium().genericHash }
func sharedSecret(_ firstKeyBytes: [UInt8], _ secondKeyBytes: [UInt8]) -> Sodium.SharedSecret? { return nil }
}
dependencies = dependencies.with(sodium: InvalidSodium())
var response: Any? = nil
var error: Error? = nil
OpenGroupAPIV2.rooms(for: "testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(error?.localizedDescription)
.toEventually(
equal(OpenGroupAPIV2.Error.signingFailed.localizedDescription),
timeout: .milliseconds(100)
)
expect(response).to(beNil())
}
func testItFailsToSignIfTheIntermediateHashDoesNotGetGenerated() throws {
class InvalidGenericHash: GenericHashType {
func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes? {
return nil
}
}
dependencies = dependencies.with(genericHash: InvalidGenericHash())
var response: Any? = nil
var error: Error? = nil
OpenGroupAPIV2.rooms(for: "testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(error?.localizedDescription)
.toEventually(
equal(OpenGroupAPIV2.Error.signingFailed.localizedDescription),
timeout: .milliseconds(100)
)
expect(response).to(beNil())
}
func testItFailsToSignIfTheSecretHashDoesNotGetGenerated() throws {
class InvalidSecondGenericHash: GenericHashType {
static var didSucceedOnce: Bool = false
func hashSaltPersonal(message: Bytes, outputLength: Int, key: Bytes?, salt: Bytes, personal: Bytes) -> Bytes? {
if !InvalidSecondGenericHash.didSucceedOnce {
InvalidSecondGenericHash.didSucceedOnce = true
return Data().bytes
}
return nil
}
}
dependencies = dependencies.with(genericHash: InvalidSecondGenericHash())
var response: Any? = nil
var error: Error? = nil
OpenGroupAPIV2.rooms(for: "testServer", using: dependencies)
.get { result in response = result }
.catch { requestError in error = requestError }
.retainUntilComplete()
expect(error?.localizedDescription)
.toEventually(
equal(OpenGroupAPIV2.Error.signingFailed.localizedDescription),
timeout: .milliseconds(100)
)
expect(response).to(beNil())
}
}

@ -13,6 +13,7 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
case allV2OpenGroups
case openGroupPublicKeys
case userKeyPair
case openGroupImage
}
typealias Key = DataKey
@ -112,3 +113,10 @@ class TestStorage: SessionMessagingKitStorageProtocol, Mockable {
func setAttachmentState(to state: TSAttachmentPointerState, for pointer: TSAttachmentPointer, associatedWith tsIncomingMessageID: String, using transaction: Any) {}
func persist(_ stream: TSAttachmentStream, associatedWith tsIncomingMessageID: String, using transaction: Any) {}
}
// MARK: - SessionMessagingKitOpenGroupStorageProtocol
extension TestStorage: SessionMessagingKitOpenGroupStorageProtocol {
func getOpenGroupImage(for room: String, on server: String) -> Data? { return (mockData[.openGroupImage] as? Data) }
func setOpenGroupImage(to data: Data, for room: String, on server: String, using transaction: Any) {}
}

@ -4,7 +4,7 @@ import Foundation
extension String {
public func dataFromHex() -> Data? {
guard (self.count % 2) == 0 else { return nil }
guard self.count > 0 && (self.count % 2) == 0 else { return nil }
let chars = self.map { $0 }
let bytes: [UInt8] = stride(from: 0, to: chars.count, by: 2)

Loading…
Cancel
Save