#import "XMPPProcessOne.h"
#import "XMPP.h"
#import "XMPPInternal.h"
#import "XMPPLogging.h"
#if ! __has_feature(objc_arc)
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
#endif
NSString *const XMPPProcessOneSessionID = @"XMPPProcessOneSessionID";
NSString *const XMPPProcessOneSessionJID = @"XMPPProcessOneSessionJID";
NSString *const XMPPProcessOneSessionDate = @"XMPPProcessOneSessionDate";
@interface XMPPProcessOne ()
{
NSXMLElement *pushConfiguration;
BOOL pushConfigurationSent;
BOOL pushConfigurationConfirmed;
NSString *pushIQID;
}
- (void)sendPushConfiguration;
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPProcessOne
- (id)initWithDispatchQueue:(dispatch_queue_t)queue
{
if ((self = [super initWithDispatchQueue:NULL]))
{
pushConfiguration = nil;
pushConfigurationSent = YES;
pushConfigurationConfirmed = YES;
}
return self;
}
- (NSString *)savedSessionID
{
return [[NSUserDefaults standardUserDefaults] stringForKey:XMPPProcessOneSessionID];
}
- (void)setSavedSessionID:(NSString *)savedSessionID
{
if (savedSessionID)
[[NSUserDefaults standardUserDefaults] setObject:savedSessionID forKey:XMPPProcessOneSessionID];
else
[[NSUserDefaults standardUserDefaults] removeObjectForKey:XMPPProcessOneSessionID];
}
- (XMPPJID *)savedSessionJID
{
NSString *sessionJidStr = [[NSUserDefaults standardUserDefaults] stringForKey:XMPPProcessOneSessionJID];
return [XMPPJID jidWithString:sessionJidStr];
}
- (void)setSavedSessionJID:(XMPPJID *)savedSessionJID
{
NSString *sessionJidStr = [savedSessionJID full];
if (sessionJidStr)
[[NSUserDefaults standardUserDefaults] setObject:sessionJidStr forKey:XMPPProcessOneSessionJID];
else
[[NSUserDefaults standardUserDefaults] removeObjectForKey:XMPPProcessOneSessionJID];
}
- (NSDate *)savedSessionDate
{
return [[NSUserDefaults standardUserDefaults] objectForKey:XMPPProcessOneSessionDate];
}
- (void)setSavedSessionDate:(NSDate *)savedSessionDate
{
if (savedSessionDate)
[[NSUserDefaults standardUserDefaults] setObject:savedSessionDate forKey:XMPPProcessOneSessionDate];
else
[[NSUserDefaults standardUserDefaults] removeObjectForKey:XMPPProcessOneSessionDate];
}
- (NSXMLElement *)pushConfiguration
{
if (dispatch_get_specific(moduleQueueTag))
{
return pushConfiguration;
}
else
{
__block NSXMLElement *result = nil;
dispatch_sync(moduleQueue, ^{
result = [pushConfiguration copy];
});
return result;
}
}
- (void)setPushConfiguration:(NSXMLElement *)pushConfig
{
NSXMLElement *newPushConfiguration = [pushConfig copy];
dispatch_block_t block = ^{
if (pushConfiguration == nil && newPushConfiguration == nil)
{
return;
}
pushConfiguration = newPushConfiguration;
pushConfigurationSent = NO;
pushConfigurationConfirmed = NO;
if ([xmppStream isAuthenticated])
{
[self sendPushConfiguration];
}
};
if (dispatch_get_specific(moduleQueueTag))
block();
else
dispatch_async(moduleQueue, block);
}
- (void)sendPushConfiguration
{
if (pushConfiguration)
{
pushIQID = [XMPPStream generateUUID];
NSXMLElement *push = [pushConfiguration copy];
XMPPIQ *iq = [XMPPIQ iqWithType:@"set" to:nil elementID:pushIQID child:push];
[xmppStream sendElement:iq];
pushConfigurationSent = YES;
}
else
{
//
//
//
NSXMLElement *disable = [NSXMLElement elementWithName:@"disable" xmlns:@"p1:push"];
XMPPIQ *iq = [XMPPIQ iqWithType:@"set" child:disable];
[xmppStream sendElement:iq];
pushConfigurationSent = YES;
}
}
- (XMPPElementReceipt *)goOnStandby
{
// true
NSXMLElement *standby = [NSXMLElement elementWithName:@"standby" stringValue:@"true"];
XMPPElementReceipt *receipt = nil;
[xmppStream sendElement:standby andGetReceipt:&receipt];
return receipt;
}
- (XMPPElementReceipt *)goOffStandby
{
// false
NSXMLElement *standby = [NSXMLElement elementWithName:@"standby" stringValue:@"false"];
XMPPElementReceipt *receipt = nil;
[xmppStream sendElement:standby andGetReceipt:&receipt];
return receipt;
}
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender
{
if (!pushConfigurationSent)
{
[self sendPushConfiguration];
}
}
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error
{
if (pushConfigurationConfirmed)
{
self.savedSessionDate = [[NSDate alloc] init];
}
else
{
// The pushConfiguration was sent to the server, but we never received a confirmation.
// So either the pushConfiguration never made it to the server,
// or we got disconnected before we received the confirmation from the server.
//
// To be sure, we need to resent the pushConfiguration next time we authenticate.
pushConfigurationSent = NO;
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark Configuration Elements
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ (NSXMLElement *)pushConfigurationContainer
{
return [NSXMLElement elementWithName:@"push" xmlns:@"p1:push"];
}
+ (NSXMLElement *)keepaliveWithMax:(NSTimeInterval)max
{
// keepalive is the max interval between keepalive events received by server
// before going out of reception (in seconds)
NSString *maxStr = [NSString stringWithFormat:@"%.0f", max];
NSXMLElement *keepalive = [NSXMLElement elementWithName:@"keepalive"];
[keepalive addAttributeWithName:@"max" stringValue:maxStr];
return keepalive;
}
+ (NSXMLElement *)sessionWithDuration:(NSTimeInterval)durationInSeconds
{
// session is the max time the session is kept before automatically
// closing the session while in push mode (in minutes).
//
// Max session is 24 hours.
// Server can decide to force the session close anyway, if the message queue is getting large.
double durationInMinutes = durationInSeconds / 60.0;
NSString *durationStr = [NSString stringWithFormat:@"%.0f", durationInMinutes];
NSXMLElement *session = [NSXMLElement elementWithName:@"session"];
[session addAttributeWithName:@"duration" stringValue:durationStr];
return session;
}
+ (NSXMLElement *)statusWithType:(NSString *)type message:(NSString *)message
{
// status (optional) is the XMPP status the user should appear in,
// and the message when the XMPP session is not linked to a TCP connection.
//
// If omittted, the presence and status is not change when going into disconnected opened session.
NSXMLElement *status = [NSXMLElement elementWithName:@"status"];
if (type)
[status addAttributeWithName:@"type" stringValue:type];
if (message)
[status setStringValue:message];
return status;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPRebindAuthentication
{
#if __has_feature(objc_arc_weak)
__weak XMPPStream *xmppStream;
#else
__unsafe_unretained XMPPStream *xmppStream;
#endif
NSString *sessionID;
XMPPJID *sessionJID;
}
+ (NSString *)mechanismName
{
return nil;
}
- (id)initWithStream:(XMPPStream *)stream password:(NSString *)password
{
if ((self = [super init]))
{
xmppStream = stream;
}
return self;
}
- (id)initWithStream:(XMPPStream *)stream sessionID:(NSString *)aSessionID sessionJID:(XMPPJID *)aSessionJID
{
if ((self = [super init]))
{
xmppStream = stream;
sessionID = aSessionID;
sessionJID = aSessionJID;
}
return self;
}
- (BOOL)start:(NSError **)errPtr
{
if (!sessionID || !sessionJID)
{
NSString *errMsg = @"Missing sessionID and/or sessionJID.";
NSDictionary *info = @{NSLocalizedDescriptionKey : errMsg};
NSError *err = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamInvalidState userInfo:info];
if (errPtr) *errPtr = err;
return NO;
}
//
// user@domain/resource
// 123456789
//
NSXMLElement *jid = [NSXMLElement elementWithName:@"jid" stringValue:[sessionJID full]];
NSXMLElement *sid = [NSXMLElement elementWithName:@"sid" stringValue:sessionID];
NSXMLElement *rebind = [NSXMLElement elementWithName:@"rebind" xmlns:@"p1:rebind"];
[rebind addChild:jid];
[rebind addChild:sid];
[xmppStream sendAuthElement:rebind];
return YES;
}
- (XMPPHandleAuthResponse)handleAuth:(NSXMLElement *)response
{
if ([[response name] isEqualToString:@"rebind"])
{
return XMPP_AUTH_SUCCESS;
}
else
{
return XMPP_AUTH_FAIL;
}
}
- (BOOL)shouldResendOpeningNegotiationAfterSuccessfulAuthentication
{
return NO;
}
@end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#pragma mark -
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@implementation XMPPStream (XMPPProcessOne)
- (BOOL)supportsPush
{
__block BOOL result = NO;
dispatch_block_t block = ^{ @autoreleasepool {
// The root element can be properly queried anytime after the
// stream:features are received, and TLS has been setup (if required)
if (self.state >= STATE_XMPP_POST_NEGOTIATION)
{
NSXMLElement *features = [self.rootElement elementForName:@"stream:features"];
NSXMLElement *push = [features elementForName:@"push" xmlns:@"p1:push"];
result = (push != nil);
}
}};
if (dispatch_get_specific(self.xmppQueueTag))
block();
else
dispatch_sync(self.xmppQueue, block);
return result;
}
- (BOOL)supportsRebind
{
__block BOOL result = NO;
dispatch_block_t block = ^{ @autoreleasepool {
// The root element can be properly queried anytime after the
// stream:features are received, and TLS has been setup (if required)
if (self.state >= STATE_XMPP_POST_NEGOTIATION)
{
NSXMLElement *features = [self.rootElement elementForName:@"stream:features"];
NSXMLElement *rebind = [features elementForName:@"rebind" xmlns:@"p1:rebind"];
result = (rebind != nil);
}
}};
if (dispatch_get_specific(self.xmppQueueTag))
block();
else
dispatch_sync(self.xmppQueue, block);
return result;
}
- (NSString *)rebindSessionID
{
return [[self rootElement] attributeStringValueForName:@"id"];
}
@end