#import "XMPPAutoTime.h" #import "XMPP.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 // 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 @interface XMPPAutoTime () @property (nonatomic, strong) NSData *lastServerAddress; @property (nonatomic, strong) NSDate *systemUptimeChecked; - (void)updateRecalibrationTimer; - (void)startRecalibrationTimer; - (void)stopRecalibrationTimer; @end #pragma mark - @implementation XMPPAutoTime - (id)initWithDispatchQueue:(dispatch_queue_t)queue { if ((self = [super initWithDispatchQueue:queue])) { recalibrationInterval = (60 * 60 * 24); lastCalibrationTime = DISPATCH_TIME_FOREVER; xmppTime = [[XMPPTime alloc] initWithDispatchQueue:queue]; xmppTime.respondsToQueries = NO; [xmppTime addDelegate:self delegateQueue:moduleQueue]; } return self; } - (BOOL)activate:(XMPPStream *)aXmppStream { if ([super activate:aXmppStream]) { [xmppTime activate:aXmppStream]; self.systemUptimeChecked = [NSDate date]; systemUptime = [[NSProcessInfo processInfo] systemUptime]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(systemClockDidChange:) name:NSSystemClockDidChangeNotification object:nil]; return YES; } return NO; } - (void)deactivate { dispatch_block_t block = ^{ @autoreleasepool { [self stopRecalibrationTimer]; [xmppTime deactivate]; awaitingQueryResponse = NO; [[NSNotificationCenter defaultCenter] removeObserver:self]; [super deactivate]; }}; if (dispatch_get_specific(moduleQueueTag)) block(); else dispatch_sync(moduleQueue, block); } - (void)dealloc { // recalibrationTimer released in [self deactivate] [xmppTime removeDelegate:self]; xmppTime = nil; // Might be referenced via [super dealloc] -> [self deactivate] } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Properties //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @synthesize lastServerAddress; @synthesize systemUptimeChecked; - (NSTimeInterval)recalibrationInterval { if (dispatch_get_specific(moduleQueueTag)) { return recalibrationInterval; } else { __block NSTimeInterval result; dispatch_sync(moduleQueue, ^{ result = recalibrationInterval; }); return result; } } - (void)setRecalibrationInterval:(NSTimeInterval)interval { dispatch_block_t block = ^{ if (recalibrationInterval != interval) { recalibrationInterval = interval; // Update the recalibrationTimer. // // Depending on new value and current state of the recalibrationTimer, // this may mean starting, stoping, or simply updating the timer. if (recalibrationInterval > 0) { // Remember: Only start the timer after the xmpp stream is up and authenticated if ([xmppStream isAuthenticated]) [self startRecalibrationTimer]; } else { [self stopRecalibrationTimer]; } } }; if (dispatch_get_specific(moduleQueueTag)) block(); else dispatch_async(moduleQueue, block); } - (XMPPJID *)targetJID { if (dispatch_get_specific(moduleQueueTag)) { return targetJID; } else { __block XMPPJID *result; dispatch_sync(moduleQueue, ^{ result = targetJID; }); return result; } } - (void)setTargetJID:(XMPPJID *)jid { dispatch_block_t block = ^{ if (![targetJID isEqualToJID:jid]) { targetJID = jid; } }; if (dispatch_get_specific(moduleQueueTag)) block(); else dispatch_async(moduleQueue, block); } - (NSTimeInterval)timeDifference { if (dispatch_get_specific(moduleQueueTag)) { return timeDifference; } else { __block NSTimeInterval result; dispatch_sync(moduleQueue, ^{ result = timeDifference; }); return result; } } - (NSDate *)date { if (dispatch_get_specific(moduleQueueTag)) { return [[NSDate date] dateByAddingTimeInterval:-timeDifference]; } else { __block NSDate *result; dispatch_sync(moduleQueue, ^{ result = [[NSDate date] dateByAddingTimeInterval:-timeDifference]; }); return result; } } - (dispatch_time_t)lastCalibrationTime { if (dispatch_get_specific(moduleQueueTag)) { return lastCalibrationTime; } else { __block dispatch_time_t result; dispatch_sync(moduleQueue, ^{ result = lastCalibrationTime; }); return result; } } - (BOOL)respondsToQueries { return xmppTime.respondsToQueries; } - (void)setRespondsToQueries:(BOOL)respondsToQueries { xmppTime.respondsToQueries = respondsToQueries; } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Notifications //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)systemClockDidChange:(NSNotification *)notification { XMPPLogTrace(); XMPPLogVerbose(@"NSSystemClockDidChangeNotification: %@", notification); if (lastCalibrationTime == DISPATCH_TIME_FOREVER) { // Doesn't matter, we haven't done a calibration yet. return; } // When the system clock changes, this affects our timeDifference. // However, the notification doesn't tell us by how much the system clock has changed. // So here's how we figure it out: // // The systemUptime isn't affected by the system clock. // We previously recorded the system uptime, and simultaneously recoded the system clock time. // We can now grab the current system uptime and current system clock time. // Using the four data points we can calculate how much the system clock has changed. NSDate *now = [NSDate date]; NSTimeInterval sysUptime = [[NSProcessInfo processInfo] systemUptime]; dispatch_async(moduleQueue, ^{ @autoreleasepool { // Calculate system clock change NSDate *oldSysTime = systemUptimeChecked; NSDate *newSysTime = now; NSTimeInterval oldSysUptime = systemUptime; NSTimeInterval newSysUptime = sysUptime; NSTimeInterval sysTimeDiff = [newSysTime timeIntervalSinceDate:oldSysTime]; NSTimeInterval sysUptimeDiff = newSysUptime - oldSysUptime; NSTimeInterval sysClockChange = sysTimeDiff - sysUptimeDiff; // Modify timeDifference & notify delegate timeDifference += sysClockChange; [multicastDelegate xmppAutoTime:self didUpdateTimeDifference:timeDifference]; // Dont forget to update our variables self.systemUptimeChecked = now; systemUptime = sysUptime; }}); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark Recalibration Timer //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)handleRecalibrationTimerFire { XMPPLogTrace(); if (awaitingQueryResponse) return; awaitingQueryResponse = YES; if (targetJID) [xmppTime sendQueryToJID:targetJID]; else [xmppTime sendQueryToServer]; } - (void)updateRecalibrationTimer { XMPPLogTrace(); NSAssert(recalibrationTimer != NULL, @"Broken logic (1)"); NSAssert(recalibrationInterval > 0, @"Broken logic (2)"); uint64_t interval = (recalibrationInterval * NSEC_PER_SEC); dispatch_time_t tt; if (lastCalibrationTime == DISPATCH_TIME_FOREVER) tt = dispatch_time(DISPATCH_TIME_NOW, 0); // First timer fire at (NOW) else tt = dispatch_time(lastCalibrationTime, interval); // First timer fire at (lastCalibrationTime + interval) dispatch_source_set_timer(recalibrationTimer, tt, interval, 0); } - (void)startRecalibrationTimer { XMPPLogTrace(); if (recalibrationInterval <= 0) { // Timer is disabled return; } BOOL newTimer = NO; if (recalibrationTimer == NULL) { newTimer = YES; recalibrationTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, moduleQueue); dispatch_source_set_event_handler(recalibrationTimer, ^{ @autoreleasepool { [self handleRecalibrationTimerFire]; }}); } [self updateRecalibrationTimer]; if (newTimer) { dispatch_resume(recalibrationTimer); } } - (void)stopRecalibrationTimer { XMPPLogTrace(); if (recalibrationTimer) { #if !OS_OBJECT_USE_OBJC dispatch_release(recalibrationTimer); #endif recalibrationTimer = NULL; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark XMPPTime Delegate //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)xmppTime:(XMPPTime *)sender didReceiveResponse:(XMPPIQ *)iq withRTT:(NSTimeInterval)rtt { XMPPLogTrace(); awaitingQueryResponse = NO; lastCalibrationTime = dispatch_time(DISPATCH_TIME_NOW, 0); timeDifference = [XMPPTime approximateTimeDifferenceFromResponse:iq andRTT:rtt]; [multicastDelegate xmppAutoTime:self didUpdateTimeDifference:timeDifference]; } - (void)xmppTime:(XMPPTime *)sender didNotReceiveResponse:(NSString *)queryID dueToTimeout:(NSTimeInterval)timeout { XMPPLogTrace(); awaitingQueryResponse = NO; // Nothing to do here really. Most likely the server doesn't support XEP-0202. } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma mark XMPPStream Delegate //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - (void)xmppStream:(XMPPStream *)sender socketDidConnect:(GCDAsyncSocket *)socket { NSData *currentServerAddress = [socket connectedAddress]; if (lastServerAddress == nil) { self.lastServerAddress = currentServerAddress; } else if (![lastServerAddress isEqualToData:currentServerAddress]) { XMPPLogInfo(@"%@: Connected to a different server. Resetting calibration info.", [self class]); lastCalibrationTime = DISPATCH_TIME_FOREVER; timeDifference = 0.0; self.lastServerAddress = currentServerAddress; } } - (void)xmppStreamDidAuthenticate:(XMPPStream *)sender { [self startRecalibrationTimer]; } - (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error { [self stopRecalibrationTimer]; awaitingQueryResponse = NO; // We do NOT reset the lastCalibrationTime here. // If we reconnect to the same server, the lastCalibrationTime remains valid. } @end @implementation XMPPStream (XMPPAutoTime) - (NSTimeInterval)xmppAutoTime_timeDifferenceForTargetJID:(XMPPJID *)targetJID { __block NSTimeInterval timeDifference = 0.0; [self enumerateModulesOfClass:[XMPPAutoTime class] withBlock:^(XMPPModule *module, NSUInteger idx, BOOL *stop) { XMPPAutoTime *autoTime = (XMPPAutoTime *)module; if([targetJID isEqualToJID:autoTime.targetJID] || (!targetJID && !autoTime.targetJID)) { timeDifference = autoTime.timeDifference; *stop = YES; } }]; return timeDifference; } - (NSDate *)xmppAutoTime_dateForTargetJID:(XMPPJID *)targetJID { __block NSDate *date = [NSDate date]; [self enumerateModulesOfClass:[XMPPAutoTime class] withBlock:^(XMPPModule *module, NSUInteger idx, BOOL *stop) { XMPPAutoTime *autoTime = (XMPPAutoTime *)module; if([targetJID isEqualToJID:autoTime.targetJID] || (!targetJID && !autoTime.targetJID)) { date = autoTime.date; *stop = YES; } }]; return date; } @end