PNXMPPFramework/Extensions/XEP-0009/XMPPJabberRPCModule.m
2016-02-24 16:56:39 +01:00

342 lines
8.5 KiB
Objective-C

//
// 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:
//
// <iq from="deusty.com" to="robbiehanson@deusty.com/work" id="abc123" type="result"/>
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 xmlns="http://jabber.org/protocol/disco#info">
// ...
// <identity category='automation' type='rpc'/>
// <feature var='jabber:iq:rpc'/>
// ...
// </query>
[query addChild:[XMPPIQ elementRpcIdentity]];
[query addChild:[XMPPIQ elementRpcFeature]];
}
#endif
@end