// // XMPPJabberRPCModule.m // XEP-0009 // // Originally created by Eric Chamberlain on 5/16/10. // #import "XMPPJabberRPCModule.h" #import "XMPP.h" #import "XMPPIQ+JabberRPC.h" #import "XMPPIQ+JabberRPCResonse.h" #import "XMPPLogging.h" #import "XMPPFramework.h" #if ! __has_feature(objc_arc) #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). #endif // Log levels: off, error, warn, info, verbose // Log flags: trace #if DEBUG static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; // | XMPP_LOG_FLAG_TRACE; #else static const int xmppLogLevel = XMPP_LOG_LEVEL_WARN; #endif NSString *const XMPPJabberRPCErrorDomain = @"XMPPJabberRPCErrorDomain"; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @interface RPCID : NSObject { NSString *rpcID; dispatch_source_t timer; } @property (nonatomic, readonly) NSString *rpcID; @property (nonatomic, readonly) dispatch_source_t timer; - (id)initWithRpcID:(NSString *)rpcID timer:(dispatch_source_t)timer; - (void)cancelTimer; @end @implementation RPCID @synthesize rpcID; @synthesize timer; - (id)initWithRpcID:(NSString *)aRpcID timer:(dispatch_source_t)aTimer { if ((self = [super init])) { rpcID = [aRpcID copy]; timer = aTimer; #if !OS_OBJECT_USE_OBJC dispatch_retain(timer); #endif } return self; } - (BOOL)isEqual:(id)anObject { return [rpcID isEqual:anObject]; } - (NSUInteger)hash { return [rpcID hash]; } - (void)cancelTimer { if (timer) { dispatch_source_cancel(timer); #if !OS_OBJECT_USE_OBJC dispatch_release(timer); #endif timer = NULL; } } - (void)dealloc { [self cancelTimer]; } @end //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @implementation XMPPJabberRPCModule @dynamic defaultTimeout; - (NSTimeInterval)defaultTimeout { __block NSTimeInterval result; dispatch_block_t block = ^{ result = defaultTimeout; }; if (dispatch_get_specific(moduleQueueTag)) block(); else dispatch_sync(moduleQueue, block); return result; } - (void)setDefaultTimeout:(NSTimeInterval)newDefaultTimeout { dispatch_block_t block = ^{ XMPPLogTrace(); defaultTimeout = newDefaultTimeout; }; if (dispatch_get_specific(moduleQueueTag)) block(); else dispatch_async(moduleQueue, block); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Module Management //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (id)init { return [self initWithDispatchQueue:NULL]; } - (id)initWithDispatchQueue:(dispatch_queue_t)queue { if ((self = [super initWithDispatchQueue:queue])) { XMPPLogTrace(); rpcIDs = [[NSMutableDictionary alloc] initWithCapacity:5]; defaultTimeout = 5.0; } return self; } - (BOOL)activate:(XMPPStream *)aXmppStream { XMPPLogTrace(); if ([super activate:aXmppStream]) { #ifdef _XMPP_CAPABILITIES_H [xmppStream autoAddDelegate:self delegateQueue:moduleQueue toModulesOfClass:[XMPPCapabilities class]]; #endif return YES; } return NO; } - (void)deactivate { XMPPLogTrace(); #ifdef _XMPP_CAPABILITIES_H [xmppStream removeAutoDelegate:self delegateQueue:moduleQueue fromModulesOfClass:[XMPPCapabilities class]]; #endif [super deactivate]; } - (void)dealloc { XMPPLogTrace(); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Send RPC //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (NSString *)sendRpcIQ:(XMPPIQ *)iq { return [self sendRpcIQ:iq withTimeout:[self defaultTimeout]]; } - (NSString *)sendRpcIQ:(XMPPIQ *)iq withTimeout:(NSTimeInterval)timeout { XMPPLogTrace(); NSString *elementID = [iq elementID]; dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, moduleQueue); dispatch_source_set_event_handler(timer, ^{ @autoreleasepool { [self timeoutRemoveRpcID:elementID]; }}); dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (timeout * NSEC_PER_SEC)); dispatch_source_set_timer(timer, tt, DISPATCH_TIME_FOREVER, 0); dispatch_resume(timer); RPCID *rpcID = [[RPCID alloc] initWithRpcID:elementID timer:timer]; rpcIDs[elementID] = rpcID; [xmppStream sendElement:iq]; return elementID; } - (void)timeoutRemoveRpcID:(NSString *)elementID { XMPPLogTrace(); RPCID *rpcID = rpcIDs[elementID]; if (rpcID) { [rpcID cancelTimer]; [rpcIDs removeObjectForKey:elementID]; NSError *error = [NSError errorWithDomain:XMPPJabberRPCErrorDomain code:1400 userInfo:@{@"error" : @"Request timed out"}]; [multicastDelegate jabberRPC:self elementID:elementID didReceiveError:error]; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark XMPPStream delegate //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq { XMPPLogTrace(); if ([iq isJabberRPC]) { if ([iq isResultIQ] || [iq isErrorIQ]) { // Example: // // NSString *elementID = [iq elementID]; // check if this is a JabberRPC query // we could check the query element, but we should be able to do a lookup based on the unique elementID // because we send an ID, we should get one back RPCID *rpcID = rpcIDs[elementID]; if (rpcID == nil) { return NO; } XMPPLogVerbose(@"%@: Received RPC response!", THIS_FILE); if ([iq isResultIQ]) { id response; NSError *error = nil; //TODO: parse iq and generate response. response = [iq methodResponse:&error]; if (error == nil) { [multicastDelegate jabberRPC:self elementID:elementID didReceiveMethodResponse:response]; } else { [multicastDelegate jabberRPC:self elementID:elementID didReceiveError:error]; } } else { // TODO: implement error parsing // not much specified in XEP, only 403 forbidden error NSXMLElement *errorElement = [iq childErrorElement]; NSError *error = [NSError errorWithDomain:XMPPJabberRPCErrorDomain code:[errorElement attributeIntValueForName:@"code"] userInfo:@{ @"error" : [errorElement attributesAsDictionary], @"condition" : [[errorElement childAtIndex:0] name], @"iq" : iq}]; [multicastDelegate jabberRPC:self elementID:elementID didReceiveError:error]; } [rpcID cancelTimer]; [rpcIDs removeObjectForKey:elementID]; #ifdef _XMPP_CAPABILITIES_H } else if ([iq isSetIQ]) { // we would receive set when implementing Jabber-RPC server [multicastDelegate jabberRPC:self didReceiveSetIQ:iq]; #endif } // Jabber-RPC doesn't use get iq type } return NO; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark XMPPCapabilities delegate //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #ifdef _XMPP_CAPABILITIES_H /** * If an XMPPCapabilites instance is used we want to advertise our support for JabberRPC. **/ - (void)xmppCapabilities:(XMPPCapabilities *)sender collectingMyCapabilities:(NSXMLElement *)query { XMPPLogTrace(); // // ... // // // ... // [query addChild:[XMPPIQ elementRpcIdentity]]; [query addChild:[XMPPIQ elementRpcFeature]]; } #endif @end