diff --git a/Core/XMPPFramework.h b/Core/XMPPFramework.h index cb5326d31b..cbb94af320 100644 --- a/Core/XMPPFramework.h +++ b/Core/XMPPFramework.h @@ -131,6 +131,7 @@ #import "XMPPStreamManagementMemoryStorage.h" #import "XMPPStreamManagementStanzas.h" #import "XMPPStreamManagement.h" +#import "XMPPManagedMessaging.h" #import "XMPPAutoPing.h" #import "XMPPPing.h" #import "XMPPAutoTime.h" diff --git a/Extensions/XEP-0198/Managed Messaging/XMPPManagedMessaging.h b/Extensions/XEP-0198/Managed Messaging/XMPPManagedMessaging.h new file mode 100644 index 0000000000..ff64d648e9 --- /dev/null +++ b/Extensions/XEP-0198/Managed Messaging/XMPPManagedMessaging.h @@ -0,0 +1,34 @@ +#import "XMPPModule.h" + +NS_ASSUME_NONNULL_BEGIN + +@class XMPPMessage; + +/** + A module working in tandem with @c XMPPStreamManagement to trace outgoing message stream acknowledgements. + + This module only monitors messages with @c elementID assigned. The rationale behind this is that any potential retransmissions + of messages without IDs will cause deduplication issues on the receiving end. + */ +@interface XMPPManagedMessaging : XMPPModule + +@end + +/// A protocol defining @c XMPPManagedMessaging module delegate API. +@protocol XMPPManagedMessagingDelegate + +@optional + +/// Notifies the delegate that a message subject to monitoring has been sent in the stream. +- (void)xmppManagedMessaging:(XMPPManagedMessaging *)sender didBeginMonitoringOutgoingMessage:(XMPPMessage *)message; + +/// Notifies the delegate that @c XMPPStreamManagement module has received server acknowledgement for sent messages with given IDs. +- (void)xmppManagedMessaging:(XMPPManagedMessaging *)sender didConfirmSentMessagesWithIDs:(NSArray *)messageIDs; + +/// @brief Notifies the delegate that post-reauthentication message acknowledgement processing is finished. +/// At this point, no more acknowledgements for currently monitored messages are to be expected. +- (void)xmppManagedMessagingDidFinishProcessingPreviousStreamConfirmations:(XMPPManagedMessaging *)sender; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Extensions/XEP-0198/Managed Messaging/XMPPManagedMessaging.m b/Extensions/XEP-0198/Managed Messaging/XMPPManagedMessaging.m new file mode 100644 index 0000000000..c9437662dc --- /dev/null +++ b/Extensions/XEP-0198/Managed Messaging/XMPPManagedMessaging.m @@ -0,0 +1,112 @@ +#import "XMPPManagedMessaging.h" +#import "XMPPStreamManagement.h" +#import "XMPPLogging.h" + +// Log levels: off, error, warn, info, verbose +// Log flags: trace +#if DEBUG +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE; +#else +static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; +#endif + +static NSString * const XMPPManagedMessagingURLScheme = @"xmppmanagedmessage"; + +@implementation XMPPManagedMessaging + +- (void)didActivate +{ + XMPPLogTrace(); + [self.xmppStream autoAddDelegate:self delegateQueue:self.moduleQueue toModulesOfClass:[XMPPStreamManagement class]]; +} + +- (void)willDeactivate +{ + XMPPLogTrace(); + [self.xmppStream removeAutoDelegate:self delegateQueue:self.moduleQueue fromModulesOfClass:[XMPPStreamManagement class]]; +} + +- (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message +{ + XMPPLogTrace(); + + if (![message elementID]) { + XMPPLogWarn(@"Sent message without an ID excluded from managed messaging"); + return; + } + + XMPPLogInfo(@"Registering message with ID=%@ for managed messaging", [message elementID]); + [multicastDelegate xmppManagedMessaging:self didBeginMonitoringOutgoingMessage:message]; +} + +- (id)xmppStreamManagement:(XMPPStreamManagement *)sender stanzaIdForSentElement:(XMPPElement *)element +{ + if (![element isKindOfClass:[XMPPMessage class]] || ![element elementID]) { + return nil; + } + + NSURLComponents *managedMessageURLComponents = [[NSURLComponents alloc] init]; + managedMessageURLComponents.scheme = XMPPManagedMessagingURLScheme; + managedMessageURLComponents.path = [element elementID]; + + return managedMessageURLComponents.URL; +} + +- (void)xmppStreamManagement:(XMPPStreamManagement *)sender didReceiveAckForStanzaIds:(NSArray *)stanzaIds +{ + XMPPLogTrace(); + + NSArray *resumeStanzaIDs; + [sender didResumeWithAckedStanzaIds:&resumeStanzaIDs serverResponse:nil]; + if ([resumeStanzaIDs isEqualToArray:stanzaIds]) { + // Handled in -xmppStreamDidAuthenticate: + return; + } + + [self processStreamManagementAcknowledgementForStanzaIDs:stanzaIds]; +} + +- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender +{ + XMPPLogTrace(); + + dispatch_group_t stanzaAcknowledgementGroup = dispatch_group_create(); + + [sender enumerateModulesOfClass:[XMPPStreamManagement class] withBlock:^(XMPPModule *module, NSUInteger idx, BOOL *stop) { + NSArray *acknowledgedStanzaIDs; + [(XMPPStreamManagement *)module didResumeWithAckedStanzaIds:&acknowledgedStanzaIDs serverResponse:nil]; + if (acknowledgedStanzaIDs.count == 0) { + return; + } + + dispatch_group_async(stanzaAcknowledgementGroup, self.moduleQueue, ^{ + [self processStreamManagementAcknowledgementForStanzaIDs:acknowledgedStanzaIDs]; + }); + }]; + + dispatch_group_notify(stanzaAcknowledgementGroup, self.moduleQueue, ^{ + [multicastDelegate xmppManagedMessagingDidFinishProcessingPreviousStreamConfirmations:self]; + }); +} + +- (void)processStreamManagementAcknowledgementForStanzaIDs:(NSArray *)stanzaIDs +{ + NSMutableArray *managedMessageIDs = [NSMutableArray array]; + for (id stanzaID in stanzaIDs) { + if (![stanzaID isKindOfClass:[NSURL class]] || ![((NSURL *)stanzaID).scheme isEqualToString:XMPPManagedMessagingURLScheme]) { + continue; + } + // Extracting path directly from NSURL does not work if it doesn't start with "/" + NSURLComponents *managedMessageURLComponents = [[NSURLComponents alloc] initWithURL:stanzaID resolvingAgainstBaseURL:NO]; + [managedMessageIDs addObject:managedMessageURLComponents.path]; + } + + if (managedMessageIDs.count == 0) { + return; + } + + XMPPLogInfo(@"Confirming managed messages with IDs={%@}", [managedMessageIDs componentsJoinedByString:@","]); + [multicastDelegate xmppManagedMessaging:self didConfirmSentMessagesWithIDs:managedMessageIDs]; +} + +@end diff --git a/XMPPFramework.xcodeproj/project.pbxproj b/XMPPFramework.xcodeproj/project.pbxproj index a0b10483fd..1092ab2898 100644 --- a/XMPPFramework.xcodeproj/project.pbxproj +++ b/XMPPFramework.xcodeproj/project.pbxproj @@ -923,6 +923,12 @@ DD1E733A1ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E73391ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h */; settings = {ATTRIBUTES = (Public, ); }; }; DD1E733B1ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E73391ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h */; settings = {ATTRIBUTES = (Public, ); }; }; DD1E733C1ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h in Headers */ = {isa = PBXBuildFile; fileRef = DD1E73391ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD2AD6E81F84B49200E0FED2 /* XMPPManagedMessaging.h in Headers */ = {isa = PBXBuildFile; fileRef = DD2AD6DD1F84B49200E0FED2 /* XMPPManagedMessaging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD2AD6E91F84B49200E0FED2 /* XMPPManagedMessaging.h in Headers */ = {isa = PBXBuildFile; fileRef = DD2AD6DD1F84B49200E0FED2 /* XMPPManagedMessaging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD2AD6EA1F84B49200E0FED2 /* XMPPManagedMessaging.h in Headers */ = {isa = PBXBuildFile; fileRef = DD2AD6DD1F84B49200E0FED2 /* XMPPManagedMessaging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DD2AD6EB1F84B49200E0FED2 /* XMPPManagedMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = DD2AD6DE1F84B49200E0FED2 /* XMPPManagedMessaging.m */; }; + DD2AD6EC1F84B49200E0FED2 /* XMPPManagedMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = DD2AD6DE1F84B49200E0FED2 /* XMPPManagedMessaging.m */; }; + DD2AD6ED1F84B49200E0FED2 /* XMPPManagedMessaging.m in Sources */ = {isa = PBXBuildFile; fileRef = DD2AD6DE1F84B49200E0FED2 /* XMPPManagedMessaging.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1514,6 +1520,8 @@ DD1E73311ED885FD009B529B /* XMPPRoomLightCoreDataStorage+XEP_0313.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XMPPRoomLightCoreDataStorage+XEP_0313.h"; sourceTree = ""; }; DD1E73321ED885FD009B529B /* XMPPRoomLightCoreDataStorage+XEP_0313.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "XMPPRoomLightCoreDataStorage+XEP_0313.m"; sourceTree = ""; }; DD1E73391ED88622009B529B /* XMPPRoomLightCoreDataStorageProtected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMPPRoomLightCoreDataStorageProtected.h; sourceTree = ""; }; + DD2AD6DD1F84B49200E0FED2 /* XMPPManagedMessaging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XMPPManagedMessaging.h; sourceTree = ""; }; + DD2AD6DE1F84B49200E0FED2 /* XMPPManagedMessaging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMPPManagedMessaging.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2267,6 +2275,7 @@ D9DCD2001E6250930010D1C7 /* XEP-0198 */ = { isa = PBXGroup; children = ( + DD2AD6D91F84B49200E0FED2 /* Managed Messaging */, D9DCD2011E6250930010D1C7 /* Memory Storage */, D9DCD2041E6250930010D1C7 /* Private */, D9DCD2071E6250930010D1C7 /* XMPPStreamManagement.h */, @@ -2533,6 +2542,15 @@ name = Frameworks; sourceTree = ""; }; + DD2AD6D91F84B49200E0FED2 /* Managed Messaging */ = { + isa = PBXGroup; + children = ( + DD2AD6DD1F84B49200E0FED2 /* XMPPManagedMessaging.h */, + DD2AD6DE1F84B49200E0FED2 /* XMPPManagedMessaging.m */, + ); + path = "Managed Messaging"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -2589,6 +2607,7 @@ D9DCD32A1E6250930010D1C7 /* XMPPIQ+XEP_0357.h in Headers */, D9DCD24D1E6250930010D1C7 /* XMPPCoreDataStorage.h in Headers */, D9DCD24B1E6250930010D1C7 /* XMPPBandwidthMonitor.h in Headers */, + DD2AD6E81F84B49200E0FED2 /* XMPPManagedMessaging.h in Headers */, D9DCD2901E6250930010D1C7 /* XMPPRoomCoreDataStorage.h in Headers */, D9DCD3081E6250930010D1C7 /* XMPPAutoPing.h in Headers */, D9DCD2F61E6250930010D1C7 /* XMPPvCardAvatarModule.h in Headers */, @@ -2750,6 +2769,7 @@ D9DCD4C41E6256D90010D1C7 /* XMPPIQ+XEP_0357.h in Headers */, D9DCD4C51E6256D90010D1C7 /* XMPPCoreDataStorage.h in Headers */, D9DCD4C61E6256D90010D1C7 /* XMPPBandwidthMonitor.h in Headers */, + DD2AD6E91F84B49200E0FED2 /* XMPPManagedMessaging.h in Headers */, D9DCD4C71E6256D90010D1C7 /* XMPPRoomCoreDataStorage.h in Headers */, D9DCD4C81E6256D90010D1C7 /* XMPPAutoPing.h in Headers */, D9DCD4C91E6256D90010D1C7 /* XMPPvCardAvatarModule.h in Headers */, @@ -2911,6 +2931,7 @@ D9DCD6271E6258CF0010D1C7 /* XMPPIQ+XEP_0357.h in Headers */, D9DCD6281E6258CF0010D1C7 /* XMPPCoreDataStorage.h in Headers */, D9DCD6291E6258CF0010D1C7 /* XMPPBandwidthMonitor.h in Headers */, + DD2AD6EA1F84B49200E0FED2 /* XMPPManagedMessaging.h in Headers */, D9DCD62A1E6258CF0010D1C7 /* XMPPRoomCoreDataStorage.h in Headers */, D9DCD62B1E6258CF0010D1C7 /* XMPPAutoPing.h in Headers */, D9DCD62C1E6258CF0010D1C7 /* XMPPvCardAvatarModule.h in Headers */, @@ -3477,6 +3498,7 @@ D9DCD3051E6250930010D1C7 /* XMPPStreamManagementStanzas.m in Sources */, D9DCD2AD1E6250930010D1C7 /* XMPPvCard.xcdatamodeld in Sources */, D9DCD2D71E6250930010D1C7 /* NSDate+XMPPDateTimeProfiles.m in Sources */, + DD2AD6EB1F84B49200E0FED2 /* XMPPManagedMessaging.m in Sources */, D9DCD3381E6250930010D1C7 /* XMPPRoomLight.m in Sources */, 0D44BB111E5370ED000930E0 /* XMPPElement.m in Sources */, 0D44BB6A1E537110000930E0 /* GCDMulticastDelegate.m in Sources */, @@ -3629,6 +3651,7 @@ D9DCD4711E6256D90010D1C7 /* XMPPStreamManagementStanzas.m in Sources */, D9DCD4721E6256D90010D1C7 /* XMPPvCard.xcdatamodeld in Sources */, D9DCD4731E6256D90010D1C7 /* NSDate+XMPPDateTimeProfiles.m in Sources */, + DD2AD6EC1F84B49200E0FED2 /* XMPPManagedMessaging.m in Sources */, D9DCD4741E6256D90010D1C7 /* XMPPRoomLight.m in Sources */, D9DCD4751E6256D90010D1C7 /* XMPPElement.m in Sources */, D9DCD4761E6256D90010D1C7 /* GCDMulticastDelegate.m in Sources */, @@ -3781,6 +3804,7 @@ D9DCD5D41E6258CF0010D1C7 /* XMPPStreamManagementStanzas.m in Sources */, D9DCD5D51E6258CF0010D1C7 /* XMPPvCard.xcdatamodeld in Sources */, D9DCD5D61E6258CF0010D1C7 /* NSDate+XMPPDateTimeProfiles.m in Sources */, + DD2AD6ED1F84B49200E0FED2 /* XMPPManagedMessaging.m in Sources */, D9DCD5D71E6258CF0010D1C7 /* XMPPRoomLight.m in Sources */, D9DCD5D81E6258CF0010D1C7 /* XMPPElement.m in Sources */, D9DCD5D91E6258CF0010D1C7 /* GCDMulticastDelegate.m in Sources */, diff --git a/Xcode/Testing-Carthage/XMPPFrameworkTests.xcodeproj/project.pbxproj b/Xcode/Testing-Carthage/XMPPFrameworkTests.xcodeproj/project.pbxproj index 9106cb5573..021cb0726b 100644 --- a/Xcode/Testing-Carthage/XMPPFrameworkTests.xcodeproj/project.pbxproj +++ b/Xcode/Testing-Carthage/XMPPFrameworkTests.xcodeproj/project.pbxproj @@ -61,6 +61,9 @@ D9DCD70E1E625C560010D1C7 /* OMEMOTestStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = D99C5E0C1D99C48100FB068A /* OMEMOTestStorage.m */; }; D9DCD7191E625CAE0010D1C7 /* XMPPFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D9DCD6B01E625A9B0010D1C7 /* XMPPFramework.framework */; }; D9E35E701D90B894002E7CF7 /* OMEMOElementTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D9E35E6F1D90B894002E7CF7 /* OMEMOElementTests.m */; }; + DDA11A4C1F8518AE00591D1B /* XMPPManagedMessagingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DDA11A4B1F8518AE00591D1B /* XMPPManagedMessagingTests.m */; }; + DDA11A4D1F8518BA00591D1B /* XMPPManagedMessagingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DDA11A4B1F8518AE00591D1B /* XMPPManagedMessagingTests.m */; }; + DDA11A4E1F8518BB00591D1B /* XMPPManagedMessagingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DDA11A4B1F8518AE00591D1B /* XMPPManagedMessagingTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -133,6 +136,7 @@ D9DCD3EC1E6255E10010D1C7 /* XMPPFrameworkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XMPPFrameworkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D9DCD7151E625C560010D1C7 /* XMPPFrameworkTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = XMPPFrameworkTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D9E35E6F1D90B894002E7CF7 /* OMEMOElementTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OMEMOElementTests.m; path = "../Testing-Shared/OMEMOElementTests.m"; sourceTree = SOURCE_ROOT; }; + DDA11A4B1F8518AE00591D1B /* XMPPManagedMessagingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = XMPPManagedMessagingTests.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -202,6 +206,7 @@ D973A0791D2F18040096F3ED /* XMPPStorageHintTests.m */, D973A07A1D2F18040096F3ED /* XMPPURITests.m */, D973A07B1D2F18040096F3ED /* XMPPvCardTests.m */, + DDA11A4B1F8518AE00591D1B /* XMPPManagedMessagingTests.m */, 63F50D971C60208200CA0201 /* Info.plist */, ); name = XMPPFrameworkTests; @@ -389,6 +394,7 @@ D97509281D9C82DB002E6F51 /* OMEMOServerTests.m in Sources */, D99C5E0D1D99C48100FB068A /* OMEMOModuleTests.m in Sources */, D973A0861D2F18040096F3ED /* XMPPURITests.m in Sources */, + DDA11A4C1F8518AE00591D1B /* XMPPManagedMessagingTests.m in Sources */, D973A07F1D2F18040096F3ED /* XMPPMessageArchiveManagementTests.m in Sources */, D973A07E1D2F18040096F3ED /* XMPPHTTPFileUploadTests.m in Sources */, D973A0821D2F18040096F3ED /* XMPPPushTests.swift in Sources */, @@ -413,6 +419,7 @@ D9DCD3DB1E6255E10010D1C7 /* OMEMOServerTests.m in Sources */, D9DCD3DC1E6255E10010D1C7 /* OMEMOModuleTests.m in Sources */, D9DCD3DD1E6255E10010D1C7 /* XMPPURITests.m in Sources */, + DDA11A4D1F8518BA00591D1B /* XMPPManagedMessagingTests.m in Sources */, D9DCD3DE1E6255E10010D1C7 /* XMPPMessageArchiveManagementTests.m in Sources */, D9DCD3DF1E6255E10010D1C7 /* XMPPHTTPFileUploadTests.m in Sources */, D9DCD3E01E6255E10010D1C7 /* XMPPPushTests.swift in Sources */, @@ -437,6 +444,7 @@ D9DCD7041E625C560010D1C7 /* OMEMOServerTests.m in Sources */, D9DCD7051E625C560010D1C7 /* OMEMOModuleTests.m in Sources */, D9DCD7061E625C560010D1C7 /* XMPPURITests.m in Sources */, + DDA11A4E1F8518BB00591D1B /* XMPPManagedMessagingTests.m in Sources */, D9DCD7071E625C560010D1C7 /* XMPPMessageArchiveManagementTests.m in Sources */, D9DCD7081E625C560010D1C7 /* XMPPHTTPFileUploadTests.m in Sources */, D9DCD7091E625C560010D1C7 /* XMPPPushTests.swift in Sources */, diff --git a/Xcode/Testing-Shared/XMPPManagedMessagingTests.m b/Xcode/Testing-Shared/XMPPManagedMessagingTests.m new file mode 100644 index 0000000000..d0768cc673 --- /dev/null +++ b/Xcode/Testing-Shared/XMPPManagedMessagingTests.m @@ -0,0 +1,214 @@ +// +// XMPPManagedMessagingTests.m +// XMPPFrameworkTests +// +// Created by Piotr Wegrzynek on 04/10/2017. +// + +#import +#import "XMPPMockStream.h" + +@class XMPPFakeStreamManagement; + +@interface XMPPManagedMessagingTests : XCTestCase + +@property (nonatomic, strong) XMPPMockStream *mockStream; +@property (nonatomic, strong) XMPPFakeStreamManagement *fakeStreamManagement; +@property (nonatomic, strong) XMPPManagedMessaging *managedMessaging; +@property (nonatomic, strong) XCTestExpectation *delegateCallbackExpectation; + +@end + +@interface XMPPFakeStreamManagement : XMPPStreamManagement + +@property (nonatomic, copy) NSArray *resumeStanzaIDs; + +- (void)fakeReceivingAckForStanzaIDs:(NSArray *)stanzaIDs; + +@end + +@implementation XMPPManagedMessagingTests + +- (void)setUp +{ + [super setUp]; + + self.mockStream = [[XMPPMockStream alloc] init]; + + self.fakeStreamManagement = [[XMPPFakeStreamManagement alloc] initWithStorage:[[XMPPStreamManagementMemoryStorage alloc] init]]; + + self.managedMessaging = [[XMPPManagedMessaging alloc] init]; + [self.managedMessaging addDelegate:self delegateQueue:dispatch_get_main_queue()]; + [self.managedMessaging activate:self.mockStream]; +} + +- (void)testStreamManagementDependency +{ + [self.mockStream addDelegate:self delegateQueue:dispatch_get_main_queue()]; + + self.delegateCallbackExpectation = [self expectationWithDescription:@"Stream management dependency setup expectation"]; + + [self.fakeStreamManagement activate:self.mockStream]; + + [self waitForExpectationsWithTimeout:5 handler:nil]; +} + +- (void)xmppStream:(XMPPStream *)sender didRegisterModule:(id)module +{ + if (module == self.fakeStreamManagement && [[module valueForKey:@"multicastDelegate"] countOfClass:[XMPPManagedMessaging class]] == 1) { + [self.delegateCallbackExpectation fulfill]; + } +} + +- (void)testMessageRegistration +{ + XMPPMessage *message = [[XMPPMessage alloc] initWithXMLString:@"" error:NULL]; + + self.delegateCallbackExpectation = [self expectationWithDescription:@"Message registration delegate callback expectation"]; + + [self.mockStream sendElement:message]; + + [self waitForExpectationsWithTimeout:5 handler:nil]; +} + +- (void)testMessageWithoutIDHandling +{ + XMPPMessage *message = [[XMPPMessage alloc] initWithXMLString:@"" error:NULL]; + + self.delegateCallbackExpectation = [self expectationWithDescription:@"Message without ID registration delegate callback expectation"]; + self.delegateCallbackExpectation.inverted = YES; + + [self.mockStream sendElement:message]; + + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)xmppManagedMessaging:(XMPPManagedMessaging *)sender didBeginMonitoringOutgoingMessage:(XMPPMessage *)message +{ + if (![message elementID] || [[message elementID] isEqualToString:@"elementID"]) { + [self.delegateCallbackExpectation fulfill]; + } +} + +- (void)testStanzaIDAssignment +{ + XMPPMessage *message = [[XMPPMessage alloc] initWithXMLString:@"" error:NULL]; + + __block id messageID; + dispatch_sync(self.managedMessaging.moduleQueue, ^{ + messageID = [(id)self.managedMessaging xmppStreamManagement:self.fakeStreamManagement stanzaIdForSentElement:message]; + }); + + XCTAssertEqualObjects(messageID, [NSURL URLWithString:@"xmppmanagedmessage:elementID"]); +} + +- (void)testBasicMessageAcknowledgement +{ + NSURL *managedMessageURL = [NSURL URLWithString:@"xmppmanagedmessage:elementID"]; + [self.fakeStreamManagement activate:self.mockStream]; + + self.delegateCallbackExpectation = [self expectationWithDescription:@"Basic message acknowledgement delegate callback expectation"]; + + [self.fakeStreamManagement fakeReceivingAckForStanzaIDs:@[managedMessageURL]]; + + [self waitForExpectationsWithTimeout:5 handler:nil]; +} + +- (void)testStreamResumptionMessageAcknowledgement +{ + self.fakeStreamManagement.resumeStanzaIDs = @[[NSURL URLWithString:@"xmppmanagedmessage:elementID"]]; + [self.fakeStreamManagement activate:self.mockStream]; + + self.delegateCallbackExpectation = [self expectationWithDescription:@"Stream resumption message acknowledgement delegate callback expectation"]; + self.delegateCallbackExpectation.expectedFulfillmentCount = 2; + + [[self.mockStream valueForKey:@"multicastDelegate"] xmppStreamDidAuthenticate:self.mockStream]; + + [self waitForExpectationsWithTimeout:5 handler:nil]; +} + +- (void)testStreamResumptionDuplicateMessageAcknowledgementHandling +{ + self.fakeStreamManagement.resumeStanzaIDs = @[[NSURL URLWithString:@"xmppmanagedmessage:elementID"]]; + [self.fakeStreamManagement activate:self.mockStream]; + NSURL *managedMessageURL = [NSURL URLWithString:@"xmppmanagedmessage:elementID"]; + + self.delegateCallbackExpectation = [self expectationWithDescription:@"Stream resumption duplicate message acknowledgement delegate callback expectation"]; + self.delegateCallbackExpectation.inverted = YES; + + [self.fakeStreamManagement fakeReceivingAckForStanzaIDs:@[managedMessageURL]]; + + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testAuxiliaryIQHandling +{ + XMPPIQ *iq = [[XMPPIQ alloc] initWithXMLString:@"" error:NULL]; + [self.fakeStreamManagement activate:self.mockStream]; + + self.delegateCallbackExpectation = [self expectationWithDescription:@"Non-message registration delegate callback expectation"]; + self.delegateCallbackExpectation.inverted = YES; + + [self.mockStream sendElement:iq]; + [self.fakeStreamManagement fakeReceivingAckForStanzaIDs:@[@"elementID"]]; + + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)testAuxiliaryPresenceHandling +{ + XMPPPresence *presence = [[XMPPPresence alloc] initWithXMLString:@"" error:NULL]; + [self.fakeStreamManagement activate:self.mockStream]; + + self.delegateCallbackExpectation = [self expectationWithDescription:@"Non-message registration delegate callback expectation"]; + self.delegateCallbackExpectation.inverted = YES; + + [self.mockStream sendElement:presence]; + [self.fakeStreamManagement fakeReceivingAckForStanzaIDs:@[@"elementID"]]; + + [self waitForExpectationsWithTimeout:1 handler:nil]; +} + +- (void)xmppManagedMessaging:(XMPPManagedMessaging *)sender didConfirmSentMessagesWithIDs:(NSArray *)messageIDs +{ + if ([messageIDs isEqualToArray:@[@"elementID"]]) { + [self.delegateCallbackExpectation fulfill]; + } +} + +- (void)xmppManagedMessagingDidFinishProcessingPreviousStreamConfirmations:(XMPPManagedMessaging *)sender +{ + [self.delegateCallbackExpectation fulfill]; +} + +- (void)fakeIncomingManagedMessagingDelegateCallbackWithBlock:(void (^)(id managedMessaging))block +{ + dispatch_async(self.managedMessaging.moduleQueue, ^{ + block(self.managedMessaging); + }); +} + +@end + +@implementation XMPPFakeStreamManagement + +- (Class)class +{ + // Required by XMPPStream auto delegates feature + return [XMPPStreamManagement class]; +} + +- (void)fakeReceivingAckForStanzaIDs:(NSArray *)stanzaIDs +{ + dispatch_async(self.moduleQueue, ^{ + [multicastDelegate xmppStreamManagement:self didReceiveAckForStanzaIds:stanzaIDs]; + }); +} + +- (BOOL)didResumeWithAckedStanzaIds:(NSArray *__autoreleasing _Nullable *)stanzaIdsPtr serverResponse:(NSXMLElement *__autoreleasing _Nullable *)responsePtr +{ + *stanzaIdsPtr = self.resumeStanzaIDs; + return YES; +} + +@end diff --git a/Xcode/Testing-iOS/XMPPFrameworkTests.xcodeproj/project.pbxproj b/Xcode/Testing-iOS/XMPPFrameworkTests.xcodeproj/project.pbxproj index 393a79e644..8d5f86381f 100644 --- a/Xcode/Testing-iOS/XMPPFrameworkTests.xcodeproj/project.pbxproj +++ b/Xcode/Testing-iOS/XMPPFrameworkTests.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ D99C5E0E1D99C48100FB068A /* OMEMOTestStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = D99C5E0C1D99C48100FB068A /* OMEMOTestStorage.m */; }; D9E35E701D90B894002E7CF7 /* OMEMOElementTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D9E35E6F1D90B894002E7CF7 /* OMEMOElementTests.m */; }; DD1E732C1ED86B7D009B529B /* XMPPPubSubTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DD1E732B1ED86B7D009B529B /* XMPPPubSubTests.m */; }; + DD2AD6F01F84CB0400E0FED2 /* XMPPManagedMessagingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DD2AD6EF1F84CB0400E0FED2 /* XMPPManagedMessagingTests.m */; }; FDD2AB232C05507F2045FFFC /* Pods_XMPPFrameworkTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD0B17267211A912DE2098E /* Pods_XMPPFrameworkTests.framework */; }; /* End PBXBuildFile section */ @@ -55,6 +56,7 @@ D99C5E0C1D99C48100FB068A /* OMEMOTestStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OMEMOTestStorage.m; path = "../../Testing-Shared/OMEMOTestStorage.m"; sourceTree = ""; }; D9E35E6F1D90B894002E7CF7 /* OMEMOElementTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OMEMOElementTests.m; path = "../../Testing-Shared/OMEMOElementTests.m"; sourceTree = ""; }; DD1E732B1ED86B7D009B529B /* XMPPPubSubTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPPubSubTests.m; path = "../../Testing-Shared/XMPPPubSubTests.m"; sourceTree = ""; }; + DD2AD6EF1F84CB0400E0FED2 /* XMPPManagedMessagingTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = XMPPManagedMessagingTests.m; path = "../../Testing-Shared/XMPPManagedMessagingTests.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -108,6 +110,7 @@ D973A07A1D2F18040096F3ED /* XMPPURITests.m */, D973A07B1D2F18040096F3ED /* XMPPvCardTests.m */, DD1E732B1ED86B7D009B529B /* XMPPPubSubTests.m */, + DD2AD6EF1F84CB0400E0FED2 /* XMPPManagedMessagingTests.m */, 63F50D971C60208200CA0201 /* Info.plist */, D973A06E1D2F18030096F3ED /* XMPPFrameworkTests-Bridging-Header.h */, ); @@ -255,6 +258,7 @@ D973A07D1D2F18040096F3ED /* EncodeDecodeTest.m in Sources */, D973A0831D2F18040096F3ED /* XMPPRoomLightCoreDataStorageTests.m in Sources */, D973A0801D2F18040096F3ED /* XMPPMockStream.m in Sources */, + DD2AD6F01F84CB0400E0FED2 /* XMPPManagedMessagingTests.m in Sources */, D973A0841D2F18040096F3ED /* XMPPRoomLightTests.m in Sources */, D97509281D9C82DB002E6F51 /* OMEMOServerTests.m in Sources */, D99C5E0D1D99C48100FB068A /* OMEMOModuleTests.m in Sources */, diff --git a/Xcode/Testing-macOS/XMPPFrameworkTests.xcodeproj/project.pbxproj b/Xcode/Testing-macOS/XMPPFrameworkTests.xcodeproj/project.pbxproj index 3c8030996f..b387025179 100644 --- a/Xcode/Testing-macOS/XMPPFrameworkTests.xcodeproj/project.pbxproj +++ b/Xcode/Testing-macOS/XMPPFrameworkTests.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ D99C5E091D95EBA100FB068A /* OMEMOTestStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = D99C5E081D95EBA100FB068A /* OMEMOTestStorage.m */; }; D9E35E6E1D90B2C5002E7CF7 /* OMEMOElementTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D9E35E6D1D90B2C5002E7CF7 /* OMEMOElementTests.m */; }; D9F20D011D836080002A8D6F /* OMEMOModuleTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D9F20D001D836080002A8D6F /* OMEMOModuleTests.m */; }; + DDA11A491F8517C300591D1B /* XMPPManagedMessagingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DDA11A481F8517C200591D1B /* XMPPManagedMessagingTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -52,6 +53,7 @@ D99C5E081D95EBA100FB068A /* OMEMOTestStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OMEMOTestStorage.m; path = "../../Testing-Shared/OMEMOTestStorage.m"; sourceTree = ""; }; D9E35E6D1D90B2C5002E7CF7 /* OMEMOElementTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OMEMOElementTests.m; path = "../../Testing-Shared/OMEMOElementTests.m"; sourceTree = ""; }; D9F20D001D836080002A8D6F /* OMEMOModuleTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = OMEMOModuleTests.m; path = "../../Testing-Shared/OMEMOModuleTests.m"; sourceTree = ""; }; + DDA11A481F8517C200591D1B /* XMPPManagedMessagingTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPManagedMessagingTests.m; path = "../../Testing-Shared/XMPPManagedMessagingTests.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -108,6 +110,7 @@ D973A0A11D2F1EF60096F3ED /* XMPPSwift.swift */, D973A0A21D2F1EF60096F3ED /* XMPPURITests.m */, D973A0A31D2F1EF60096F3ED /* XMPPvCardTests.m */, + DDA11A481F8517C200591D1B /* XMPPManagedMessagingTests.m */, D973A0921D2F1EB10096F3ED /* Info.plist */, ); path = XMPPFrameworkTests; @@ -251,6 +254,7 @@ D973A0A51D2F1EF60096F3ED /* EncodeDecodeTest.m in Sources */, D973A0AB1D2F1EF60096F3ED /* XMPPRoomLightCoreDataStorageTests.m in Sources */, D973A0AE1D2F1EF60096F3ED /* XMPPSwift.swift in Sources */, + DDA11A491F8517C300591D1B /* XMPPManagedMessagingTests.m in Sources */, D9669B591D9B13FF0018533D /* OMEMOServerTests.m in Sources */, D973A0AF1D2F1EF60096F3ED /* XMPPURITests.m in Sources */, D973A0A81D2F1EF60096F3ED /* XMPPMockStream.m in Sources */,