Skip to content
Browse files

Improving xmppParser and improving parallelization. Adding some advan…

…ced options to xmppStream.
  • Loading branch information...
1 parent 9e3171e commit b793e603c3f36be71d297f34623a622e51e2399d @robbiehanson committed Jul 12, 2012
Showing with 778 additions and 472 deletions.
  1. +6 −0 Core/XMPPInternal.h
  2. +7 −14 Core/XMPPParser.h
  3. +198 −139 Core/XMPPParser.m
  4. +25 −0 Core/XMPPStream.h
  5. +541 −318 Core/XMPPStream.m
  6. +1 −1 Xcode/iPhoneXMPP/main.m
View
6 Core/XMPPInternal.h
@@ -53,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
21 Core/XMPPParser.h
@@ -1,29 +1,20 @@
#import <Foundation/Foundation.h>
-#import <libxml2/libxml/parser.h>
#if TARGET_OS_IPHONE
#import "DDXML.h"
#endif
@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
337 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,31 +11,42 @@
#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;
#else
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,17 +180,17 @@ 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!
}
else
{
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,25 +242,25 @@ 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!
}
else
{
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 delegate = 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);
@@ -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
25 Core/XMPPStream.h
@@ -766,6 +766,31 @@ typedef enum XMPPStreamErrorCode XMPPStreamErrorCode;
- (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.
View
859 Core/XMPPStream.m
@@ -50,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.
@@ -103,18 +97,22 @@
@interface XMPPStream ()
{
dispatch_queue_t xmppQueue;
- dispatch_queue_t parserQueue;
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;
- NSMutableData *socketBuffer;
UInt64 numberOfBytesSent;
UInt64 numberOfBytesReceived;
@@ -192,12 +190,17 @@ @implementation XMPPStream
- (void)commonInit
{
xmppQueue = dispatch_queue_create("xmpp", NULL);
- parserQueue = dispatch_queue_create("xmpp.parser", 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];
state = STATE_XMPP_DISCONNECTED;
@@ -208,7 +211,7 @@ - (void)commonInit
numberOfBytesSent = 0;
numberOfBytesReceived = 0;
- parser = [[XMPPParser alloc] initWithDelegate:self];
+ parser = [[XMPPParser alloc] initWithDelegate:self delegateQueue:xmppQueue];
hostPort = 5222;
keepAliveInterval = DEFAULT_KEEPALIVE_INTERVAL;
@@ -274,16 +277,19 @@ - (void)dealloc
{
#if NEEDS_DISPATCH_RETAIN_RELEASE
dispatch_release(xmppQueue);
- dispatch_release(parserQueue);
dispatch_release(willSendIqQueue);
dispatch_release(willSendMessageQueue);
dispatch_release(willSendPresenceQueue);
+ dispatch_release(willReceiveIqQueue);
+ dispatch_release(willReceiveMessageQueue);
+ dispatch_release(willReceivePresenceQueue);
+ dispatch_release(didReceiveIqQueue);
#endif
[asyncSocket setDelegate:nil delegateQueue:NULL];
[asyncSocket disconnect];
- [parser setDelegate:nil];
+ [parser setDelegate:nil delegateQueue:NULL];
if (keepAliveTimer)
{
@@ -2280,6 +2286,337 @@ - (void)sendAuthElement:(NSXMLElement *)element
dispatch_async(xmppQueue, block);
}
+- (void)receiveIQ:(XMPPIQ *)iq
+{
+ NSAssert(dispatch_get_current_queue() == xmppQueue, @"Invoked on incorrect queue");
+ NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state");
+
+ // We're getting ready to receive an IQ.
+ // Notify delegates to allow them to optionally alter/filter the incoming IQ element.
+
+ SEL selector = @selector(xmppStream:willReceiveIQ:);
+
+ if (![multicastDelegate hasDelegateThatRespondsToSelector:selector])
+ {
+ // None of the delegates implement the method.
+ // Use a shortcut.
+
+ [self continueReceiveIQ:iq];
+ }
+ else
+ {
+ // Notify all interested delegates.
+ // This must be done serially to allow them to alter the element in a thread-safe manner.
+
+ GCDMulticastDelegateEnumerator *delegateEnumerator = [multicastDelegate delegateEnumerator];
+
+ dispatch_async(willReceiveIqQueue, ^{ @autoreleasepool {
+
+ // Allow delegates to modify and/or filter incoming element
+
+ __block XMPPIQ *modifiedIQ = iq;
+
+ id del;
+ dispatch_queue_t dq;
+
+ while (modifiedIQ && [delegateEnumerator getNextDelegate:&del delegateQueue:&dq forSelector:selector])
+ {
+ dispatch_sync(dq, ^{ @autoreleasepool {
+
+ modifiedIQ = [del xmppStream:self willReceiveIQ:modifiedIQ];
+
+ }});
+ }
+
+ if (modifiedIQ)
+ {
+ dispatch_async(xmppQueue, ^{ @autoreleasepool {
+
+ if (state == STATE_XMPP_CONNECTED) {
+ [self continueReceiveIQ:modifiedIQ];
+ }
+ }});
+ }
+ }});
+ }
+}
+
+- (void)receiveMessage:(XMPPMessage *)message
+{
+ NSAssert(dispatch_get_current_queue() == xmppQueue, @"Invoked on incorrect queue");
+ NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state");
+
+ // We're getting ready to receive a message.
+ // Notify delegates to allow them to optionally alter/filter the incoming message.
+
+ SEL selector = @selector(xmppStream:willReceiveMessage:);
+
+ if (![multicastDelegate hasDelegateThatRespondsToSelector:selector])
+ {
+ // None of the delegates implement the method.
+ // Use a shortcut.
+
+ [self continueReceiveMessage:message];
+ }
+ else
+ {
+ // Notify all interested delegates.
+ // This must be done serially to allow them to alter the element in a thread-safe manner.
+
+ GCDMulticastDelegateEnumerator *delegateEnumerator = [multicastDelegate delegateEnumerator];
+
+ dispatch_async(willReceiveMessageQueue, ^{ @autoreleasepool {
+
+ // Allow delegates to modify incoming element
+
+ __block XMPPMessage *modifiedMessage = message;
+
+ id del;
+ dispatch_queue_t dq;
+
+ while (modifiedMessage && [delegateEnumerator getNextDelegate:&del delegateQueue:&dq forSelector:selector])
+ {
+ dispatch_sync(dq, ^{ @autoreleasepool {
+
+ modifiedMessage = [del xmppStream:self willReceiveMessage:modifiedMessage];
+
+ }});
+ }
+
+ if (modifiedMessage)
+ {
+ dispatch_async(xmppQueue, ^{ @autoreleasepool {
+
+ if (state == STATE_XMPP_CONNECTED) {
+ [self continueReceiveMessage:modifiedMessage];
+ }
+ }});
+ }
+ }});
+ }
+}
+
+- (void)receivePresence:(XMPPPresence *)presence
+{
+ NSAssert(dispatch_get_current_queue() == xmppQueue, @"Invoked on incorrect queue");
+ NSAssert(state == STATE_XMPP_CONNECTED, @"Invoked with incorrect state");
+
+ // We're getting ready to receive a presence element.
+ // Notify delegates to allow them to optionally alter/filter the incoming presence.
+
+ SEL selector = @selector(xmppStream:willReceivePresence:);
+
+ if (![multicastDelegate hasDelegateThatRespondsToSelector:selector])
+ {
+ // None of the delegates implement the method.
+ // Use a shortcut.
+
+ [self continueReceivePresence:presence];
+ }
+ else
+ {
+ // Notify all interested delegates.
+ // This must be done serially to allow them to alter the element in a thread-safe manner.
+
+ GCDMulticastDelegateEnumerator *delegateEnumerator = [multicastDelegate delegateEnumerator];
+
+ dispatch_async(willSendPresenceQueue, ^{ @autoreleasepool {
+
+ // Allow delegates to modify outgoing element
+
+ __block XMPPPresence *modifiedPresence = presence;
+
+ id del;
+ dispatch_queue_t dq;
+
+ while (modifiedPresence && [delegateEnumerator getNextDelegate:&del delegateQueue:&dq forSelector:selector])
+ {
+ dispatch_sync(dq, ^{ @autoreleasepool {
+
+ modifiedPresence = [del xmppStream:self willReceivePresence:modifiedPresence];
+
+ }});
+ }
+
+ if (modifiedPresence)
+ {
+ dispatch_async(xmppQueue, ^{ @autoreleasepool {
+
+ if (state == STATE_XMPP_CONNECTED) {
+ [self continueReceivePresence:presence];
+ }
+ }});
+ }
+ }});
+ }
+}
+
+- (void)continueReceiveIQ:(XMPPIQ *)iq
+{
+ if ([iq requiresResponse])
+ {
+ // As per the XMPP specificiation, if the IQ requires a response,
+ // and we don't have any delegates or modules that can properly respond to the IQ,
+ // we MUST send back and error IQ.
+ //
+ // So we notifiy all interested delegates and modules about the received IQ,
+ // keeping track of whether or not any of them have handled it.
+
+ GCDMulticastDelegateEnumerator *delegateEnumerator = [multicastDelegate delegateEnumerator];
+
+ id del;
+ dispatch_queue_t dq;
+
+ SEL selector = @selector(xmppStream:didReceiveIQ:);
+
+ dispatch_semaphore_t delSemaphore = dispatch_semaphore_create(0);
+ dispatch_group_t delGroup = dispatch_group_create();
+
+ while ([delegateEnumerator getNextDelegate:&del delegateQueue:&dq forSelector:selector])
+ {
+ dispatch_group_async(delGroup, dq, ^{ @autoreleasepool {
+
+ if ([del xmppStream:self didReceiveIQ:iq])
+ {
+ dispatch_semaphore_signal(delSemaphore);
+ }
+
+ }});
+ }
+
+ dispatch_async(didReceiveIqQueue, ^{ @autoreleasepool {
+
+ dispatch_group_wait(delGroup, DISPATCH_TIME_FOREVER);
+
+ // Did any of the delegates handle the IQ? (handle == will response)
+
+ BOOL handled = (dispatch_semaphore_wait(delSemaphore, DISPATCH_TIME_NOW) == 0);
+
+ // An entity that receives an IQ request of type "get" or "set" MUST reply
+ // with an IQ response of type "result" or "error".
+ //
+ // The response MUST preserve the 'id' attribute of the request.
+
+ if (!handled)
+ {
+ // Return error message:
+ //
+ // <iq to="jid" type="error" id="id">
+ // <query xmlns="ns"/>
+ // <error type="cancel" code="501">
+ // <feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
+ // </error>
+ // </iq>
+
+ NSXMLElement *reason = [NSXMLElement elementWithName:@"feature-not-implemented"
+ xmlns:@"urn:ietf:params:xml:ns:xmpp-stanzas"];
+
+ NSXMLElement *error = [NSXMLElement elementWithName:@"error"];
+ [error addAttributeWithName:@"type" stringValue:@"cancel"];
+ [error addAttributeWithName:@"code" stringValue:@"501"];
+ [error addChild:reason];
+
+ XMPPIQ *iqResponse = [XMPPIQ iqWithType:@"error"
+ to:[iq from]
+ elementID:[iq elementID]
+ child:error];
+
+ NSXMLElement *iqChild = [iq childElement];
+ if (iqChild)
+ {
+ NSXMLNode *iqChildCopy = [iqChild copy];
+ [iqResponse insertChild:iqChildCopy atIndex:0];
+ }
+
+ // Purposefully go through the sendElement: method
+ // so that it gets dispatched onto the xmppQueue,
+ // and so that modules may get notified of the outgoing error message.
+
+ [self sendElement:iqResponse];
+ }
+
+ #if NEEDS_DISPATCH_RETAIN_RELEASE
+ dispatch_release(delSemaphore);
+ dispatch_release(delGroup);
+ #endif
+
+ }});
+ }
+ else
+ {
+ // The IQ doesn't require a response.
+ // So we can just fire the delegate method and ignore the responses.
+
+ [multicastDelegate xmppStream:self didReceiveIQ:iq];
+ }
+}
+
+- (void)continueReceiveMessage:(XMPPMessage *)message
+{
+ [multicastDelegate xmppStream:self didReceiveMessage:message];
+}
+
+- (void)continueReceivePresence:(XMPPPresence *)presence
+{
+ [multicastDelegate xmppStream:self didReceivePresence:presence];
+}
+
+/**
+ * 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
+{
+ if (element == nil) return;
+
+ dispatch_block_t block = ^{ @autoreleasepool {
+
+ if (state != STATE_XMPP_CONNECTED)
+ {
+ return_from_block;
+ }
+
+ if ([element isKindOfClass:[XMPPIQ class]])
+ {
+ [self receiveIQ:(XMPPIQ *)element];
+ }
+ else if ([element isKindOfClass:[XMPPMessage class]])
+ {
+ [self receiveMessage:(XMPPMessage *)element];
+ }
+ else if ([element isKindOfClass:[XMPPPresence class]])
+ {
+ [self receivePresence:(XMPPPresence *)element];
+ }
+ else
+ {
+ NSString *elementName = [element name];
+
+ if ([elementName isEqualToString:@"iq"])
+ {
+ [self receiveIQ:[XMPPIQ iqFromElement:element]];
+ }
+ else if ([elementName isEqualToString:@"message"])
+ {
+ [self receiveMessage:[XMPPMessage messageFromElement:element]];
+ }
+ else if ([elementName isEqualToString:@"presence"])
+ {
+ [self receivePresence:[XMPPPresence presenceFromElement:element]];
+ }
+ else
+ {
+ [multicastDelegate xmppStream:self didReceiveError:element];
+ }
+ }
+ }};
+
+ if (dispatch_get_current_queue() == xmppQueue)
+ block();
+ else
+ dispatch_async(xmppQueue, block);
+}
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Stream Negotiation
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -2300,18 +2637,8 @@ - (void)startNegotiation
// Inform delegate that the TCP connection is open, and the stream handshake has begun
[multicastDelegate xmppStreamDidStartNegotiation:self];
- // Initialize socket buffer
- if (socketBuffer == nil)
- {
- socketBuffer = [[NSMutableData alloc] initWithLength:SOCKET_BUFFER_SIZE];
- }
-
// And start reading in the server's XML stream
- [asyncSocket readDataWithTimeout:TIMEOUT_XMPP_READ_START
- buffer:socketBuffer
- bufferOffset:0
- maxLength:[socketBuffer length]
- tag:TAG_XMPP_READ_START];
+ [asyncSocket readDataWithTimeout:TIMEOUT_XMPP_READ_START tag:TAG_XMPP_READ_START];
}
/**
@@ -2345,16 +2672,16 @@ - (void)sendOpeningNegotiation
XMPPLogVerbose(@"%@: Resetting parser...", THIS_FILE);
// We're restarting our negotiation, so we need to reset the parser.
- [parser setDelegate:nil];
+ [parser setDelegate:nil delegateQueue:NULL];
- parser = [(XMPPParser *)[XMPPParser alloc] initWithDelegate:self];
+ parser = [[XMPPParser alloc] initWithDelegate:self delegateQueue:xmppQueue];
}
else if (parser == nil)
{
XMPPLogVerbose(@"%@: Initializing parser...", THIS_FILE);
// Need to create parser (it was destroyed when the socket was last disconnected)
- parser = [(XMPPParser *)[XMPPParser alloc] initWithDelegate:self];
+ parser = [[XMPPParser alloc] initWithDelegate:self delegateQueue:NULL];
}
else
{
@@ -2504,11 +2831,7 @@ - (void)continueStartTLS:(NSMutableDictionary *)settings
// We paused reading from the socket.
// We're ready to continue now.
- [asyncSocket readDataWithTimeout:TIMEOUT_XMPP_READ_STREAM
- buffer:socketBuffer
- bufferOffset:0
- maxLength:[socketBuffer length]
- tag:TAG_XMPP_READ_STREAM];
+ [asyncSocket readDataWithTimeout:TIMEOUT_XMPP_READ_STREAM tag:TAG_XMPP_READ_STREAM];
}
else
{
@@ -3094,43 +3417,30 @@ - (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)t
XMPPLogTrace();
lastSendReceiveTime = [NSDate timeIntervalSinceReferenceDate];
+ numberOfBytesReceived += [data length];
- if (XMPP_LOG_RECV_PRE)
- {
- NSString *dataAsStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
-
- XMPPLogRecvPre(@"RECV: %@", dataAsStr);
- }
+ XMPPLogRecvPre(@"RECV: %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
- numberOfBytesReceived += [data length];
+ // Asynchronously parse the xml data
+ [parser parseData:data];
- dispatch_async(parserQueue, ^{ @autoreleasepool {
-
- [parser parseData:data];
-
- dispatch_async(xmppQueue, ^{ @autoreleasepool {
-
- // Continue reading for XML elements.
-
- if (state == STATE_XMPP_OPENING)
- {
- [asyncSocket readDataWithTimeout:TIMEOUT_XMPP_READ_START
- buffer:socketBuffer
- bufferOffset:0
- maxLength:[socketBuffer length]
- tag:TAG_XMPP_READ_START];
- }
- else if (state != STATE_XMPP_STARTTLS_2)
- {
- [asyncSocket readDataWithTimeout:TIMEOUT_XMPP_READ_STREAM
- buffer:socketBuffer
- bufferOffset:0
- maxLength:[socketBuffer length]
- tag:TAG_XMPP_READ_STREAM];
- }
-
- }});
- }});
+ if ([self isSecure])
+ {
+ // Continue reading for XML elements
+ if (state == STATE_XMPP_OPENING)
+ {
+ [asyncSocket readDataWithTimeout:TIMEOUT_XMPP_READ_START tag:TAG_XMPP_READ_START];
+ }
+ else if (state != STATE_XMPP_STARTTLS_2)
+ {
+ [asyncSocket readDataWithTimeout:TIMEOUT_XMPP_READ_STREAM tag:TAG_XMPP_READ_STREAM];
+ }
+ }
+ else
+ {
+ // Don't queue up a read on the socket as we may need to upgrade to TLS.
+ // We'll read more data after we've parsed the current chunk of data.
+ }
}
/**
@@ -3176,11 +3486,8 @@ - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
// Update state
state = STATE_XMPP_DISCONNECTED;
- // Release socket buffer
- socketBuffer = nil;
-
// Release the parser (to free underlying resources)
- [parser setDelegate:nil];
+ [parser setDelegate:nil delegateQueue:NULL];
parser = nil;
// Clear any saved authentication information
@@ -3236,307 +3543,223 @@ - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
**/
- (void)xmppParser:(XMPPParser *)sender didReadRoot:(NSXMLElement *)root
{
- NSAssert(dispatch_get_current_queue() == parserQueue, @"Invoked on incorrect queue");
+ // This method is invoked on the xmppQueue.
- XMPPLogTrace();
+ if (sender != parser) return;
- dispatch_async(xmppQueue, ^{ @autoreleasepool {
+ XMPPLogTrace();
+ XMPPLogRecvPost(@"RECV: %@", [root compactXMLString]);
- if (sender != parser) return_from_block;
+ // At this point we've sent our XML stream header, and we've received the response XML stream header.
+ // We save the root element of our stream for future reference.
- XMPPLogRecvPost(@"RECV: %@", [root compactXMLString]);
-
- // At this point we've sent our XML stream header, and we've received the response XML stream header.
- // We save the root element of our stream for future reference.
- // Digest Access authentication requires us to know the ID attribute from the <stream:stream/> element.
+ rootElement = root;
+
+ if ([self isP2P])
+ {
+ // XEP-0174 specifies that <stream:features/> SHOULD be sent by the receiver.
+ // In other words, if we're the recipient we will now send our features.
+ // But if we're the initiator, we can't depend on receiving their features.
- rootElement = root;
+ // Either way, we're connected at this point.
+ state = STATE_XMPP_CONNECTED;
- if ([self isP2P])
+ if ([self isP2PRecipient])
{
- // XEP-0174 specifies that <stream:features/> SHOULD be sent by the receiver.
- // In other words, if we're the recipient we will now send our features.
- // But if we're the initiator, we can't depend on receiving their features.
+ // Extract the remoteJID:
+ //
+ // <stream:stream ... from='<remoteJID>' to='<myJID>'>
- // Either way, we're connected at this point.
- state = STATE_XMPP_CONNECTED;
+ NSString *from = [[rootElement attributeForName:@"from"] stringValue];
+ remoteJID = [XMPPJID jidWithString:from];
- if ([self isP2PRecipient])
- {
- // Extract the remoteJID:
- //
- // <stream:stream ... from='<remoteJID>' to='<myJID>'>
-
- NSString *from = [[rootElement attributeForName:@"from"] stringValue];
- remoteJID = [XMPPJID jidWithString:from];
-
- // Send our stream features.
- // To do so we need to ask the delegate to fill it out for us.
-
- NSXMLElement *streamFeatures = [NSXMLElement elementWithName:@"stream:features"];
-
- [multicastDelegate xmppStream:self willSendP2PFeatures:streamFeatures];
-
- NSString *outgoingStr = [streamFeatures compactXMLString];
- NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding];
-
- XMPPLogSend(@"SEND: %@", outgoingStr);
- numberOfBytesSent += [outgoingData length];
-
- [asyncSocket writeData:outgoingData
- withTimeout:TIMEOUT_XMPP_WRITE
- tag:TAG_XMPP_WRITE_STREAM];
-
- }
+ // Send our stream features.
+ // To do so we need to ask the delegate to fill it out for us.
- // Make sure the delegate didn't disconnect us in the xmppStream:willSendP2PFeatures: method.
+ NSXMLElement *streamFeatures = [NSXMLElement elementWithName:@"stream:features"];
- if ([self isConnected])
- {
- [multicastDelegate xmppStreamDidConnect:self];
- }
+ [multicastDelegate xmppStream:self willSendP2PFeatures:streamFeatures];
+
+ NSString *outgoingStr = [streamFeatures compactXMLString];
+ NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding];
+
+ XMPPLogSend(@"SEND: %@", outgoingStr);
+ numberOfBytesSent += [outgoingData length];
+
+ [asyncSocket writeData:outgoingData
+ withTimeout:TIMEOUT_XMPP_WRITE
+ tag:TAG_XMPP_WRITE_STREAM];
+ }
+
+ // Make sure the delegate didn't disconnect us in the xmppStream:willSendP2PFeatures: method.
+
+ if ([self isConnected])
+ {
+ [multicastDelegate xmppStreamDidConnect:self];
+ }
+ }
+ else
+ {
+ // Check for RFC compliance
+ if ([self serverXmppStreamVersionNumber] >= 1.0)
+ {
+ // Update state - we're now onto stream negotiations
+ state = STATE_XMPP_NEGOTIATING;
+
+ // Note: We're waiting for the <stream:features> now
}
else
{
- // Check for RFC compliance
- if ([self serverXmppStreamVersionNumber] >= 1.0)
- {
- // Update state - we're now onto stream negotiations
- state = STATE_XMPP_NEGOTIATING;
-
- // Note: We're waiting for the <stream:features> now
- }
- else
- {
- // The server isn't RFC comliant, and won't be sending any stream features.
-
- // We would still like to know what authentication features it supports though,
- // so we'll use the jabber:iq:auth namespace, which was used prior to the RFC spec.
-
- // Update state - we're onto psuedo negotiation
- state = STATE_XMPP_NEGOTIATING;
-
- NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:auth"];
-
- NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"];
- [iq addAttributeWithName:@"type" stringValue:@"get"];
- [iq addChild:query];
-
- NSString *outgoingStr = [iq compactXMLString];
- NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding];
-
- XMPPLogSend(@"SEND: %@", outgoingStr);
- numberOfBytesSent += [outgoingData length];
-
- [asyncSocket writeData:outgoingData
- withTimeout:TIMEOUT_XMPP_WRITE
- tag:TAG_XMPP_WRITE_STREAM];
-
- // Now wait for the response IQ
- }
+ // The server isn't RFC comliant, and won't be sending any stream features.
+
+ // We would still like to know what authentication features it supports though,
+ // so we'll use the jabber:iq:auth namespace, which was used prior to the RFC spec.
+
+ // Update state - we're onto psuedo negotiation
+ state = STATE_XMPP_NEGOTIATING;
+
+ NSXMLElement *query = [NSXMLElement elementWithName:@"query" xmlns:@"jabber:iq:auth"];
+
+ NSXMLElement *iq = [NSXMLElement elementWithName:@"iq"];
+ [iq addAttributeWithName:@"type" stringValue:@"get"];
+ [iq addChild:query];
+
+ NSString *outgoingStr = [iq compactXMLString];
+ NSData *outgoingData = [outgoingStr dataUsingEncoding:NSUTF8StringEncoding];
+
+ XMPPLogSend(@"SEND: %@", outgoingStr);
+ numberOfBytesSent += [outgoingData length];
+
+ [asyncSocket writeData:outgoingData
+ withTimeout:TIMEOUT_XMPP_WRITE
+ tag:TAG_XMPP_WRITE_STREAM];
+
+ // Now wait for the response IQ
}
-
- }});
+ }
}
- (void)xmppParser:(XMPPParser *)sender didReadElement:(NSXMLElement *)element
{
- NSAssert(dispatch_get_current_queue() == parserQueue, @"Invoked on incorrect queue");
+ // This method is invoked on the xmppQueue.
- XMPPLogTrace();
+ if (sender != parser) return;
- dispatch_async(xmppQueue, ^{ @autoreleasepool {
-
- if (sender != parser) return_from_block;
-
- XMPPLogRecvPost(@"RECV: %@", [element compactXMLString]);
+ XMPPLogTrace();
+ XMPPLogRecvPost(@"RECV: %@", [element compactXMLString]);
- NSString *elementName = [element name];
+ NSString *elementName = [element name];
+
+ if ([elementName isEqualToString:@"stream:error"] || [elementName isEqualToString:@"error"])
+ {
+ [multicastDelegate xmppStream:self didReceiveError:element];
- if ([elementName isEqualToString:@"stream:error"] || [elementName isEqualToString:@"error"])
- {
- [multicastDelegate xmppStream:self didReceiveError:element];
-
- return_from_block;
- }
+ return;
+ }
+
+ if (state == STATE_XMPP_NEGOTIATING)
+ {
+ // We've just read in the stream features
+ // We consider this part of the root element, so we'll add it (replacing any previously sent features)
+ [rootElement setChildren:[NSArray arrayWithObject:element]];
- if (state == STATE_XMPP_NEGOTIATING)
+ // Call a method to handle any requirements set forth in the features
+ [self handleStreamFeatures];
+ }
+ else if (state == STATE_XMPP_STARTTLS_1)
+ {
+ // The response from our starttls message
+ [self handleStartTLSResponse:element];
+ }
+ else if (state == STATE_XMPP_REGISTERING)
+ {
+ // The iq response from our registration request
+ [self handleRegistration:element];
+ }
+ else if (state == STATE_XMPP_AUTH)
+ {
+ // Some response to the authentication process
+ [self handleAuth:element];
+ }
+ else if (state == STATE_XMPP_BINDING)
+ {
+ // The response from our binding request
+ [self handleBinding:element];
+ }
+ else if (state == STATE_XMPP_START_SESSION)
+ {
+ // The response from our start session request
+ [self handleStartSessionResponse:element];
+ }
+ else
+ {
+ if ([elementName isEqualToString:@"iq"])
{
- // We've just read in the stream features
- // We consider this part of the root element, so we'll add it (replacing any previously sent features)
- [rootElement setChildren:[NSArray arrayWithObject:element]];
-
- // Call a method to handle any requirements set forth in the features
- [self handleStreamFeatures];
+ [self receiveIQ:[XMPPIQ iqFromElement:element]];
}
- else if (state == STATE_XMPP_STARTTLS_1)
+ else if ([elementName isEqualToString:@"message"])
{
- // The response from our starttls message
- [self handleStartTLSResponse:element];
+ [self receiveMessage:[XMPPMessage messageFromElement:element]];
}
- else if (state == STATE_XMPP_REGISTERING)
+ else if ([elementName isEqualToString:@"presence"])
{
- // The iq response from our registration request
- [self handleRegistration:element];
+ [self receivePresence:[XMPPPresence presenceFromElement:element]];
}
- else if (state == STATE_XMPP_AUTH)
+ else if ([self isP2P] &&
+ ([elementName isEqualToString:@"stream:features"] || [elementName isEqualToString:@"features"]))
{
- // Some response to the authentication process
- [self handleAuth:element];
+ [multicastDelegate xmppStream:self didReceiveP2PFeatures:element];
}
- else if (state == STATE_XMPP_BINDING)
+ else
{
- // The response from our binding request
- [self handleBinding:element];
+ [multicastDelegate xmppStream:self didReceiveError:element];
}
- else if (state == STATE_XMPP_START_SESSION)
+ }
+}
+
+- (void)xmppParserDidParseData:(XMPPParser *)sender
+{
+ // This method is invoked on the xmppQueue.
+
+ if (sender != parser) return;
+
+ XMPPLogTrace();
+
+ if (![self isSecure])
+ {
+ // Continue reading for XML elements
+ if (state == STATE_XMPP_OPENING)
{
- // The response from our start session request
- [self handleStartSessionResponse:element];
+ [asyncSocket readDataWithTimeout:TIMEOUT_XMPP_READ_START tag:TAG_XMPP_READ_START];
}
- else
+ else if (state != STATE_XMPP_STARTTLS_2)
{
- if ([elementName isEqualToString:@"iq"])
- {
- XMPPIQ *iq = [XMPPIQ iqFromElement:element];
-
- // Notify all interested delegates about the received IQ.
- // Keep track of whether the delegates respond to the IQ.
-
- GCDMulticastDelegateEnumerator *delegateEnumerator = [multicastDelegate delegateEnumerator];
-
- id del;
- dispatch_queue_t dq;
-
- SEL selector = @selector(xmppStream:didReceiveIQ:);
-
- dispatch_semaphore_t delSemaphore = dispatch_semaphore_create(0);
- dispatch_group_t delGroup = dispatch_group_create();
-
- while ([delegateEnumerator getNextDelegate:&del delegateQueue:&dq forSelector:selector])
- {
- dispatch_group_async(delGroup, dq, ^{ @autoreleasepool {
-
- if ([del xmppStream:self didReceiveIQ:iq])
- {
- dispatch_semaphore_signal(delSemaphore);
- }
-
- }});
- }
-
- dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- dispatch_async(concurrentQueue, ^{ @autoreleasepool {
-
- dispatch_group_wait(delGroup, DISPATCH_TIME_FOREVER);
-
- // Did any of the delegates respond to the IQ?
-
- BOOL responded = (dispatch_semaphore_wait(delSemaphore, DISPATCH_TIME_NOW) == 0);
-
- // An entity that receives an IQ request of type "get" or "set" MUST reply
- // with an IQ response of type "result" or "error".
- //
- // The response MUST preserve the 'id' attribute of the request.
-
- if (!responded && [iq requiresResponse])
- {
- // Return error message:
- //
- // <iq to="jid" type="error" id="id">
- // <query xmlns="ns"/>
- // <error type="cancel" code="501">
- // <feature-not-implemented xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
- // </error>
- // </iq>
-
- NSXMLElement *reason = [NSXMLElement elementWithName:@"feature-not-implemented"
- xmlns:@"urn:ietf:params:xml:ns:xmpp-stanzas"];
-
- NSXMLElement *error = [NSXMLElement elementWithName:@"error"];
- [error addAttributeWithName:@"type" stringValue:@"cancel"];
- [error addAttributeWithName:@"code" stringValue:@"501"];
- [error addChild:reason];
-
- XMPPIQ *iqResponse = [XMPPIQ iqWithType:@"error"
- to:[iq from]
- elementID:[iq elementID]
- child:error];
-
- NSXMLElement *iqChild = [iq childElement];
- if (iqChild)
- {
- NSXMLNode *iqChildCopy = [iqChild copy];
- [iqResponse insertChild:iqChildCopy atIndex:0];
- }
-
- // Purposefully go through the sendElement: method
- // so that it gets dispatched onto the xmppQueue,
- // and so that modules may get notified of the outgoing error message.
-
- [self sendElement:iqResponse];
- }
-
- #if NEEDS_DISPATCH_RETAIN_RELEASE
- dispatch_release(delSemaphore);
- dispatch_release(delGroup);
- #endif
-
- }});
-
- }
- else if ([elementName isEqualToString:@"message"])
- {
- [multicastDelegate xmppStream:self didReceiveMessage:[XMPPMessage messageFromElement:element]];
- }
- else if ([elementName isEqualToString:@"presence"])
- {
- [multicastDelegate xmppStream:self didReceivePresence:[XMPPPresence presenceFromElement:element]];
- }
- else if ([self isP2P] &&
- ([elementName isEqualToString:@"stream:features"] || [elementName isEqualToString:@"features"]))
- {
- [multicastDelegate xmppStream:self didReceiveP2PFeatures:element];
- }
- else
- {
- [multicastDelegate xmppStream:self didReceiveError:element];
- }
+ [asyncSocket readDataWithTimeout:TIMEOUT_XMPP_READ_STREAM tag:TAG_XMPP_READ_STREAM];
}
-
- }});
+ }
}
- (void)xmppParserDidEnd:(XMPPParser *)sender
{
- NSAssert(dispatch_get_current_queue() == parserQueue, @"Invoked on incorrect queue");
+ // This method is invoked on the xmppQueue.
+
+ if (sender != parser) return;
XMPPLogTrace();
- dispatch_async(xmppQueue, ^{ @autoreleasepool {
-
- if (sender != parser) return_from_block;
-
- [asyncSocket disconnect];
-
- }});
+ [asyncSocket disconnect];
}
- (void)xmppParser:(XMPPParser *)sender didFail:(NSError *)error
{
- NSAssert(dispatch_get_current_queue() == parserQueue, @"Invoked on incorrect queue");
+ // This method is invoked on the xmppQueue.
+
+ if (sender != parser) return;
XMPPLogTrace();
- dispatch_async(xmppQueue, ^{ @autoreleasepool {
-
- if (sender != parser) return_from_block;
-
- parserError = error;
-
- [asyncSocket disconnect];
-
- }});
+ parserError = error;
+ [asyncSocket disconnect];
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
View
2 Xcode/iPhoneXMPP/main.m
@@ -3,7 +3,7 @@
// iPhoneXMPP
//
// Created by Robbie Hanson on 3/18/10.
-// Copyright __MyCompanyName__ 2010. All rights reserved.
+// Copyright Deusty, LLC 2012. All rights reserved.
//
#import <UIKit/UIKit.h>

0 comments on commit b793e60

Please sign in to comment.
Something went wrong with that request. Please try again.