diff --git a/Extensions/XEP-0030/XMPPIQ+XEP_0030.m b/Extensions/XEP-0030/XMPPIQ+XEP_0030.m index 40bc1a7515..b0276adf5e 100644 --- a/Extensions/XEP-0030/XMPPIQ+XEP_0030.m +++ b/Extensions/XEP-0030/XMPPIQ+XEP_0030.m @@ -80,7 +80,7 @@ + (nonnull XMPPIQ *) discoverInfoAssociatedWithJID:(nonnull XMPPJID *)jid { NSXMLElement *query = [iq elementForName:@"query" xmlns: XMPPDiscoInfoNamespace]; if(query) { - return [query children]; + return [query children] ? [query children] : ((NSArray *)[[NSArray alloc] init]); } return nil; diff --git a/Extensions/XEP-0030/XMPPServiceDiscovery.h b/Extensions/XEP-0030/XMPPServiceDiscovery.h new file mode 100644 index 0000000000..f24eebb029 --- /dev/null +++ b/Extensions/XEP-0030/XMPPServiceDiscovery.h @@ -0,0 +1,31 @@ +// +// XMPPServiceDiscovery.h +// Mangosta +// +// Created by Andres Canal on 4/27/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import + +@class XMPPIDTracker; + +@interface XMPPServiceDiscovery : XMPPModule { + XMPPIDTracker *xmppIDTracker; +} + +- (void)discoverInformationAboutJID:(nonnull XMPPJID *)jid; +- (void)discoverItemsAssociatedWithJID:(nonnull XMPPJID *)jid; + +@end + +@protocol XMPPServiceDiscoveryDelegate + +@optional + +- (void)xmppServiceDiscovery:(nonnull XMPPServiceDiscovery *)sender didDiscoverInformation:(nonnull NSArray *)items; +- (void)xmppServiceDiscovery:(nonnull XMPPServiceDiscovery *)sender didDiscoverItems:(nonnull NSArray *)items; + +- (void)xmppServiceDiscovery:(nonnull XMPPServiceDiscovery *)sender didFailToDiscover:(nonnull XMPPIQ *)iq; + +@end \ No newline at end of file diff --git a/Extensions/XEP-0030/XMPPServiceDiscovery.m b/Extensions/XEP-0030/XMPPServiceDiscovery.m new file mode 100644 index 0000000000..66857da31f --- /dev/null +++ b/Extensions/XEP-0030/XMPPServiceDiscovery.m @@ -0,0 +1,99 @@ +// +// XMPPServiceDiscovery.m +// Mangosta +// +// Created by Andres Canal on 4/27/16. +// Copyright © 2016 Inaka. All rights reserved. +// + +#import "XMPPServiceDiscovery.h" +#import "XMPPIQ+XEP_0030.h" +#import "XMPPIDTracker.h" +#import "XMPPConstants.h" + +@implementation XMPPServiceDiscovery + +- (BOOL)activate:(XMPPStream *)aXmppStream { + + if ([super activate:aXmppStream]) { + xmppIDTracker = [[XMPPIDTracker alloc] initWithDispatchQueue:moduleQueue]; + + return YES; + } + + return NO; +} + +- (void)deactivate { + dispatch_block_t block = ^{ @autoreleasepool { + + [xmppIDTracker removeAllIDs]; + xmppIDTracker = nil; + + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_sync(moduleQueue, block); + + [super deactivate]; +} + +- (void) discoverWithIQ:(XMPPIQ *) infoOrItem { + + dispatch_block_t block = ^{ @autoreleasepool { + + NSString *iqID = [infoOrItem elementID]; + [xmppIDTracker addID:iqID + target:self + selector:@selector(handleDiscovery:withInfo:) + timeout:60.0]; + + [xmppStream sendElement:infoOrItem]; + }}; + + if (dispatch_get_specific(moduleQueueTag)) + block(); + else + dispatch_async(moduleQueue, block); +} + +- (void)discoverInformationAboutJID:(XMPPJID *)jid{ + [self discoverWithIQ:[XMPPIQ discoverInfoAssociatedWithJID:jid]]; +} + + +- (void)discoverItemsAssociatedWithJID:(XMPPJID *)jid{ + [self discoverWithIQ:[XMPPIQ discoverItemsAssociatedWithJID:jid]]; +} + +- (void)handleDiscovery:(XMPPIQ *)iq withInfo:(id )info{ + + if ([[iq type] isEqualToString:@"result"]){ + NSXMLElement *query = [iq elementForName:@"query"]; + NSArray *items = [query children]; + + if ([query.xmlns isEqualToString:XMPPDiscoInfoNamespace]) { + [multicastDelegate xmppServiceDiscovery:self didDiscoverInformation:items]; + } else { + [multicastDelegate xmppServiceDiscovery:self didDiscoverItems:items]; + } + + } else { + [multicastDelegate xmppServiceDiscovery:self didFailToDiscover:iq]; + } +} + +- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq { + + NSString *type = [iq type]; + + if ([type isEqualToString:@"result"] || [type isEqualToString:@"error"]){ + return [xmppIDTracker invokeForID:[iq elementID] withObject:iq]; + } + + return NO; +} + +@end diff --git a/Xcode/Testing-Shared/XMPPServiceDiscoveryTests.m b/Xcode/Testing-Shared/XMPPServiceDiscoveryTests.m new file mode 100644 index 0000000000..a2108105a4 --- /dev/null +++ b/Xcode/Testing-Shared/XMPPServiceDiscoveryTests.m @@ -0,0 +1,196 @@ +// +// XMPPServiceDiscoveryTests.m +// XMPPFrameworkTests +// +// Created by Andres Canal on 5/27/16. +// +// + +#import +#import "XMPPFramework/XMPPServiceDiscovery.h" +#import "XMPPMockStream.h" + +@interface XMPPServiceDiscoveryTests : XCTestCase +@property (nonatomic, strong) XCTestExpectation *delegateExpectation; +@end + +@implementation XMPPServiceDiscoveryTests + +- (void) testDiscoverInformationAbout{ + self.delegateExpectation = [self expectationWithDescription:@"Information Response"]; + + XMPPMockStream *streamTest = [[XMPPMockStream alloc] init]; + XMPPServiceDiscovery *serviceDiscovery = [[XMPPServiceDiscovery alloc] init]; + [serviceDiscovery activate:streamTest]; + [serviceDiscovery addDelegate:self delegateQueue:dispatch_get_main_queue()]; + + __weak typeof(XMPPMockStream) *weakStreamTest = streamTest; + streamTest.elementReceived = ^void(NSXMLElement *element) { + NSString *elementID = [element attributeForName:@"id"].stringValue; + XMPPIQ *iq = [self fakeInfoIQWithID:elementID]; + [weakStreamTest fakeIQResponse:iq]; + }; + + [serviceDiscovery discoverInformationAboutJID:[XMPPJID jidWithString:@"test.com"]]; + + [self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) { + if(error){ + XCTFail(@"Expectation Failed with error: %@", error); + } + }]; +} + +- (void)xmppServiceDiscovery:(XMPPServiceDiscovery *)sender didDiscoverInformation:(NSArray *)items{ + XCTAssertEqual(items.count, 9); + [self.delegateExpectation fulfill]; +} + +- (void) testDiscoverItems{ + self.delegateExpectation = [self expectationWithDescription:@"Items Response"]; + + XMPPMockStream *streamTest = [[XMPPMockStream alloc] init]; + XMPPServiceDiscovery *serviceDiscovery = [[XMPPServiceDiscovery alloc] init]; + [serviceDiscovery activate:streamTest]; + [serviceDiscovery addDelegate:self delegateQueue:dispatch_get_main_queue()]; + + __weak typeof(XMPPMockStream) *weakStreamTest = streamTest; + streamTest.elementReceived = ^void(NSXMLElement *element) { + NSString *elementID = [element attributeForName:@"id"].stringValue; + XMPPIQ *iq = [self fakeItemIQWithID:elementID]; + [weakStreamTest fakeIQResponse:iq]; + }; + + [serviceDiscovery discoverItemsAssociatedWithJID:[XMPPJID jidWithString:@"test.com"]]; + + [self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) { + if(error){ + XCTFail(@"Expectation Failed with error: %@", error); + } + }]; +} + +- (void)xmppServiceDiscovery:(XMPPServiceDiscovery *)sender didDiscoverItems:(NSArray *)items{ + XCTAssertEqual(items.count, 8); + [self.delegateExpectation fulfill]; +} + +- (void) testError{ + self.delegateExpectation = [self expectationWithDescription:@"Error Response"]; + + XMPPMockStream *streamTest = [[XMPPMockStream alloc] init]; + XMPPServiceDiscovery *serviceDiscovery = [[XMPPServiceDiscovery alloc] init]; + [serviceDiscovery activate:streamTest]; + [serviceDiscovery addDelegate:self delegateQueue:dispatch_get_main_queue()]; + + __weak typeof(XMPPMockStream) *weakStreamTest = streamTest; + streamTest.elementReceived = ^void(NSXMLElement *element) { + NSString *elementID = [element attributeForName:@"id"].stringValue; + XMPPIQ *iq = [self fakeErrorWithID:elementID]; + [weakStreamTest fakeIQResponse:iq]; + }; + + [serviceDiscovery discoverItemsAssociatedWithJID:[XMPPJID jidWithString:@"test.com"]]; + + [self waitForExpectationsWithTimeout:10 handler:^(NSError * _Nullable error) { + if(error){ + XCTFail(@"Expectation Failed with error: %@", error); + } + }]; +} + +- (XMPPIQ *)fakeErrorWithID:(NSString *) elementID{ + NSMutableString *s = [NSMutableString string]; + [s appendString:@""]; + [s appendString:@" "]; + [s appendString:@" "]; + [s appendString:@" "]; + [s appendString:@" "]; + [s appendString:@""]; + + NSError *error; + NSXMLDocument *doc = [[NSXMLDocument alloc] initWithXMLString:s options:0 error:&error]; + XMPPIQ *iq = [XMPPIQ iqFromElement:[doc rootElement]]; + [iq addAttributeWithName:@"id" stringValue:elementID]; + + return iq; +} + +- (void)xmppServiceDiscovery:(XMPPServiceDiscovery *)sender didFailToDiscover:(XMPPIQ *)iq{ + XCTAssertNotNil([iq elementForName:@"error"]); + [self.delegateExpectation fulfill]; +} + + +- (XMPPIQ *)fakeItemIQWithID:(NSString *) elementID{ + NSMutableString *s = [NSMutableString string]; + [s appendString:@""]; + [s appendString:@" "]; + [s appendString:@" "]; + [s appendString:@" "]; + [s appendString:@" "]; + [s appendString:@" "]; + [s appendString:@" "]; + [s appendString:@" "]; + [s appendString:@" "]; + [s appendString:@" "]; + [s appendString:@" "]; + [s appendString:@""]; + + NSError *error; + NSXMLDocument *doc = [[NSXMLDocument alloc] initWithXMLString:s options:0 error:&error]; + XMPPIQ *iq = [XMPPIQ iqFromElement:[doc rootElement]]; + [iq addAttributeWithName:@"id" stringValue:elementID]; + + return iq; +} + +- (XMPPIQ *)fakeInfoIQWithID:(NSString *) elementID{ + + NSMutableString *s = [NSMutableString string]; + [s appendString: @""]; + [s appendString: @" "]; + [s appendString: @" "]; + [s appendString: @" "]; + [s appendString: @" "]; + [s appendString: @" "]; + [s appendString: @" "]; + [s appendString: @" "]; + [s appendString: @" "]; + [s appendString: @" "]; + [s appendString: @" "]; + [s appendString: @" "]; + [s appendString: @""]; + + NSError *error; + NSXMLDocument *doc = [[NSXMLDocument alloc] initWithXMLString:s options:0 error:&error]; + XMPPIQ *iq = [XMPPIQ iqFromElement:[doc rootElement]]; + [iq addAttributeWithName:@"id" stringValue:elementID]; + + return iq; +} + +@end diff --git a/Xcode/Testing-iOS/XMPPFrameworkTests.xcodeproj/project.pbxproj b/Xcode/Testing-iOS/XMPPFrameworkTests.xcodeproj/project.pbxproj index 31795ce04c..883d36c4ef 100644 --- a/Xcode/Testing-iOS/XMPPFrameworkTests.xcodeproj/project.pbxproj +++ b/Xcode/Testing-iOS/XMPPFrameworkTests.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ D973A0871D2F18040096F3ED /* XMPPvCardTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D973A07B1D2F18040096F3ED /* XMPPvCardTests.m */; }; D973A0891D2F18310096F3ED /* XMPPSwift.swift in Sources */ = {isa = PBXBuildFile; fileRef = D973A0881D2F18310096F3ED /* XMPPSwift.swift */; }; E0B1B8161D33F5A700B7E608 /* XMPP0030Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0B1B8141D33F5A700B7E608 /* XMPP0030Tests.m */; }; + E0B3E9211D34083800EAD41B /* XMPPServiceDiscoveryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0B3E9201D34083800EAD41B /* XMPPServiceDiscoveryTests.m */; }; FDD2AB232C05507F2045FFFC /* Pods_XMPPFrameworkTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CD0B17267211A912DE2098E /* Pods_XMPPFrameworkTests.framework */; }; /* End PBXBuildFile section */ @@ -46,6 +47,7 @@ D973A07B1D2F18040096F3ED /* XMPPvCardTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPvCardTests.m; path = "../../Testing-Shared/XMPPvCardTests.m"; sourceTree = ""; }; D973A0881D2F18310096F3ED /* XMPPSwift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = XMPPSwift.swift; path = "../../Testing-Shared/XMPPSwift.swift"; sourceTree = ""; }; E0B1B8141D33F5A700B7E608 /* XMPP0030Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPP0030Tests.m; path = "../../Testing-Shared/XMPP0030Tests.m"; sourceTree = ""; }; + E0B3E9201D34083800EAD41B /* XMPPServiceDiscoveryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPServiceDiscoveryTests.m; path = "../../Testing-Shared/XMPPServiceDiscoveryTests.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -79,7 +81,10 @@ 63F50D941C60208200CA0201 /* XMPPFrameworkTests */ = { isa = PBXGroup; children = ( + 63F50D971C60208200CA0201 /* Info.plist */, + D973A06E1D2F18030096F3ED /* XMPPFrameworkTests-Bridging-Header.h */, D973A0881D2F18310096F3ED /* XMPPSwift.swift */, + D973A0761D2F18040096F3ED /* XMPPPushTests.swift */, D973A06F1D2F18040096F3ED /* CapabilitiesHashingTest.m */, D973A0701D2F18040096F3ED /* EncodeDecodeTest.m */, D973A0711D2F18040096F3ED /* XMPPHTTPFileUploadTests.m */, @@ -87,15 +92,13 @@ D973A0731D2F18040096F3ED /* XMPPMockStream.h */, D973A0741D2F18040096F3ED /* XMPPMockStream.m */, D973A0751D2F18040096F3ED /* XMPPMUCLightTests.m */, - D973A0761D2F18040096F3ED /* XMPPPushTests.swift */, D973A0771D2F18040096F3ED /* XMPPRoomLightCoreDataStorageTests.m */, D973A0781D2F18040096F3ED /* XMPPRoomLightTests.m */, D973A0791D2F18040096F3ED /* XMPPStorageHintTests.m */, D973A07A1D2F18040096F3ED /* XMPPURITests.m */, D973A07B1D2F18040096F3ED /* XMPPvCardTests.m */, E0B1B8141D33F5A700B7E608 /* XMPP0030Tests.m */, - 63F50D971C60208200CA0201 /* Info.plist */, - D973A06E1D2F18030096F3ED /* XMPPFrameworkTests-Bridging-Header.h */, + E0B3E9201D34083800EAD41B /* XMPPServiceDiscoveryTests.m */, ); path = XMPPFrameworkTests; sourceTree = ""; @@ -262,6 +265,7 @@ D973A07F1D2F18040096F3ED /* XMPPMessageArchiveManagementTests.m in Sources */, D973A07E1D2F18040096F3ED /* XMPPHTTPFileUploadTests.m in Sources */, D973A0821D2F18040096F3ED /* XMPPPushTests.swift in Sources */, + E0B3E9211D34083800EAD41B /* XMPPServiceDiscoveryTests.m in Sources */, D973A0851D2F18040096F3ED /* XMPPStorageHintTests.m in Sources */, D973A0891D2F18310096F3ED /* XMPPSwift.swift in Sources */, D973A0871D2F18040096F3ED /* XMPPvCardTests.m in Sources */, diff --git a/Xcode/Testing-macOS/XMPPFrameworkTests.xcodeproj/project.pbxproj b/Xcode/Testing-macOS/XMPPFrameworkTests.xcodeproj/project.pbxproj index 017ea0cc65..c4b90f830a 100644 --- a/Xcode/Testing-macOS/XMPPFrameworkTests.xcodeproj/project.pbxproj +++ b/Xcode/Testing-macOS/XMPPFrameworkTests.xcodeproj/project.pbxproj @@ -22,6 +22,7 @@ D973A0AF1D2F1EF60096F3ED /* XMPPURITests.m in Sources */ = {isa = PBXBuildFile; fileRef = D973A0A21D2F1EF60096F3ED /* XMPPURITests.m */; }; D973A0B01D2F1EF60096F3ED /* XMPPvCardTests.m in Sources */ = {isa = PBXBuildFile; fileRef = D973A0A31D2F1EF60096F3ED /* XMPPvCardTests.m */; }; E0B1B8191D34033D00B7E608 /* XMPP0030Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0B1B8181D34033D00B7E608 /* XMPP0030Tests.m */; }; + E0B3E9231D340AA300EAD41B /* XMPPServiceDiscoveryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = E0B3E9221D340AA300EAD41B /* XMPPServiceDiscoveryTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -45,6 +46,7 @@ D973A0A21D2F1EF60096F3ED /* XMPPURITests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPURITests.m; path = "../../Testing-Shared/XMPPURITests.m"; sourceTree = ""; }; D973A0A31D2F1EF60096F3ED /* XMPPvCardTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPvCardTests.m; path = "../../Testing-Shared/XMPPvCardTests.m"; sourceTree = ""; }; E0B1B8181D34033D00B7E608 /* XMPP0030Tests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPP0030Tests.m; path = "../../Testing-Shared/XMPP0030Tests.m"; sourceTree = ""; }; + E0B3E9221D340AA300EAD41B /* XMPPServiceDiscoveryTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = XMPPServiceDiscoveryTests.m; path = "../../Testing-Shared/XMPPServiceDiscoveryTests.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -81,6 +83,9 @@ D973A08F1D2F1EB10096F3ED /* XMPPFrameworkTests */ = { isa = PBXGroup; children = ( + D973A0921D2F1EB10096F3ED /* Info.plist */, + D973A0A11D2F1EF60096F3ED /* XMPPSwift.swift */, + D973A09D1D2F1EF60096F3ED /* XMPPPushTests.swift */, D973A0961D2F1EF60096F3ED /* CapabilitiesHashingTest.m */, D973A0971D2F1EF60096F3ED /* EncodeDecodeTest.m */, D973A0981D2F1EF60096F3ED /* XMPPHTTPFileUploadTests.m */, @@ -88,15 +93,13 @@ D973A09A1D2F1EF60096F3ED /* XMPPMockStream.h */, D973A09B1D2F1EF60096F3ED /* XMPPMockStream.m */, D973A09C1D2F1EF60096F3ED /* XMPPMUCLightTests.m */, - D973A09D1D2F1EF60096F3ED /* XMPPPushTests.swift */, D973A09E1D2F1EF60096F3ED /* XMPPRoomLightCoreDataStorageTests.m */, D973A09F1D2F1EF60096F3ED /* XMPPRoomLightTests.m */, D973A0A01D2F1EF60096F3ED /* XMPPStorageHintTests.m */, - D973A0A11D2F1EF60096F3ED /* XMPPSwift.swift */, D973A0A21D2F1EF60096F3ED /* XMPPURITests.m */, D973A0A31D2F1EF60096F3ED /* XMPPvCardTests.m */, E0B1B8181D34033D00B7E608 /* XMPP0030Tests.m */, - D973A0921D2F1EB10096F3ED /* Info.plist */, + E0B3E9221D340AA300EAD41B /* XMPPServiceDiscoveryTests.m */, ); path = XMPPFrameworkTests; sourceTree = ""; @@ -252,6 +255,7 @@ D973A0AC1D2F1EF60096F3ED /* XMPPRoomLightTests.m in Sources */, D973A0B01D2F1EF60096F3ED /* XMPPvCardTests.m in Sources */, D973A0A71D2F1EF60096F3ED /* XMPPMessageArchiveManagementTests.m in Sources */, + E0B3E9231D340AA300EAD41B /* XMPPServiceDiscoveryTests.m in Sources */, D973A0A61D2F1EF60096F3ED /* XMPPHTTPFileUploadTests.m in Sources */, D973A0AA1D2F1EF60096F3ED /* XMPPPushTests.swift in Sources */, D973A0AD1D2F1EF60096F3ED /* XMPPStorageHintTests.m in Sources */,