#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