Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'master' into xep-0136

  • Loading branch information...
commit 7514ac909afe5e2fe8ad85a290bc85d293b1cad4 2 parents 2b30e76 + b28b3b1
@robbiehanson authored
Showing with 15,643 additions and 3,450 deletions.
  1. +2 −2 Authentication/Anonymous/XMPPAnonymousAuthentication.m
  2. +5 −5 Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.m
  3. +5 −5 Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.m
  4. +37 −2 Authentication/Digest-MD5/XMPPDigestMD5Authentication.m
  5. +4 −4 Authentication/Plain/XMPPPlainAuthentication.m
  6. +20 −13 Authentication/X-Facebook-Platform/XMPPXFacebookPlatformAuthentication.m
  7. +4 −3 Categories/NSData+XMPP.m
  8. +6 −0 Core/XMPPIQ.h
  9. +30 −0 Core/XMPPIQ.m
  10. +13 −13 Core/XMPPInternal.h
  11. +24 −0 Core/XMPPJID.h
  12. +176 −7 Core/XMPPJID.m
  13. +32 −19 Core/XMPPLogging.h
  14. +2 −4 Core/XMPPMessage.m
  15. +1 −1  Core/XMPPModule.h
  16. +31 −49 Core/XMPPModule.m
  17. +7 −14 Core/XMPPParser.h
  18. +201 −142 Core/XMPPParser.m
  19. +81 −62 Core/XMPPStream.h
  20. +1,057 −469 Core/XMPPStream.m
  21. +28 −2 Extensions/CoreDataStorage/XMPPCoreDataStorage.m
  22. +101 −0 Extensions/ProcessOne/XMPPProcessOne.h
  23. +359 −0 Extensions/ProcessOne/XMPPProcessOne.m
  24. +30 −1 Extensions/Reconnect/XMPPReconnect.m
  25. +5 −1 Extensions/Roster/MemoryStorage/XMPPRosterMemoryStorage.h
  26. +43 −14 Extensions/Roster/MemoryStorage/XMPPRosterMemoryStorage.m
  27. +7 −8 Extensions/XEP-0009/XMPPIQ+JabberRPC.m
  28. +30 −0 Extensions/XEP-0009/XMPPJabberRPCModule.m
  29. +229 −0 Extensions/XEP-0016/XMPPPrivacy.h
  30. +1,001 −0 Extensions/XEP-0016/XMPPPrivacy.m
  31. +29 −1 Extensions/XEP-0045/CoreDataStorage/XMPPRoomCoreDataStorage.m
  32. +28 −0 Extensions/XEP-0045/HybridStorage/XMPPRoomHybridStorage.m
  33. +1 −1  Extensions/XEP-0045/MemoryStorage/XMPPRoomMemoryStorage.h
  34. +34 −1 Extensions/XEP-0045/MemoryStorage/XMPPRoomMemoryStorage.m
  35. +2 −1  Extensions/XEP-0054/XMPPvCardTemp.m
  36. +41 −11 Extensions/XEP-0065/TURNSocket.m
  37. +55 −7 Extensions/XEP-0115/XMPPCapabilities.m
  38. +3 −1 Extensions/XEP-0153/XMPPvCardAvatarModule.m
  39. +28 −0 Extensions/XEP-0199/XMPPAutoPing.m
  40. +28 −3 Extensions/XEP-0202/XMPPAutoTime.m
  41. +1 −1  README.markdown
  42. +43 −3 Core/XMPPFramework.h → Sample_XMPPFramework.h
  43. +3 −1 Utilities/GCDMulticastDelegate.h
  44. +274 −47 Utilities/GCDMulticastDelegate.m
  45. +34 −1 Utilities/XMPPIDTracker.m
  46. +41 −2 Utilities/XMPPSRVResolver.m
  47. +18 −4 Vendor/CocoaAsyncSocket/GCDAsyncSocket.h
  48. +538 −245 Vendor/CocoaAsyncSocket/GCDAsyncSocket.m
  49. +49 −3 Vendor/CocoaLumberjack/DDAbstractDatabaseLogger.m
  50. +102 −61 Vendor/CocoaLumberjack/DDFileLogger.h
  51. +186 −56 Vendor/CocoaLumberjack/DDFileLogger.m
  52. +133 −37 Vendor/CocoaLumberjack/DDLog.h
  53. +162 −109 Vendor/CocoaLumberjack/DDLog.m
  54. +127 −9 Vendor/CocoaLumberjack/DDTTYLogger.h
  55. +1,347 −53 Vendor/CocoaLumberjack/DDTTYLogger.m
  56. +1 −1  Vendor/CocoaLumberjack/Extensions/ContextFilterLogFormatter.m
  57. +17 −0 Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.h
  58. +79 −27 Vendor/CocoaLumberjack/Extensions/DispatchQueueLogFormatter.m
  59. +8 −8 Xcode/DesktopXMPP/AppDelegate.h
  60. +25 −25 Xcode/DesktopXMPP/AppDelegate.m
  61. +2 −2 Xcode/DesktopXMPP/RequestController.m
  62. +5 −4 Xcode/DesktopXMPP/RosterController.m
  63. +9 −7 Xcode/DesktopXMPP/XMPPStream.xcodeproj/project.pbxproj
  64. +0 −6 Xcode/Testing/MulticastDelegateTest/Class1.h
  65. +0 −47 Xcode/Testing/MulticastDelegateTest/Class1.m
  66. +0 −6 Xcode/Testing/MulticastDelegateTest/Class2.h
  67. +0 −45 Xcode/Testing/MulticastDelegateTest/Class2.m
  68. +340 −0 Xcode/Testing/MulticastDelegateTest/Desktop/MulticastDelegateTest.xcodeproj/project.pbxproj
  69. 0  ...DelegateTest/{ → Desktop}/MulticastDelegateTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  70. +8 −0 Xcode/Testing/MulticastDelegateTest/Desktop/MulticastDelegateTest/AppDelegate.h
  71. +14 −0 Xcode/Testing/MulticastDelegateTest/Desktop/MulticastDelegateTest/AppDelegate.m
  72. +8 −6 Xcode/Testing/MulticastDelegateTest/{ → Desktop/MulticastDelegateTest}/MulticastDelegateTest-Info.plist
  73. +1 −1  ...st/{MulticastDelegateTest_Prefix.pch → Desktop/MulticastDelegateTest/MulticastDelegateTest-Prefix.pch}
  74. +29 −0 Xcode/Testing/MulticastDelegateTest/Desktop/MulticastDelegateTest/en.lproj/Credits.rtf
  75. 0  ...sting/MulticastDelegateTest/{English.lproj → Desktop/MulticastDelegateTest/en.lproj}/InfoPlist.strings
  76. +1,637 −1,169 Xcode/Testing/MulticastDelegateTest/{English.lproj → Desktop/MulticastDelegateTest/en.lproj}/MainMenu.xib
  77. +14 −0 Xcode/Testing/MulticastDelegateTest/Desktop/MulticastDelegateTest/main.m
  78. +308 −0 Xcode/Testing/MulticastDelegateTest/Mobile/MulticastDelegateTest.xcodeproj/project.pbxproj
  79. +7 −0 ...g/MulticastDelegateTest/Mobile/MulticastDelegateTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  80. +8 −0 Xcode/Testing/MulticastDelegateTest/Mobile/MulticastDelegateTest/AppDelegate.h
  81. +30 −0 Xcode/Testing/MulticastDelegateTest/Mobile/MulticastDelegateTest/AppDelegate.m
  82. +38 −0 Xcode/Testing/MulticastDelegateTest/Mobile/MulticastDelegateTest/MulticastDelegateTest-Info.plist
  83. +14 −0 Xcode/Testing/MulticastDelegateTest/Mobile/MulticastDelegateTest/MulticastDelegateTest-Prefix.pch
  84. +2 −0  Xcode/Testing/MulticastDelegateTest/Mobile/MulticastDelegateTest/en.lproj/InfoPlist.strings
  85. +18 −0 Xcode/Testing/MulticastDelegateTest/Mobile/MulticastDelegateTest/main.m
  86. +0 −318 Xcode/Testing/MulticastDelegateTest/MulticastDelegateTest.xcodeproj/project.pbxproj
  87. +0 −24 Xcode/Testing/MulticastDelegateTest/MulticastDelegateTestAppDelegate.h
  88. +0 −185 Xcode/Testing/MulticastDelegateTest/MulticastDelegateTestAppDelegate.m
  89. +14 −0 Xcode/Testing/MulticastDelegateTest/Shared/DelegateTesters.h
  90. +143 −0 Xcode/Testing/MulticastDelegateTest/Shared/DelegateTesters.m
  91. +10 −0 Xcode/Testing/MulticastDelegateTest/Shared/MulticastDelegateTest.h
  92. +186 −0 Xcode/Testing/MulticastDelegateTest/Shared/MulticastDelegateTest.m
  93. +1 −1  Xcode/Testing/MulticastDelegateTest/{ → Shared}/MyProtocol.h
  94. +0 −14 Xcode/Testing/MulticastDelegateTest/main.m
  95. +34 −1 Xcode/Testing/TestElementReceipt/TestElementReceipt/TestElementReceiptAppDelegate.m
  96. +365 −0 Xcode/Testing/TestJID/Desktop/TestJID.xcodeproj/project.pbxproj
  97. +7 −0 Xcode/Testing/TestJID/Desktop/TestJID.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  98. +7 −0 Xcode/Testing/TestJID/Desktop/TestJID/AppDelegate.h
  99. +56 −0 Xcode/Testing/TestJID/Desktop/TestJID/AppDelegate.m
  100. +34 −0 Xcode/Testing/TestJID/Desktop/TestJID/TestJID-Info.plist
  101. +7 −0 Xcode/Testing/TestJID/Desktop/TestJID/TestJID-Prefix.pch
  102. +29 −0 Xcode/Testing/TestJID/Desktop/TestJID/en.lproj/Credits.rtf
  103. +2 −0  Xcode/Testing/TestJID/Desktop/TestJID/en.lproj/InfoPlist.strings
  104. +4,587 −0 Xcode/Testing/TestJID/Desktop/TestJID/en.lproj/MainMenu.xib
  105. +14 −0 Xcode/Testing/TestJID/Desktop/TestJID/main.m
  106. +333 −0 Xcode/Testing/TestJID/Mobile/TestJID.xcodeproj/project.pbxproj
  107. +7 −0 Xcode/Testing/TestJID/Mobile/TestJID.xcodeproj/project.xcworkspace/contents.xcworkspacedata
  108. +7 −0 Xcode/Testing/TestJID/Mobile/TestJID/AppDelegate.h
  109. +68 −0 Xcode/Testing/TestJID/Mobile/TestJID/AppDelegate.m
  110. +38 −0 Xcode/Testing/TestJID/Mobile/TestJID/TestJID-Info.plist
  111. +14 −0 Xcode/Testing/TestJID/Mobile/TestJID/TestJID-Prefix.pch
  112. +2 −0  Xcode/Testing/TestJID/Mobile/TestJID/en.lproj/InfoPlist.strings
  113. +18 −0 Xcode/Testing/TestJID/Mobile/TestJID/main.m
  114. +157 −52 Xcode/iPhoneXMPP/Classes/SettingsViewController.xib
  115. +1 −1  Xcode/iPhoneXMPP/Classes/iPhoneXMPPAppDelegate.m
  116. +1 −1  Xcode/iPhoneXMPP/iPhoneXMPP.xcodeproj/project.pbxproj
  117. +1 −1  Xcode/iPhoneXMPP/main.m
View
4 Authentication/Anonymous/XMPPAnonymousAuthentication.m
@@ -117,10 +117,10 @@ - (BOOL)authenticateAnonymously:(NSError **)errPtr
}
}};
- if (dispatch_get_current_queue() == xmppQueue)
+ if (dispatch_get_current_queue() == self.xmppQueue)
block();
else
- dispatch_sync(xmppQueue, block);
+ dispatch_sync(self.xmppQueue, block);
if (errPtr)
*errPtr = err;
View
10 Authentication/Deprecated-Digest/XMPPDeprecatedDigestAuthentication.m
@@ -122,7 +122,7 @@ - (BOOL)supportsDeprecatedDigestAuthentication
// The root element can be properly queried for authentication mechanisms anytime after the
// stream:features are received, and TLS has been setup (if required)
- if (state >= STATE_XMPP_POST_NEGOTIATION)
+ if (self.state >= STATE_XMPP_POST_NEGOTIATION)
{
// Search for an iq element within the rootElement.
// Recall that some servers might stupidly add a "jabber:client" namespace which might cause problems
@@ -130,10 +130,10 @@ - (BOOL)supportsDeprecatedDigestAuthentication
NSXMLElement *iq = nil;
- NSUInteger i, count = [rootElement childCount];
+ NSUInteger i, count = [self.rootElement childCount];
for (i = 0; i < count; i++)
{
- NSXMLNode *childNode = [rootElement childAtIndex:i];
+ NSXMLNode *childNode = [self.rootElement childAtIndex:i];
if ([childNode kind] == NSXMLElementKind)
{
@@ -151,10 +151,10 @@ - (BOOL)supportsDeprecatedDigestAuthentication
}
}};
- if (dispatch_get_current_queue() == xmppQueue)
+ if (dispatch_get_current_queue() == self.xmppQueue)
block();
else
- dispatch_sync(xmppQueue, block);
+ dispatch_sync(self.xmppQueue, block);
return result;
}
View
10 Authentication/Deprecated-Plain/XMPPDeprecatedPlainAuthentication.m
@@ -116,7 +116,7 @@ - (BOOL)supportsDeprecatedPlainAuthentication
// The root element can be properly queried for authentication mechanisms anytime after the
// stream:features are received, and TLS has been setup (if required)
- if (state >= STATE_XMPP_POST_NEGOTIATION)
+ if (self.state >= STATE_XMPP_POST_NEGOTIATION)
{
// Search for an iq element within the rootElement.
// Recall that some servers might stupidly add a "jabber:client" namespace which might cause problems
@@ -124,10 +124,10 @@ - (BOOL)supportsDeprecatedPlainAuthentication
NSXMLElement *iq = nil;
- NSUInteger i, count = [rootElement childCount];
+ NSUInteger i, count = [self.rootElement childCount];
for (i = 0; i < count; i++)
{
- NSXMLNode *childNode = [rootElement childAtIndex:i];
+ NSXMLNode *childNode = [self.rootElement childAtIndex:i];
if ([childNode kind] == NSXMLElementKind)
{
@@ -145,10 +145,10 @@ - (BOOL)supportsDeprecatedPlainAuthentication
}
}};
- if (dispatch_get_current_queue() == xmppQueue)
+ if (dispatch_get_current_queue() == self.xmppQueue)
block();
else
- dispatch_sync(xmppQueue, block);
+ dispatch_sync(self.xmppQueue, block);
return result;
}
View
39 Authentication/Digest-MD5/XMPPDigestMD5Authentication.m
@@ -35,6 +35,16 @@ @interface XMPPDigestMD5Authentication ()
NSString *password;
}
+// The properties are hooks (primarily for testing)
+
+@property (nonatomic, strong) NSString *realm;
+@property (nonatomic, strong) NSString *nonce;
+@property (nonatomic, strong) NSString *qop;
+@property (nonatomic, strong) NSString *cnonce;
+@property (nonatomic, strong) NSString *digestURI;
+@property (nonatomic, strong) NSString *username;
+@property (nonatomic, strong) NSString *password;
+
- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge;
- (NSString *)base64EncodedFullResponse;
@@ -51,6 +61,14 @@ + (NSString *)mechanismName
return @"DIGEST-MD5";
}
+@synthesize realm;
+@synthesize nonce;
+@synthesize qop;
+@synthesize cnonce;
+@synthesize digestURI;
+@synthesize username;
+@synthesize password;
+
- (id)initWithStream:(XMPPStream *)stream password:(NSString *)inPassword
{
if ((self = [super init]))
@@ -119,7 +137,9 @@ - (XMPPHandleAuthResponse)handleAuth1:(NSXMLElement *)authResponse
else
digestURI = [NSString stringWithFormat:@"xmpp/%@", serverHostName];
- cnonce = [XMPPStream generateUUID];
+ if (cnonce == nil)
+ cnonce = [XMPPStream generateUUID];
+
username = [myJID user];
// Create and send challenge response element
@@ -234,22 +254,37 @@ - (NSString *)response
NSString *HA1str = [NSString stringWithFormat:@"%@:%@:%@", username, realm, password];
NSString *HA2str = [NSString stringWithFormat:@"AUTHENTICATE:%@", digestURI];
+ XMPPLogVerbose(@"HA1str: %@", HA1str);
+ XMPPLogVerbose(@"HA2str: %@", HA2str);
+
NSData *HA1dataA = [[HA1str dataUsingEncoding:NSUTF8StringEncoding] md5Digest];
NSData *HA1dataB = [[NSString stringWithFormat:@":%@:%@", nonce, cnonce] dataUsingEncoding:NSUTF8StringEncoding];
+ XMPPLogVerbose(@"HA1dataA: %@", HA1dataA);
+ XMPPLogVerbose(@"HA1dataB: %@", HA1dataB);
+
NSMutableData *HA1data = [NSMutableData dataWithCapacity:([HA1dataA length] + [HA1dataB length])];
[HA1data appendData:HA1dataA];
[HA1data appendData:HA1dataB];
+ XMPPLogVerbose(@"HA1data: %@", HA1data);
+
NSString *HA1 = [[HA1data md5Digest] hexStringValue];
NSString *HA2 = [[[HA2str dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue];
+ XMPPLogVerbose(@"HA1: %@", HA1);
+ XMPPLogVerbose(@"HA2: %@", HA2);
+
NSString *responseStr = [NSString stringWithFormat:@"%@:%@:00000001:%@:auth:%@",
HA1, nonce, cnonce, HA2];
+ XMPPLogVerbose(@"responseStr: %@", responseStr);
+
NSString *response = [[[responseStr dataUsingEncoding:NSUTF8StringEncoding] md5Digest] hexStringValue];
+ XMPPLogVerbose(@"response: %@", response);
+
return response;
}
@@ -266,7 +301,7 @@ - (NSString *)base64EncodedFullResponse
[buffer appendFormat:@"response=%@,", [self response]];
[buffer appendFormat:@"charset=utf-8"];
- XMPPLogSend(@"decoded response: %@", buffer);
+ XMPPLogVerbose(@"%@: Decoded response: %@", THIS_FILE, buffer);
NSData *utf8data = [buffer dataUsingEncoding:NSUTF8StringEncoding];
View
8 Authentication/Plain/XMPPPlainAuthentication.m
@@ -19,11 +19,11 @@
@implementation XMPPPlainAuthentication
{
- #if __has_feature(objc_arc_weak)
+ #if __has_feature(objc_arc_weak)
__weak XMPPStream *xmppStream;
- #else
+ #else
__unsafe_unretained XMPPStream *xmppStream;
- #endif
+ #endif
NSString *password;
}
@@ -56,7 +56,7 @@ - (BOOL)start:(NSError **)errPtr
NSString *username = [xmppStream.myJID user];
- NSString *payload = [NSString stringWithFormat:@"%C%@%C%@", 0, username, 0, password];
+ NSString *payload = [NSString stringWithFormat:@"\0%@\0%@", username, password];
NSString *base64 = [[payload dataUsingEncoding:NSUTF8StringEncoding] base64Encoded];
// <auth xmlns="urn:ietf:params:xml:ns:xmpp-sasl" mechanism="PLAIN">Base-64-Info</auth>
View
33 Authentication/X-Facebook-Platform/XMPPXFacebookPlatformAuthentication.m
@@ -21,11 +21,7 @@
static char facebookAppIdKey;
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-#pragma mark -
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
-@implementation XMPPXFacebookPlatformAuthentication
+@interface XMPPXFacebookPlatformAuthentication ()
{
#if __has_feature(objc_arc_weak)
__weak XMPPStream *xmppStream;
@@ -41,6 +37,17 @@ @implementation XMPPXFacebookPlatformAuthentication
NSString *method;
}
+- (NSDictionary *)dictionaryFromChallenge:(NSXMLElement *)challenge;
+- (NSString *)base64EncodedFullResponse;
+
+@end
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark -
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+@implementation XMPPXFacebookPlatformAuthentication
+
+ (NSString *)mechanismName
{
return @"X-FACEBOOK-PLATFORM";
@@ -225,7 +232,7 @@ - (id)initWithFacebookAppId:(NSString *)fbAppId
if ((self = [self init])) // Note: Using [self init], NOT [super init]
{
self.facebookAppId = fbAppId;
- myJID_setByClient = [XMPPJID jidWithString:XMPPFacebookChatHostName];
+ self.myJID = [XMPPJID jidWithString:XMPPFacebookChatHostName];
// As of October 8, 2011, Facebook doesn't have their XMPP SRV records set.
// And, as per the XMPP specification, we MUST check the XMPP SRV records for an IP address,
@@ -233,7 +240,7 @@ - (id)initWithFacebookAppId:(NSString *)fbAppId
//
// So we're setting the hostname as a minor optimization to avoid the SRV timeout delay.
- hostName = XMPPFacebookChatHostName;
+ self.hostName = XMPPFacebookChatHostName;
}
return self;
}
@@ -246,10 +253,10 @@ - (NSString *)facebookAppId
result = objc_getAssociatedObject(self, &facebookAppIdKey);
};
- if (dispatch_get_current_queue() == xmppQueue)
+ if (dispatch_get_current_queue() == self.xmppQueue)
block();
else
- dispatch_sync(xmppQueue, block);
+ dispatch_sync(self.xmppQueue, block);
return result;
}
@@ -262,10 +269,10 @@ - (void)setFacebookAppId:(NSString *)inFacebookAppId
objc_setAssociatedObject(self, &facebookAppIdKey, newFacebookAppId, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
};
- if (dispatch_get_current_queue() == xmppQueue)
+ if (dispatch_get_current_queue() == self.xmppQueue)
block();
else
- dispatch_async(xmppQueue, block);
+ dispatch_async(self.xmppQueue, block);
}
- (BOOL)supportsXFacebookPlatformAuthentication
@@ -307,10 +314,10 @@ - (BOOL)authenticateWithFacebookAccessToken:(NSString *)accessToken error:(NSErr
}};
- if (dispatch_get_current_queue() == xmppQueue)
+ if (dispatch_get_current_queue() == self.xmppQueue)
block();
else
- dispatch_sync(xmppQueue, block);
+ dispatch_sync(self.xmppQueue, block);
if (errPtr)
*errPtr = err;
View
7 Categories/NSData+XMPP.m
@@ -40,7 +40,7 @@ - (NSString *)hexStringValue
for (i = 0; i < [self length]; ++i)
{
- [stringBuffer appendFormat:@"%02x", (unsigned long)dataBuffer[i]];
+ [stringBuffer appendFormat:@"%02x", (unsigned int)dataBuffer[i]];
}
return [stringBuffer copy];
@@ -106,11 +106,12 @@ - (NSData *)base64Decoded
unsigned long ixtext = 0;
unsigned long lentext = [self length];
unsigned char ch = 0;
- unsigned char inbuf[4], outbuf[3];
+ unsigned char inbuf[4] = {0, 0, 0, 0};
+ unsigned char outbuf[3] = {0, 0, 0};
short i = 0, ixinbuf = 0;
BOOL flignore = NO;
BOOL flendtext = NO;
-
+
while( YES )
{
if( ixtext >= lentext ) break;
View
6 Core/XMPPIQ.h
@@ -27,6 +27,9 @@
+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid;
+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
+ (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
++ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid;
++ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
++ (XMPPIQ *)iqWithType:(NSString *)type child:(NSXMLElement *)childElement;
/**
* Creates and returns a new XMPPIQ element.
@@ -37,6 +40,9 @@
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid;
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
- (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
+- (id)initWithType:(NSString *)type elementID:(NSString *)eid;
+- (id)initWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
+- (id)initWithType:(NSString *)type child:(NSXMLElement *)childElement;
/**
* Returns the type attribute of the IQ.
View
30 Core/XMPPIQ.m
@@ -65,6 +65,21 @@ + (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)
return [[XMPPIQ alloc] initWithType:type to:jid elementID:eid child:childElement];
}
++ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid
+{
+ return [[XMPPIQ alloc] initWithType:type to:nil elementID:eid child:nil];
+}
+
++ (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement
+{
+ return [[XMPPIQ alloc] initWithType:type to:nil elementID:eid child:childElement];
+}
+
++ (XMPPIQ *)iqWithType:(NSString *)type child:(NSXMLElement *)childElement
+{
+ return [[XMPPIQ alloc] initWithType:type to:nil elementID:nil child:childElement];
+}
+
- (id)init
{
return [self initWithType:nil to:nil elementID:nil child:nil];
@@ -104,6 +119,21 @@ - (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid
return self;
}
+- (id)initWithType:(NSString *)type elementID:(NSString *)eid
+{
+ return [self initWithType:type to:nil elementID:eid child:nil];
+}
+
+- (id)initWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement
+{
+ return [self initWithType:type to:nil elementID:eid child:childElement];
+}
+
+- (id)initWithType:(NSString *)type child:(NSXMLElement *)childElement
+{
+ return [self initWithType:type to:nil elementID:nil child:childElement];
+}
+
- (NSString *)type
{
return [[self attributeStringValueForName:@"type"] lowercaseString];
View
26 Core/XMPPInternal.h
@@ -4,18 +4,6 @@
#import "XMPPSASLAuthentication.h"
-// Define the various timeouts (in seconds) for retreiving various parts of the XML stream
-#define TIMEOUT_XMPP_WRITE -1
-#define TIMEOUT_XMPP_READ_START 10
-#define TIMEOUT_XMPP_READ_STREAM -1
-
-// Define the various tags we'll use to differentiate what it is we're currently reading or writing
-#define TAG_XMPP_READ_START 100
-#define TAG_XMPP_READ_STREAM 101
-#define TAG_XMPP_WRITE_START 200
-#define TAG_XMPP_WRITE_STREAM 201
-#define TAG_XMPP_WRITE_RECEIPT 202
-
// Define the various states we'll use to track our progress
enum XMPPStreamState
{
@@ -48,8 +36,14 @@ typedef enum XMPPStreamState XMPPStreamState;
**/
extern NSString *const XMPPStreamDidChangeMyJIDNotification;
-@interface XMPPStream (Internal)
+@interface XMPPStream (/* Internal */)
+
+/**
+ * Categories on XMPPStream should maintain thread safety by dispatching through the internal xmppQueue.
+ * They may also need to ensure the stream is in the proper state for their activity.
+**/
+@property (readonly) dispatch_queue_t xmppQueue;
@property (readonly) XMPPStreamState state;
/**
@@ -59,4 +53,10 @@ extern NSString *const XMPPStreamDidChangeMyJIDNotification;
**/
- (void)sendAuthElement:(NSXMLElement *)element;
+/**
+ * This method allows you to inject an element into the stream as if it was received on the socket.
+ * This is an advanced technique, but makes for some interesting possibilities.
+**/
+- (void)injectElement:(NSXMLElement *)element;
+
@end
View
24 Core/XMPPJID.h
@@ -27,6 +27,22 @@ typedef enum XMPPJIDCompareOptions XMPPJIDCompareOptions;
@property (strong, readonly) NSString *domain;
@property (strong, readonly) NSString *resource;
+/**
+ * Terminology (from RFC 6120):
+ *
+ * The term "bare JID" refers to an XMPP address of the form <localpart@domainpart> (for an account at a server)
+ * or of the form <domainpart> (for a server).
+ *
+ * The term "full JID" refers to an XMPP address of the form
+ * <localpart@domainpart/resourcepart> (for a particular authorized client or device associated with an account)
+ * or of the form <domainpart/resourcepart> (for a particular resource or script associated with a server).
+ *
+ * Thus a bareJID is one that does not have a resource.
+ * And a fullJID is one that does have a resource.
+ *
+ * For convenience, there are also methods that that check for a user component as well.
+**/
+
- (XMPPJID *)bareJID;
- (XMPPJID *)domainJID;
@@ -39,9 +55,17 @@ typedef enum XMPPJIDCompareOptions XMPPJIDCompareOptions;
- (BOOL)isFull;
- (BOOL)isFullWithUser;
+/**
+ * A server JID does not have a user component.
+**/
- (BOOL)isServer;
/**
+ * Returns a new jid with the given resource.
+**/
+- (XMPPJID *)jidWithNewResource:(NSString *)resource;
+
+/**
* When you know both objects are JIDs, this method is a faster way to check equality than isEqual:.
**/
- (BOOL)isEqualToJID:(XMPPJID *)aJID;
View
183 Core/XMPPJID.m
@@ -129,7 +129,8 @@ + (XMPPJID *)jidWithString:(NSString *)jidStr
+ (XMPPJID *)jidWithString:(NSString *)jidStr resource:(NSString *)resource
{
- if (![self validateResource:resource]) return nil;
+ NSString *prepResource = [LibIDN prepResource:resource];
+ if (![self validateResource:prepResource]) return nil;
NSString *user;
NSString *domain;
@@ -139,7 +140,7 @@ + (XMPPJID *)jidWithString:(NSString *)jidStr resource:(NSString *)resource
XMPPJID *jid = [[XMPPJID alloc] init];
jid->user = [user copy];
jid->domain = [domain copy];
- jid->resource = [resource copy];
+ jid->resource = [prepResource copy];
return jid;
}
@@ -166,7 +167,9 @@ + (XMPPJID *)jidWithUser:(NSString *)user domain:(NSString *)domain resource:(NS
return nil;
}
-+ (XMPPJID *)jidWithPrevalidatedUser:(NSString *)user domain:(NSString *)domain resource:(NSString *)resource
++ (XMPPJID *)jidWithPrevalidatedUser:(NSString *)user
+ prevalidatedDomain:(NSString *)domain
+ prevalidatedResource:(NSString *)resource
{
XMPPJID *jid = [[XMPPJID alloc] init];
jid->user = [user copy];
@@ -176,6 +179,21 @@ + (XMPPJID *)jidWithPrevalidatedUser:(NSString *)user domain:(NSString *)domain
return jid;
}
++ (XMPPJID *)jidWithPrevalidatedUser:(NSString *)user
+ prevalidatedDomain:(NSString *)domain
+ resource:(NSString *)resource
+{
+ NSString *prepResource = [LibIDN prepResource:resource];
+ if (![self validateResource:prepResource]) return nil;
+
+ XMPPJID *jid = [[XMPPJID alloc] init];
+ jid->user = [user copy];
+ jid->domain = [domain copy];
+ jid->resource = [prepResource copy];
+
+ return jid;
+}
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Encoding, Decoding:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -272,7 +290,7 @@ - (XMPPJID *)bareJID
}
else
{
- return [XMPPJID jidWithPrevalidatedUser:user domain:domain resource:nil];
+ return [XMPPJID jidWithPrevalidatedUser:user prevalidatedDomain:domain prevalidatedResource:nil];
}
}
@@ -284,7 +302,7 @@ - (XMPPJID *)domainJID
}
else
{
- return [XMPPJID jidWithPrevalidatedUser:nil domain:domain resource:nil];
+ return [XMPPJID jidWithPrevalidatedUser:nil prevalidatedDomain:domain prevalidatedResource:nil];
}
}
@@ -350,20 +368,171 @@ - (BOOL)isServer
return (user == nil);
}
+- (XMPPJID *)jidWithNewResource:(NSString *)newResource
+{
+ return [XMPPJID jidWithPrevalidatedUser:user prevalidatedDomain:domain resource:newResource];
+}
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark NSObject Methods:
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- (NSUInteger)hash
{
- return [[self full] hash];
+ // We used to do this:
+ // return [[self full] hash];
+ //
+ // It was functional but less than optimal because it required the creation of a new NSString everytime.
+ // Now the hashing of a string itself is extremely fast,
+ // so combining 3 hashes is much faster than creating a new string.
+ // To accomplish this we use the murmur hashing algorithm.
+ //
+ // MurmurHash2 was written by Austin Appleby, and is placed in the public domain.
+ // http://code.google.com/p/smhasher
+
+ NSUInteger uhash = [user hash];
+ NSUInteger dhash = [domain hash];
+ NSUInteger rhash = [resource hash];
+
+ if (NSUIntegerMax == UINT32_MAX) // Should be optimized out via compiler since these are constants
+ {
+ // MurmurHash2 (32-bit)
+ //
+ // uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed )
+ //
+ // Normally one would pass a chunk of data ('key') and associated data chunk length ('len').
+ // Instead we're going to use our 3 hashes.
+ // And we're going to randomly make up a 'seed'.
+
+ const uint32_t seed = 0xa2f1b6f; // Some random value I made up
+ const uint32_t len = 12; // 3 hashes, each 4 bytes = 12 bytes
+
+ // 'm' and 'r' are mixing constants generated offline.
+ // They're not really 'magic', they just happen to work well.
+
+ const uint32_t m = 0x5bd1e995;
+ const int r = 24;
+
+ // Initialize the hash to a 'random' value
+
+ uint32_t h = seed ^ len;
+ uint32_t k;
+
+ // Mix uhash
+
+ k = uhash;
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h *= m;
+ h ^= k;
+
+ // Mix dhash
+
+ k = dhash;
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h *= m;
+ h ^= k;
+
+ // Mix rhash
+
+ k = rhash;
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h *= m;
+ h ^= k;
+
+ // Do a few final mixes of the hash to ensure the last few
+ // bytes are well-incorporated.
+
+ h ^= h >> 13;
+ h *= m;
+ h ^= h >> 15;
+
+ return (NSUInteger)h;
+ }
+ else
+ {
+ // MurmurHash2 (64-bit)
+ //
+ // uint64_t MurmurHash64A ( const void * key, int len, uint64_t seed )
+ //
+ // Normally one would pass a chunk of data ('key') and associated data chunk length ('len').
+ // Instead we're going to use our 3 hashes.
+ // And we're going to randomly make up a 'seed'.
+
+ const uint32_t seed = 0xa2f1b6f; // Some random value I made up
+ const uint32_t len = 24; // 3 hashes, each 8 bytes = 24 bytes
+
+ // 'm' and 'r' are mixing constants generated offline.
+ // They're not really 'magic', they just happen to work well.
+
+ const uint64_t m = 0xc6a4a7935bd1e995LLU;
+ const int r = 47;
+
+ // Initialize the hash to a 'random' value
+
+ uint64_t h = seed ^ (len * m);
+ uint64_t k;
+
+ // Mix uhash
+
+ k = uhash;
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h ^= k;
+ h *= m;
+
+ // Mix dhash
+
+ k = dhash;
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h ^= k;
+ h *= m;
+
+ // Mix rhash
+
+ k = rhash;
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h ^= k;
+ h *= m;
+
+ // Do a few final mixes of the hash to ensure the last few
+ // bytes are well-incorporated.
+
+ h ^= h >> r;
+ h *= m;
+ h ^= h >> r;
+
+ return (NSUInteger)h;
+ }
}
- (BOOL)isEqual:(id)anObject
{
if ([anObject isMemberOfClass:[self class]])
{
- return [self isEqualToJID:(XMPPJID *)anObject];
+ return [self isEqualToJID:(XMPPJID *)anObject options:XMPPJIDCompareFull];
}
return NO;
}
View
51 Core/XMPPLogging.h
@@ -61,6 +61,12 @@
#import "DDLog.h"
+// Global flag to enable/disable logging throughout the entire xmpp framework.
+
+#ifndef XMPP_LOGGING_ENABLED
+#define XMPP_LOGGING_ENABLED 1
+#endif
+
// Define logging context for every log message coming from the XMPP framework.
// The logging context can be extracted from the DDLogMessage from within the logging framework.
// This gives loggers, formatters, and filters the ability to optionally process them differently.
@@ -88,7 +94,6 @@
#define XMPP_LOG_FLAG_TRACE (1 << 4) // 0...10000
-
// Setup the usual boolean macros.
#define XMPP_LOG_ERROR (xmppLogLevel & XMPP_LOG_FLAG_ERROR)
@@ -114,42 +119,50 @@
#define XMPP_LOG_ASYNC_TRACE (YES && XMPP_LOG_ASYNC_ENABLED)
// Define logging primitives.
+// These are primarily wrappers around the macros defined in Lumberjack's DDLog.h header file.
+
+#define XMPP_LOG_OBJC_MAYBE(async, lvl, flg, ctx, frmt, ...) \
+ do{ if(XMPP_LOGGING_ENABLED) LOG_MAYBE(async, lvl, flg, ctx, sel_getName(_cmd), frmt, ##__VA_ARGS__); } while(0)
+
+#define XMPP_LOG_C_MAYBE(async, lvl, flg, ctx, frmt, ...) \
+ do{ if(XMPP_LOGGING_ENABLED) LOG_MAYBE(async, lvl, flg, ctx, __FUNCTION__, frmt, ##__VA_ARGS__); } while(0)
+
-#define XMPPLogError(frmt, ...) LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_ERROR, xmppLogLevel, XMPP_LOG_FLAG_ERROR, \
+#define XMPPLogError(frmt, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_ERROR, xmppLogLevel, XMPP_LOG_FLAG_ERROR, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
-#define XMPPLogWarn(frmt, ...) LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_WARN, xmppLogLevel, XMPP_LOG_FLAG_WARN, \
+#define XMPPLogWarn(frmt, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_WARN, xmppLogLevel, XMPP_LOG_FLAG_WARN, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
-#define XMPPLogInfo(frmt, ...) LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_INFO, xmppLogLevel, XMPP_LOG_FLAG_INFO, \
+#define XMPPLogInfo(frmt, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_INFO, xmppLogLevel, XMPP_LOG_FLAG_INFO, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
-#define XMPPLogVerbose(frmt, ...) LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_VERBOSE, xmppLogLevel, XMPP_LOG_FLAG_VERBOSE, \
+#define XMPPLogVerbose(frmt, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_VERBOSE, xmppLogLevel, XMPP_LOG_FLAG_VERBOSE, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
-#define XMPPLogTrace() LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_TRACE, xmppLogLevel, XMPP_LOG_FLAG_TRACE, \
+#define XMPPLogTrace() XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_TRACE, xmppLogLevel, XMPP_LOG_FLAG_TRACE, \
XMPP_LOG_CONTEXT, @"%@: %@", THIS_FILE, THIS_METHOD)
-#define XMPPLogTrace2(frmt, ...) LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_TRACE, xmppLogLevel, XMPP_LOG_FLAG_TRACE, \
+#define XMPPLogTrace2(frmt, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_TRACE, xmppLogLevel, XMPP_LOG_FLAG_TRACE, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
-#define XMPPLogCError(frmt, ...) LOG_C_MAYBE(XMPP_LOG_ASYNC_ERROR, xmppLogLevel, XMPP_LOG_FLAG_ERROR, \
+#define XMPPLogCError(frmt, ...) XMPP_LOG_C_MAYBE(XMPP_LOG_ASYNC_ERROR, xmppLogLevel, XMPP_LOG_FLAG_ERROR, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
-#define XMPPLogCWarn(frmt, ...) LOG_C_MAYBE(XMPP_LOG_ASYNC_WARN, xmppLogLevel, XMPP_LOG_FLAG_WARN, \
+#define XMPPLogCWarn(frmt, ...) XMPP_LOG_C_MAYBE(XMPP_LOG_ASYNC_WARN, xmppLogLevel, XMPP_LOG_FLAG_WARN, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
-#define XMPPLogCInfo(frmt, ...) LOG_C_MAYBE(XMPP_LOG_ASYNC_INFO, xmppLogLevel, XMPP_LOG_FLAG_INFO, \
+#define XMPPLogCInfo(frmt, ...) XMPP_LOG_C_MAYBE(XMPP_LOG_ASYNC_INFO, xmppLogLevel, XMPP_LOG_FLAG_INFO, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
-#define XMPPLogCVerbose(frmt, ...) LOG_C_MAYBE(XMPP_LOG_ASYNC_VERBOSE, xmppLogLevel, XMPP_LOG_FLAG_VERBOSE, \
+#define XMPPLogCVerbose(frmt, ...) XMPP_LOG_C_MAYBE(XMPP_LOG_ASYNC_VERBOSE, xmppLogLevel, XMPP_LOG_FLAG_VERBOSE, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
-#define XMPPLogCTrace() LOG_C_MAYBE(XMPP_LOG_ASYNC_TRACE, xmppLogLevel, XMPP_LOG_FLAG_TRACE, \
+#define XMPPLogCTrace() XMPP_LOG_C_MAYBE(XMPP_LOG_ASYNC_TRACE, xmppLogLevel, XMPP_LOG_FLAG_TRACE, \
XMPP_LOG_CONTEXT, @"%@: %s", THIS_FILE, __FUNCTION__)
-#define XMPPLogCTrace2(frmt, ...) LOG_C_MAYBE(XMPP_LOG_ASYNC_TRACE, xmppLogLevel, XMPP_LOG_FLAG_TRACE, \
+#define XMPPLogCTrace2(frmt, ...) XMPP_LOG_C_MAYBE(XMPP_LOG_ASYNC_TRACE, xmppLogLevel, XMPP_LOG_FLAG_TRACE, \
XMPP_LOG_CONTEXT, frmt, ##__VA_ARGS__)
// Setup logging for XMPPStream (and subclasses such as XMPPStreamFacebook)
@@ -168,11 +181,11 @@
#define XMPP_LOG_ASYNC_RECV_PRE (YES && XMPP_LOG_ASYNC_ENABLED)
#define XMPP_LOG_ASYNC_RECV_POST (YES && XMPP_LOG_ASYNC_ENABLED)
-#define XMPPLogSend(format, ...) LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_SEND, xmppLogLevel, XMPP_LOG_FLAG_SEND, \
- XMPP_LOG_CONTEXT, format, ##__VA_ARGS__)
+#define XMPPLogSend(format, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_SEND, xmppLogLevel, \
+ XMPP_LOG_FLAG_SEND, XMPP_LOG_CONTEXT, format, ##__VA_ARGS__)
-#define XMPPLogRecvPre(format, ...) LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_RECV_PRE, xmppLogLevel, XMPP_LOG_FLAG_RECV_PRE, \
- XMPP_LOG_CONTEXT, format, ##__VA_ARGS__)
+#define XMPPLogRecvPre(format, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_RECV_PRE, xmppLogLevel, \
+ XMPP_LOG_FLAG_RECV_PRE, XMPP_LOG_CONTEXT, format, ##__VA_ARGS__)
-#define XMPPLogRecvPost(format, ...) LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_RECV_POST, xmppLogLevel, XMPP_LOG_FLAG_RECV_POST, \
- XMPP_LOG_CONTEXT, format, ##__VA_ARGS__)
+#define XMPPLogRecvPost(format, ...) XMPP_LOG_OBJC_MAYBE(XMPP_LOG_ASYNC_RECV_POST, xmppLogLevel, \
+ XMPP_LOG_FLAG_RECV_POST, XMPP_LOG_CONTEXT, format, ##__VA_ARGS__)
View
6 Core/XMPPMessage.m
@@ -86,7 +86,7 @@ - (BOOL)isChatMessage
- (BOOL)isChatMessageWithBody
{
- if([self isChatMessage])
+ if ([self isChatMessage])
{
return [self isMessageWithBody];
}
@@ -112,9 +112,7 @@ - (NSError *)errorMessage {
- (BOOL)isMessageWithBody
{
- NSString *body = [[self elementForName:@"body"] stringValue];
-
- return ([body length] > 0);
+ return ([self elementForName:@"body"] != nil);
}
@end
View
2  Core/XMPPModule.h
@@ -15,7 +15,7 @@
**/
@interface XMPPModule : NSObject
{
- __strong XMPPStream *xmppStream;
+ XMPPStream *xmppStream;
dispatch_queue_t moduleQueue;
id multicastDelegate;
View
80 Core/XMPPModule.m
@@ -6,6 +6,32 @@
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
+/**
+ * Does ARC support support GCD objects?
+ * It does if the minimum deployment target is iOS 6+ or Mac OS X 10.8+
+**/
+#if TARGET_OS_IPHONE
+
+ // Compiling for iOS
+
+ #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 0
+ #else // iOS 5.X or earlier
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 1
+ #endif
+
+#else
+
+ // Compiling for Mac OS X
+
+ #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 0
+ #else
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier
+ #endif
+
+#endif
+
// Log levels: off, error, warn, info, verbose
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
@@ -34,7 +60,9 @@ - (id)initWithDispatchQueue:(dispatch_queue_t)queue
if (queue)
{
moduleQueue = queue;
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
dispatch_retain(moduleQueue);
+ #endif
}
else
{
@@ -49,42 +77,9 @@ - (id)initWithDispatchQueue:(dispatch_queue_t)queue
- (void)dealloc
{
- if (xmppStream)
- {
- // It is dangerous to rely on the dealloc method to deactivate a module.
- //
- // Why?
- // Because when a module is activated, it is added as a delegate to the xmpp stream.
- // In addition to this, the module may be added as a delegate to various other xmpp components.
- // As per usual, these delegate references do NOT retain this module.
- // This means that modules may get invoked after they are deallocated.
- //
- // Consider the following example:
- //
- // 1. Thread A: Module is created (alloc/init) (retainCount = 1)
- // 2. Thread A: Module is activated (retainCount = 1)
- // 3. Thread A: Module is released, and dealloc is called.
- // First [MyCustomModule dealloc] is invoked.
- // Then [XMPPModule dealloc] is invoked.
- // Only at this point is [XMPPModule deactivate] run.
- // Since the deactivate method is synchronous,
- // it blocks until the module is removed as a delegate from the stream and all other modules.
- // 4. Thread B: Invokes a delegate method on our module ([XMPPModule deactivate] is waiting on thread B).
- // 5. Thread A: The [XMPPModule deactivate] method returns, but the damage is done.
- // Thread B has asynchronously dispatched a delegate method set to run on our module.
- // 6. Thread A: The dealloc method completes, and our module is now deallocated.
- // 7. Thread C: The delegate method attempts to run on our module, which is deallocated,
- // the application crashes, the computer blows up, and a meteor hits your favorite restaurant.
-
- XMPPLogWarn(@"%@: Deallocating activated module. You should deactivate modules before their final release.",
- NSStringFromClass([self class]));
-
- [self deactivate];
- }
-
-
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
dispatch_release(moduleQueue);
-
+ #endif
}
/**
@@ -148,20 +143,7 @@ - (void)deactivate
- (dispatch_queue_t)moduleQueue
{
- if (dispatch_get_current_queue() == moduleQueue)
- {
- return moduleQueue;
- }
- else
- {
- __block dispatch_queue_t result;
-
- dispatch_sync(moduleQueue, ^{
- result = moduleQueue;
- });
-
- return result;
- }
+ return moduleQueue;
}
- (XMPPStream *)xmppStream
View
21 Core/XMPPParser.h
@@ -1,5 +1,4 @@
#import <Foundation/Foundation.h>
-#import <libxml2/libxml/parser.h>
#if TARGET_OS_IPHONE
#import "DDXML.h"
@@ -7,23 +6,15 @@
@interface XMPPParser : NSObject
-{
- id delegate;
-
- BOOL hasReportedRoot;
- unsigned depth;
-
- xmlParserCtxt *parserCtxt;
-}
-- (id)initWithDelegate:(id)delegate;
+- (id)initWithDelegate:(id)delegate delegateQueue:(dispatch_queue_t)dq;
+- (id)initWithDelegate:(id)delegate delegateQueue:(dispatch_queue_t)dq parserQueue:(dispatch_queue_t)pq;
-- (id)delegate;
-- (void)setDelegate:(id)delegate;
+- (void)setDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
/**
- * Synchronously parses the given data.
- * This means the delegate methods will get called before this method returns.
+ * Asynchronously parses the given data.
+ * The delegate methods will be dispatch_async'd as events occur.
**/
- (void)parseData:(NSData *)data;
@@ -44,4 +35,6 @@
- (void)xmppParser:(XMPPParser *)sender didFail:(NSError *)error;
+- (void)xmppParserDidParseData:(XMPPParser *)sender;
+
@end
View
343 Core/XMPPParser.m
@@ -1,5 +1,6 @@
#import "XMPPParser.h"
#import "XMPPLogging.h"
+#import <libxml/parser.h>
#import <libxml/parserInternals.h>
#if TARGET_OS_IPHONE
@@ -10,6 +11,32 @@
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
+/**
+ * Does ARC support support GCD objects?
+ * It does if the minimum deployment target is iOS 6+ or Mac OS X 10.8+
+**/
+#if TARGET_OS_IPHONE
+
+ // Compiling for iOS
+
+ #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 0
+ #else // iOS 5.X or earlier
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 1
+ #endif
+
+#else
+
+ // Compiling for Mac OS X
+
+ #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 0
+ #else
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier
+ #endif
+
+#endif
+
// Log levels: off, error, warn, info, verbose
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_VERBOSE;
@@ -17,24 +44,9 @@
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
-
-// When the xmpp parser invokes a delegate method, such as xmppParser:didReadElement:,
-// it exposes itself to the possibility of exceptions mid-parse.
-// This aborts the current run loop,
-// and thus causes the parser to lose the rest of the data that was passed to it via the parseData method.
-//
-// The end result is that our parser will likely barf the next time it tries to parse data.
-// Probably with a "EndTag: '</' not found" error.
-// After this the xmpp stream would be closed.
-//
-// Now during development, it's probably good to be exposed to these exceptions so they can be tracked down and fixed.
-// But for release, we might not want these exceptions to break the xmpp stream.
-// So for release mode you may consider enabling the try/catch.
-#define USE_TRY_CATCH 0
-
#define CHECK_FOR_NULL(value) \
do { \
- if(value == NULL) { \
+ if (value == NULL) { \
xmpp_xmlAbortDueToMemoryShortage(ctxt); \
return; \
} \
@@ -46,6 +58,21 @@
@implementation XMPPParser
+{
+ #if __has_feature(objc_arc_weak)
+ __weak id delegate;
+ #else
+ __unsafe_unretained id delegate;
+ #endif
+ dispatch_queue_t delegateQueue;
+
+ dispatch_queue_t parserQueue;
+
+ BOOL hasReportedRoot;
+ unsigned depth;
+
+ xmlParserCtxt *parserCtxt;
+}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark iPhone
@@ -55,7 +82,7 @@ @implementation XMPPParser
static void xmpp_onDidReadRoot(XMPPParser *parser, xmlNodePtr root)
{
- if([parser->delegate respondsToSelector:@selector(xmppParser:didReadRoot:)])
+ if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didReadRoot:)])
{
// We first copy the root node.
// We do this to allow the delegate to retain and make changes to the reported root
@@ -72,22 +99,13 @@ static void xmpp_onDidReadRoot(XMPPParser *parser, xmlNodePtr root)
xmlNodePtr rootCopy = xmlCopyNode(root, 2);
DDXMLElement *rootCopyWrapper = [DDXMLElement nodeWithElementPrimitive:rootCopy owner:nil];
-#if USE_TRY_CATCH
- @try
- {
- // If the delegate throws an exception that we don't catch,
- // this would cause our parser to abort,
- // and ignore the rest of the data that was passed to us in parseData.
- //
- // The end result is that our parser will likely barf the next time it tries to parse data.
- // Probably with a "EndTag: '</' not found" error.
+ __strong id theDelegate = parser->delegate;
+
+ dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
- [parser->delegate xmppParser:parser didReadRoot:rootCopyWrapper];
- }
- @catch (id exception) { /* Ignore */ }
-#else
- [parser->delegate xmppParser:parser didReadRoot:rootCopyWrapper];
-#endif
+ [theDelegate xmppParser:parser didReadRoot:rootCopyWrapper];
+ }});
+
// Note: DDXMLElement will properly free the rootCopy when it's deallocated.
}
}
@@ -110,24 +128,14 @@ static void xmpp_onDidReadElement(XMPPParser *parser, xmlNodePtr child)
// Note: We want to detach the child from the root even if the delegate method isn't setup.
// This prevents the doc from growing infinitely large.
- if([parser->delegate respondsToSelector:@selector(xmppParser:didReadElement:)])
+ if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didReadElement:)])
{
-#if USE_TRY_CATCH
- @try
- {
- // If the delegate throws an exception that we don't catch,
- // this would cause our parser to abort,
- // and ignore the rest of the data that was passed to us in parseData.
- //
- // The end result is that our parser will likely barf the next time it tries to parse data.
- // Probably with a "EndTag: '</' not found" error.
+ __strong id theDelegate = parser->delegate;
+
+ dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
- [parser->delegate xmppParser:parser didReadElement:childWrapper];
- }
- @catch (id exception) { /* Ignore */ }
-#else
- [parser->delegate xmppParser:parser didReadElement:childWrapper];
-#endif
+ [theDelegate xmppParser:parser didReadElement:childWrapper];
+ }});
}
// Note: DDXMLElement will properly free the child when it's deallocated.
@@ -143,13 +151,13 @@ static void xmpp_setName(NSXMLElement *element, xmlNodePtr node)
{
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
- if(node->name == NULL)
+ if (node->name == NULL)
{
[element setName:@""];
return;
}
- if((node->ns != NULL) && (node->ns->prefix != NULL))
+ if ((node->ns != NULL) && (node->ns->prefix != NULL))
{
// E.g: <deusty:element xmlns:deusty="deusty.com"/>
@@ -172,9 +180,9 @@ static void xmpp_addNamespaces(NSXMLElement *element, xmlNodePtr node)
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
xmlNsPtr nsNode = node->nsDef;
- while(nsNode != NULL)
+ while (nsNode != NULL)
{
- if(nsNode->href == NULL)
+ if (nsNode->href == NULL)
{
// Namespace doesn't have a value!
}
@@ -182,7 +190,7 @@ static void xmpp_addNamespaces(NSXMLElement *element, xmlNodePtr node)
{
NSXMLNode *ns = [[NSXMLNode alloc] initWithKind:NSXMLNamespaceKind];
- if(nsNode->prefix != NULL)
+ if (nsNode->prefix != NULL)
{
NSString *nsName = [[NSString alloc] initWithUTF8String:(const char *)nsNode->prefix];
[ns setName:nsName];
@@ -210,15 +218,15 @@ static void xmpp_addChildren(NSXMLElement *element, xmlNodePtr node)
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
xmlNodePtr childNode = node->children;
- while(childNode != NULL)
+ while (childNode != NULL)
{
- if(childNode->type == XML_ELEMENT_NODE)
+ if (childNode->type == XML_ELEMENT_NODE)
{
xmpp_recursiveAddChild(element, childNode);
}
- else if(childNode->type == XML_TEXT_NODE)
+ else if (childNode->type == XML_TEXT_NODE)
{
- if(childNode->content != NULL)
+ if (childNode->content != NULL)
{
NSString *value = [[NSString alloc] initWithUTF8String:(const char *)childNode->content];
[element setStringValue:value];
@@ -234,17 +242,17 @@ static void xmpp_addAttributes(NSXMLElement *element, xmlNodePtr node)
// Remember: The NSString initWithUTF8String raises an exception if passed NULL
xmlAttrPtr attrNode = node->properties;
- while(attrNode != NULL)
+ while (attrNode != NULL)
{
- if(attrNode->name == NULL)
+ if (attrNode->name == NULL)
{
// Attribute doesn't have a name!
}
- else if(attrNode->children == NULL)
+ else if (attrNode->children == NULL)
{
// Attribute doesn't have a value node!
}
- else if(attrNode->children->content == NULL)
+ else if (attrNode->children->content == NULL)
{
// Attribute doesn't have a value!
}
@@ -252,7 +260,7 @@ static void xmpp_addAttributes(NSXMLElement *element, xmlNodePtr node)
{
NSXMLNode *attr = [[NSXMLNode alloc] initWithKind:NSXMLAttributeKind];
- if((attrNode->ns != NULL) && (attrNode->ns->prefix != NULL))
+ if ((attrNode->ns != NULL) && (attrNode->ns->prefix != NULL))
{
// E.g: <element xmlns:deusty="deusty.com" deusty:attr="value"/>
@@ -323,51 +331,31 @@ static void xmpp_recursiveAddChild(NSXMLElement *parent, xmlNodePtr childNode)
static void xmpp_onDidReadRoot(XMPPParser *parser, xmlNodePtr root)
{
- if([parser->delegate respondsToSelector:@selector(xmppParser:didReadRoot:)])
+ if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didReadRoot:)])
{
NSXMLElement *nsRoot = xmpp_nsxmlFromLibxml(root);
-#if USE_TRY_CATCH
- @try
- {
- // If the delegate throws an exception that we don't catch,
- // this would cause our parser to abort,
- // and ignore the rest of the data that was passed to us in parseData.
- //
- // The end result is that our parser will likely barf the next time it tries to parse data.
- // Probably with a "EndTag: '</' not found" error.
+ __strong id theDelegate = parser->delegate;
+
+ dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
- [parser->delegate xmppParser:parser didReadRoot:nsRoot];
- }
- @catch (id exception) { /* Ignore */ }
-#else
- [parser->delegate xmppParser:parser didReadRoot:nsRoot];
-#endif
+ [theDelegate xmppParser:parser didReadRoot:nsRoot];
+ }});
}
}
static void xmpp_onDidReadElement(XMPPParser *parser, xmlNodePtr child)
{
- if([parser->delegate respondsToSelector:@selector(xmppParser:didReadElement:)])
+ if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didReadElement:)])
{
NSXMLElement *nsChild = xmpp_nsxmlFromLibxml(child);
-#if USE_TRY_CATCH
- @try
- {
- // If the delegate throws an exception that we don't catch,
- // this would cause our parser to abort,
- // and ignore the rest of the data that was passed to us in parseData.
- //
- // The end result is that our parser will likely barf the next time it tries to parse data.
- // Probably with a "EndTag: '</' not found" error.
-
- [parser->delegate xmppParser:parser didReadElement:nsChild];
- }
- @catch (id exception) { /* Ignore */ }
-#else
- [parser->delegate xmppParser:parser didReadElement:nsChild];
-#endif
+ __strong id theDelegate = parser->delegate;
+
+ dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate xmppParser:parser didReadElement:nsChild];
+ }});
}
// Note: We want to detach the child from the root even if the delegate method isn't setup.
@@ -393,14 +381,14 @@ static void xmpp_postStartElement(xmlParserCtxt *ctxt)
XMPPParser *parser = (__bridge XMPPParser *)ctxt->_private;
parser->depth++;
- if(!(parser->hasReportedRoot) && (parser->depth == 1))
+ if (!(parser->hasReportedRoot) && (parser->depth == 1))
{
// We've received the full root - report it to the delegate
- if(ctxt->myDoc)
+ if (ctxt->myDoc)
{
xmlNodePtr root = xmlDocGetRootElement(ctxt->myDoc);
- if(root)
+ if (root)
{
xmpp_onDidReadRoot(parser, root);
@@ -419,7 +407,7 @@ static void xmpp_postEndElement(xmlParserCtxt *ctxt)
XMPPParser *parser = (__bridge XMPPParser *)ctxt->_private;
parser->depth--;
- if(parser->depth == 1)
+ if (parser->depth == 1)
{
// End of full xmpp element.
// That is, a child of the root element.
@@ -429,9 +417,9 @@ static void xmpp_postEndElement(xmlParserCtxt *ctxt)
xmlNodePtr root = xmlDocGetRootElement(doc);
xmlNodePtr child = root->children;
- while(child != NULL)
+ while (child != NULL)
{
- if(child->type == XML_ELEMENT_NODE)
+ if (child->type == XML_ELEMENT_NODE)
{
xmpp_onDidReadElement(parser, child);
@@ -442,13 +430,18 @@ static void xmpp_postEndElement(xmlParserCtxt *ctxt)
child = child->next;
}
}
- else if(parser->depth == 0)
+ else if (parser->depth == 0)
{
// End of the root element
- if([parser->delegate respondsToSelector:@selector(xmppParserDidEnd:)])
+ if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParserDidEnd:)])
{
- [parser->delegate xmppParserDidEnd:parser];
+ __strong id theDelegate = parser->delegate;
+
+ dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate xmppParserDidEnd:parser];
+ }});
}
}
}
@@ -462,14 +455,19 @@ static void xmpp_xmlAbortDueToMemoryShortage(xmlParserCtxt *ctxt)
xmlStopParser(ctxt);
- if([parser->delegate respondsToSelector:@selector(xmppParser:didFail:)])
+ if (parser->delegateQueue && [parser->delegate respondsToSelector:@selector(xmppParser:didFail:)])
{
NSString *errMsg = @"Unable to allocate memory in xmpp parser";
NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
NSError *error = [NSError errorWithDomain:@"libxmlErrorDomain" code:1001 userInfo:info];
- [parser->delegate xmppParser:parser didFail:error];
+ __strong id theDelegate = parser->delegate;
+
+ dispatch_async(parser->delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate xmppParser:parser didFail:error];
+ }});
}
}
@@ -544,7 +542,7 @@ static void xmpp_xmlStartElement(void *ctx, const xmlChar *nodeName,
CHECK_FOR_NULL(newNode);
// Add the node to the tree
- if(parent == NULL)
+ if (parent == NULL)
{
// Root node
xmlAddChild((xmlNodePtr)ctxt->myDoc, newNode);
@@ -572,7 +570,8 @@ static void xmpp_xmlStartElement(void *ctx, const xmlChar *nodeName,
if (newNode->nsDef == NULL)
{
- newNode->nsDef = lastAddedNs = newNs;
+ newNode->nsDef = newNs;
+ lastAddedNs = newNs;
}
else
{
@@ -608,12 +607,11 @@ static void xmpp_xmlStartElement(void *ctx, const xmlChar *nodeName,
if (newNode->nsDef == NULL)
{
- newNode->nsDef = lastAddedNs = newNs;
+ newNode->nsDef = newNs;
}
else
{
lastAddedNs->next = newNs;
- lastAddedNs = newNs;
}
newNode->ns = newNs;
@@ -715,11 +713,33 @@ static void xmpp_xmlEndElement(void *ctx, const xmlChar *localname,
xmpp_postEndElement(ctxt);
}
-- (id)initWithDelegate:(id)aDelegate
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq
+{
+ return [self initWithDelegate:aDelegate delegateQueue:dq parserQueue:NULL];
+}
+
+- (id)initWithDelegate:(id)aDelegate delegateQueue:(dispatch_queue_t)dq parserQueue:(dispatch_queue_t)pq
{
if ((self = [super init]))
{
delegate = aDelegate;
+ delegateQueue = dq;
+
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
+ if (delegateQueue)
+ dispatch_retain(delegateQueue);
+ #endif
+
+ if (pq) {
+ parserQueue = pq;
+
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
+ dispatch_retain(parserQueue);
+ #endif
+ }
+ else {
+ parserQueue = dispatch_queue_create("xmpp.parser", NULL);
+ }
hasReportedRoot = NO;
depth = 0;
@@ -756,7 +776,6 @@ - (void)dealloc
if (parserCtxt)
{
// The xmlFreeParserCtxt method will not free the created document in parserCtxt->myDoc.
-
if (parserCtxt->myDoc)
{
// Free the created xmlDoc
@@ -766,51 +785,91 @@ - (void)dealloc
xmlFreeParserCtxt(parserCtxt);
}
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
+ if (delegateQueue)
+ dispatch_release(delegateQueue);
+ if (parserQueue)
+ dispatch_release(parserQueue);
+ #endif
}
-- (id)delegate {
- return delegate;
-}
-- (void)setDelegate:(id)aDelegate {
- delegate = aDelegate;
+- (void)setDelegate:(id)newDelegate delegateQueue:(dispatch_queue_t)newDelegateQueue
+{
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
+ if (newDelegateQueue)
+ dispatch_retain(newDelegateQueue);
+ #endif
+
+ dispatch_block_t block = ^{
+
+ delegate = newDelegate;
+
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
+ if (delegateQueue)
+ dispatch_release(delegateQueue);
+ #endif
+
+ delegateQueue = newDelegateQueue;
+ };
+
+ if (dispatch_get_current_queue() == parserQueue)
+ block();
+ else
+ dispatch_async(parserQueue, block);
}
- (void)parseData:(NSData *)data
{
- // The xmlParseChunk method below will cause the delegate methods to be invoked before this method returns.
- // If the delegate subsequently attempts to release us in one of those methods, and our dealloc method
- // gets invoked, then the parserCtxt will be freed in the middle of the xmlParseChunk method.
- // This often has the effect of crashing the application.
- // To get around this problem we simply retain/release within the method.
- XMPPParser *selfRetain = self;
-
- int result = xmlParseChunk(parserCtxt, (const char *)[data bytes], (int)[data length], 0);
+ dispatch_block_t block = ^{ @autoreleasepool {
- if(result != 0)
- {
- if([delegate respondsToSelector:@selector(xmppParser:didFail:)])
+ int result = xmlParseChunk(parserCtxt, (const char *)[data bytes], (int)[data length], 0);
+
+ if (result == 0)
{
- NSError *error;
-
- xmlError *xmlErr = xmlCtxtGetLastError(parserCtxt);
-
- if(xmlErr->message)
+ if (delegateQueue && [delegate respondsToSelector:@selector(xmppParserDidParseData:)])
{
- NSString *errMsg = [NSString stringWithFormat:@"%s", xmlErr->message];
- NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+ __strong id theDelegate = delegate;
- error = [NSError errorWithDomain:@"libxmlErrorDomain" code:xmlErr->code userInfo:info];
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate xmppParserDidParseData:self];
+ }});
}
- else
+ }
+ else
+ {
+ if (delegateQueue && [delegate respondsToSelector:@selector(xmppParser:didFail:)])
{
- error = [NSError errorWithDomain:@"libxmlErrorDomain" code:xmlErr->code userInfo:nil];
+ NSError *error;
+
+ xmlError *xmlErr = xmlCtxtGetLastError(parserCtxt);
+
+ if (xmlErr->message)
+ {
+ NSString *errMsg = [NSString stringWithFormat:@"%s", xmlErr->message];
+ NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
+
+ error = [NSError errorWithDomain:@"libxmlErrorDomain" code:xmlErr->code userInfo:info];
+ }
+ else
+ {
+ error = [NSError errorWithDomain:@"libxmlErrorDomain" code:xmlErr->code userInfo:nil];
+ }
+
+ __strong id theDelegate = delegate;
+
+ dispatch_async(delegateQueue, ^{ @autoreleasepool {
+
+ [theDelegate xmppParser:self didFail:error];
+ }});
}
-
- [delegate xmppParser:self didFail:error];
}
- }
+ }};
- selfRetain = nil;
+ if (dispatch_get_current_queue() == parserQueue)
+ block();
+ else
+ dispatch_async(parserQueue, block);
}
@end
View
143 Core/XMPPStream.h
@@ -8,7 +8,6 @@
#endif
@class XMPPSRVResolver;
-@class DDList;
@class XMPPParser;
@class XMPPJID;
@class XMPPIQ;
@@ -40,56 +39,6 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
@interface XMPPStream : NSObject <GCDAsyncSocketDelegate>
-{
- dispatch_queue_t xmppQueue;
- dispatch_queue_t parserQueue;
-
- GCDMulticastDelegate <XMPPStreamDelegate> *multicastDelegate;
-
- int state;
-
- GCDAsyncSocket *asyncSocket;
- NSMutableData *socketBuffer;
-
- UInt64 numberOfBytesSent;
- UInt64 numberOfBytesReceived;
-
- XMPPParser *parser;
- NSError *parserError;
-
- Byte flags;
- Byte config;
-
- NSString *hostName;
- UInt16 hostPort;
-
- id <XMPPSASLAuthentication> auth;
-
- XMPPJID *myJID_setByClient;
- XMPPJID *myJID_setByServer;
- XMPPJID *remoteJID;
-
- XMPPPresence *myPresence;
- NSXMLElement *rootElement;
-
- NSTimeInterval keepAliveInterval;
- dispatch_source_t keepAliveTimer;
- NSTimeInterval lastSendReceiveTime;
-
- DDList *registeredModules;
- NSMutableDictionary *autoDelegateDict;
-
- XMPPSRVResolver *srvResolver;
- NSArray *srvResults;
- NSUInteger srvResultsIndex;
-
- NSMutableArray *receipts;
-
- NSThread *xmppUtilityThread;
- NSRunLoop *xmppUtilityRunLoop;
-
- id userTag;
-}
/**
* Standard XMPP initialization.
@@ -212,6 +161,17 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
@property (readwrite, assign) NSTimeInterval keepAliveInterval;
/**
+ * The keep-alive mechanism sends whitespace which is ignored by the xmpp protocol.
+ * The default whitespace character is a space (' ').
+ *
+ * This can be changed, for whatever reason, to another whitespace character.
+ * Valid whitespace characters are space(' '), tab('\t') and newline('\n').
+ *
+ * If you attempt to set the character to any non-whitespace character, the attempt is ignored.
+**/
+@property (readwrite, assign) char keepAliveWhitespaceCharacter;
+
+/**
* Represents the last sent presence element concerning the presence of myJID on the server.
* In other words, it represents the presence as others see us.
*
@@ -319,18 +279,23 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
/**
* Disconnects from the remote host by closing the underlying TCP socket connection.
+ * The terminating </stream:stream> element is not sent to the server.
*
- * The disconnect method is synchronous.
+ * This method is synchronous.
* Meaning that the disconnect will happen immediately, even if there are pending elements yet to be sent.
- * The xmppStreamDidDisconnect:withError: method will be invoked before the disconnect method returns.
*
- * The disconnectAfterSending method is asynchronous.
- * The disconnect will happen after all pending elements have been sent.
- * Attempting to send elements after this method is called will not result in the elements getting sent.
- * The disconnectAfterSending method will return immediately,
- * and the xmppStreamDidDisconnect:withError: delegate method will be invoked at a later time.
+ * The xmppStreamDidDisconnect:withError: delegate method will immediately be dispatched onto the delegate queue.
**/
- (void)disconnect;
+
+/**
+ * Disconnects from the remote host by sending the terminating </stream:stream> element,
+ * and then closing the underlying TCP socket connection.
+ *
+ * This method is asynchronous.
+ * The disconnect will happen after all pending elements have been sent.
+ * Attempting to send elements after this method has been called will not work (the elements won't get sent).
+**/
- (void)disconnectAfterSending;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -793,11 +758,49 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
- (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(NSXMLElement *)error;
/**
+ * This method is called if the XMPP server doesn't allow our resource of choice
+ * because it conflicts with an existing resource.
+ *
+ * Return an alternative resource or return nil to let the server automatically pick a resource for us.
+**/
+- (NSString *)xmppStream:(XMPPStream *)sender alternativeResourceForConflictingResource:(NSString *)conflictingResource;
+
+/**
+ * These methods are called before their respective XML elements are broadcast as received to the rest of the stack.
+ * These methods can be used to modify elements on the fly.
+ * (E.g. perform custom decryption so the rest of the stack sees readable text.)
+ *
+ * You may also filter incoming elements by returning nil.
+ *
+ * When implementing these methods to modify the element, you do not need to copy the given element.
+ * You can simply edit the given element, and return it.
+ * The reason these methods return an element, instead of void, is to allow filtering.
+ *
+ * Concerning thread-safety, delegates implementing the method are invoked one-at-a-time to
+ * allow thread-safe modification of the given elements.
+ *
+ * You should NOT implement these methods unless you have good reason to do so.
+ * For general processing and notification of received elements, please use xmppStream:didReceiveX: methods.
+ *
+ * @see xmppStream:didReceiveIQ:
+ * @see xmppStream:didReceiveMessage:
+ * @see xmppStream:didReceivePresence:
+**/
+- (XMPPIQ *)xmppStream:(XMPPStream *)sender willReceiveIQ:(XMPPIQ *)iq;
+- (XMPPMessage *)xmppStream:(XMPPStream *)sender willReceiveMessage:(XMPPMessage *)message;
+- (XMPPPresence *)xmppStream:(XMPPStream *)sender willReceivePresence:(XMPPPresence *)presence;
+
+/**
* These methods are called after their respective XML elements are received on the stream.
*
* In the case of an IQ, the delegate method should return YES if it has or will respond to the given IQ.
* If the IQ is of type 'get' or 'set', and no delegates respond to the IQ,
* then xmpp stream will automatically send an error response.
+ *
+ * Concerning thread-safety, delegates shouldn't modify the given elements.
+ * As documented in NSXML / KissXML, elements are read-access thread-safe, but write-access thread-unsafe.
+ * If you have need to modify an element for any reason,
+ * you should copy the element first, and then modify and use the copy.
**/
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq;
- (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message;
@@ -816,12 +819,28 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
/**
* These methods are called before their respective XML elements are sent over the stream.
- * These methods can be used to customize elements on the fly.
+ * These methods can be used to modify outgoing elements on the fly.
* (E.g. add standard information for custom protocols.)
+ *
+ * You may also filter outgoing elements by returning nil.
+ *
+ * When implementing these methods to modify the element, you do not need to copy the given element.
+ * You can simply edit the given element, and return it.
+ * The reason these methods return an element, instead of void, is to allow filtering.
+ *
+ * Concerning thread-safety, delegates implementing the method are invoked one-at-a-time to
+ * allow thread-safe modification of the given elements.
+ *
+ * You should NOT implement these methods unless you have good reason to do so.
+ * For general processing and notification of sent elements, please use xmppStream:didSendX: methods.
+ *
+ * @see xmppStream:didSendIQ:
+ * @see xmppStream:didSendMessage:
+ * @see xmppStream:didSendPresence:
**/
-- (void)xmppStream:(XMPPStream *)sender willSendIQ:(XMPPIQ *)iq;
-- (void)xmppStream:(XMPPStream *)sender willSendMessage:(XMPPMessage *)message;
-- (void)xmppStream:(XMPPStream *)sender willSendPresence:(XMPPPresence *)presence;
+- (XMPPIQ *)xmppStream:(XMPPStream *)sender willSendIQ:(XMPPIQ *)iq;
+- (XMPPMessage *)xmppStream:(XMPPStream *)sender willSendMessage:(XMPPMessage *)message;
+- (XMPPPresence *)xmppStream:(XMPPStream *)sender willSendPresence:(XMPPPresence *)presence;
/**
* These methods are called after their respective XML elements are sent over the stream.
View
1,526 Core/XMPPStream.m
@@ -4,8 +4,8 @@
#import "XMPPInternal.h"
#import "XMPPSRVResolver.h"
#import "NSData+XMPP.h"
-#import "DDList.h"
+#import <objc/runtime.h>
#import <libkern/OSAtomic.h>
#if TARGET_OS_IPHONE
@@ -17,6 +17,32 @@
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
+/**
+ * Does ARC support support GCD objects?
+ * It does if the minimum deployment target is iOS 6+ or Mac OS X 10.8+
+**/
+#if TARGET_OS_IPHONE
+
+ // Compiling for iOS
+
+ #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000 // iOS 6.0 or later
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 0
+ #else // iOS 5.X or earlier
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 1
+ #endif
+
+#else
+
+ // Compiling for Mac OS X
+
+ #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 // Mac OS X 10.8 or later
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 0
+ #else
+ #define NEEDS_DISPATCH_RETAIN_RELEASE 1 // Mac OS X 10.7 or earlier
+ #endif
+
+#endif
+
// Log levels: off, error, warn, info, verbose
#if DEBUG
static const int xmppLogLevel = XMPP_LOG_LEVEL_INFO | XMPP_LOG_FLAG_SEND_RECV; // | XMPP_LOG_FLAG_TRACE;
@@ -24,12 +50,6 @@
static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN;
#endif
-#if TARGET_OS_IPHONE
- #define SOCKET_BUFFER_SIZE 512 // bytes
-#else
- #define SOCKET_BUFFER_SIZE 1024 // bytes
-#endif
-
/**
* Seeing a return statements within an inner block
* can sometimes be mistaken for a return point of the enclosing method.
@@ -37,6 +57,17 @@
**/
#define return_from_block return
+// Define the timeouts (in seconds) for retreiving various parts of the XML stream
+#define TIMEOUT_XMPP_WRITE -1
+#define TIMEOUT_XMPP_READ_START 10
+#define TIMEOUT_XMPP_READ_STREAM -1
+
+// Define the tags we'll use to differentiate what it is we're currently reading or writing
+#define TAG_XMPP_READ_START 100
+#define TAG_XMPP_READ_STREAM 101
+#define TAG_XMPP_WRITE_START 200
+#define TAG_XMPP_WRITE_STREAM 201
+#define TAG_XMPP_WRITE_RECEIPT 202
NSString *const XMPPStreamErrorDomain = @"XMPPStreamErrorDomain";
NSString *const XMPPStreamDidChangeMyJIDNotification = @"XMPPStreamDidChangeMyJID";
@@ -63,18 +94,83 @@
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-@interface XMPPStream (PrivateAPI)
+@interface XMPPStream ()
+{
+ dispatch_queue_t xmppQueue;
+
+ dispatch_queue_t willSendIqQueue;
+ dispatch_queue_t willSendMessageQueue;
+ dispatch_queue_t willSendPresenceQueue;
+
+ dispatch_queue_t willReceiveIqQueue;
+ dispatch_queue_t willReceiveMessageQueue;
+ dispatch_queue_t willReceivePresenceQueue;
+
+ dispatch_queue_t didReceiveIqQueue;
+
+ GCDMulticastDelegate <XMPPStreamDelegate> *multicastDelegate;
+
+ int state;
+
+ GCDAsyncSocket *asyncSocket;
+
+ UInt64 numberOfBytesSent;
+ UInt64 numberOfBytesReceived;
+
+ XMPPParser *parser;
+ NSError *parserError;
+
+ Byte flags;
+ Byte config;
+
+ NSString *hostName;
+ UInt16 hostPort;
+
+ id <XMPPSASLAuthentication> auth;
+
+ XMPPJID *myJID_setByClient;
+ XMPPJID *myJID_setByServer;
+ XMPPJID *remoteJID;
+
+ XMPPPresence *myPresence;
+ NSXMLElement *rootElement;
+
+ NSTimeInterval keepAliveInterval;
+ dispatch_source_t keepAliveTimer;
+ NSTimeInterval lastSendReceiveTime;
+ NSData *keepAliveData;
+
+ NSMutableArray *registeredModules;
+ NSMutableDictionary *autoDelegateDict;
+
+ XMPPSRVResolver *srvResolver;
+ NSArray *srvResults;
+ NSUInteger srvResultsIndex;
+
+ NSMutableArray *receipts;
+
+ NSThread *xmppUtilityThread;
+ NSRunLoop *xmppUtilityRunLoop;
+
+ id userTag;
+}
-- (void)cleanup;
- (void)setIsSecure:(BOOL)flag;
- (void)setIsAuthenticated:(BOOL)flag;
-- (void)continueSendElement:(NSXMLElement *)element withTag:(long)tag;
+- (void)continueSendIQ:(XMPPIQ *)iq withTag:(long)tag;
+- (void)continueSendMessage:(XMPPMessage *)message withTag:(long)tag;
+- (void)continueSendPresence:(XMPPPresence *)presence withTag:(long)tag;
- (void)startNegotiation;
- (void)sendOpeningNegotiation;
- (void)continueStartTLS:(NSMutableDictionary *)settings;
+- (void)continueHandleBinding:(NSString *)alternativeResource;
- (void)setupKeepAliveTimer;
- (void)keepAlive;
+- (void)continueReceiveMessage:(XMPPMessage *)message;
+- (void)continueReceiveIQ:(XMPPIQ *)iq;
+- (void)continueReceivePresence:(XMPPPresence *)presence;
+
@end
@interface XMPPElementReceipt (PrivateAPI)
@@ -97,8 +193,17 @@ @implementation XMPPStream
**/
- (void)commonInit
{
- xmppQueue = dispatch_queue_create("xmppStreamQueue", NULL);
- parserQueue = dispatch_queue_create("xmppParserQueue", NULL);
+ xmppQueue = dispatch_queue_create("xmpp", NULL);
+
+ willSendIqQueue = dispatch_queue_create("xmpp.willSendIq", NULL);
+ willSendMessageQueue = dispatch_queue_create("xmpp.willSendMessage", NULL);
+ willSendPresenceQueue = dispatch_queue_create("xmpp.willSendPresence", NULL);
+
+ willReceiveIqQueue = dispatch_queue_create("xmpp.willReceiveIq", NULL);
+ willReceiveMessageQueue = dispatch_queue_create("xmpp.willReceiveMessage", NULL);
+ willReceivePresenceQueue = dispatch_queue_create("xmpp.willReceivePresence", NULL);
+
+ didReceiveIqQueue = dispatch_queue_create("xmpp.didReceiveIq", NULL);
multicastDelegate = (GCDMulticastDelegate <XMPPStreamDelegate> *)[[GCDMulticastDelegate alloc] init];
@@ -110,12 +215,13 @@ - (void)commonInit
numberOfBytesSent = 0;
numberOfBytesReceived = 0;
- parser = [[XMPPParser alloc] initWithDelegate:self];
+ parser = [[XMPPParser alloc] initWithDelegate:self delegateQueue:xmppQueue];
hostPort = 5222;
keepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL;
+ keepAliveData = [@" " dataUsingEncoding:NSUTF8StringEncoding];
- registeredModules = [[DDList alloc] init];
+ registeredModules = [[NSMutableArray alloc] init];
autoDelegateDict = [[NSMutableDictionary alloc] init];