Skip to content
Permalink
5cc3b6e437
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
217 lines (177 sloc) 15.4 KB
/*╔══════════════════════════════════════════════════════════════════════════════════════════════════╗
║ NetworkClock.m ║
║ ║
║ Created by Gavin Eadie on Oct17/10 ... Copyright 2010-14 Ramsay Consulting. All rights reserved. ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════╝*/
#import <arpa/inet.h>
#import "NetworkClock.h"
#import "ntp-log.h"
@interface NetworkClock () {
NSMutableArray * timeAssociations;
NSArray * sortDescriptors;
NSSortDescriptor * dispersionSortDescriptor;
dispatch_queue_t associationDelegateQueue;
}
@end
#pragma mark -
#pragma mark N E T W O R K • C L O C K
/*┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ NetworkClock is a singleton class which will provide the best estimate of the difference in time ┃
┃ between the device's system clock and the time returned by a collection of time servers. ┃
┃ ┃
┃ The method <networkTime> returns an NSDate with the network time. ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛*/
@implementation NetworkClock
+ (instancetype) sharedNetworkClock {
static id sharedNetworkClockInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedNetworkClockInstance = [[self alloc] init];
});
return sharedNetworkClockInstance;
}
/*┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Return the offset to network-derived UTC. ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛*/
- (NSTimeInterval) networkOffset {
if ([timeAssociations count] == 0) return 0.0;
NSArray * sortedArray = [timeAssociations sortedArrayUsingDescriptors:sortDescriptors];
double timeInterval = 0.0;
short usefulCount = 0;
for (NetAssociation * timeAssociation in sortedArray) {
if (timeAssociation.active) {
if (timeAssociation.trusty) {
usefulCount++;
timeInterval = timeInterval + timeAssociation.offset;
// NSLog(@"[%@]: %f (%d)", timeAssociation.server, timeAssociation.offset*1000.0, usefulCount);
}
else {
NSLog(@"Clock•Drop: [%@]", timeAssociation.server);
if ([timeAssociations count] > 8) {
[timeAssociations removeObject:timeAssociation];
[timeAssociation finish];
}
}
if (usefulCount == 8) break; // use 8 best dispersions
}
}
if (usefulCount > 0) {
timeInterval = timeInterval / usefulCount;
// NSLog(@"timeIntervalSinceDeviceTime: %f (%d)", timeInterval*1000.0, usefulCount);
}
return timeInterval;
}
/*┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Return the device clock time adjusted for the offset to network-derived UTC. ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛*/
- (NSDate *) networkTime {
return [[NSDate date] dateByAddingTimeInterval:[self networkOffset]];
}
- (instancetype) init {
if (self = [super init]) {
/*┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Prepare a sort-descriptor to sort associations based on their dispersion, and then create an │
│ array of empty associations to use ... │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘*/
sortDescriptors = @[[[NSSortDescriptor alloc] initWithKey:@"dispersion" ascending:YES]];
timeAssociations = [NSMutableArray arrayWithCapacity:100];
/*┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ .. and fill that array with the time hosts obtained from "ntp.hosts" .. │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘*/
[[[NSOperationQueue alloc] init] addOperation:[[NSInvocationOperation alloc]
initWithTarget:self
selector:@selector(createAssociations)
object:nil]];
}
return self;
}
/*┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Use the following time servers or, if it exists, read the "ntp.hosts" file from the application ┃
┃ resources and derive all the IP addresses referred to, remove any duplicates and create an ┃
┃ 'association' (individual host client) for each one. ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛*/
- (void) createAssociations {
NSArray * ntpDomains;
NSString * filePath = [[NSBundle mainBundle] pathForResource:@"ntp.hosts" ofType:@""];
if (nil == filePath) {
ntpDomains = @[@"0.pool.ntp.org",
@"0.uk.pool.ntp.org",
@"0.us.pool.ntp.org",
@"asia.pool.ntp.org",
@"europe.pool.ntp.org",
@"north-america.pool.ntp.org",
@"south-america.pool.ntp.org",
@"oceania.pool.ntp.org",
@"africa.pool.ntp.org"];
}
else {
NSString * fileData = [[NSString alloc] initWithData:[[NSFileManager defaultManager]
contentsAtPath:filePath]
encoding:NSUTF8StringEncoding];
ntpDomains = [fileData componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
}
/*┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ for each NTP service domain name in the 'ntp.hosts' file : "0.pool.ntp.org" etc ... │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘*/
NSMutableSet * hostAddresses = [NSMutableSet setWithCapacity:100];
for (NSString * ntpDomainName in ntpDomains) {
if ([ntpDomainName length] == 0 ||
[ntpDomainName characterAtIndex:0] == ' ' ||
[ntpDomainName characterAtIndex:0] == '#') {
continue;
}
/*┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ... resolve the IP address of the named host : "0.pool.ntp.org" --> [123.45.67.89], ... │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘*/
CFHostRef ntpHostName = CFHostCreateWithName (nil, (__bridge CFStringRef)ntpDomainName);
if (nil == ntpHostName) {
NTP_Logging(@"CFHostCreateWithName <nil> for %@", ntpDomainName);
continue; // couldn't create 'host object' ...
}
CFStreamError nameError;
if (!CFHostStartInfoResolution (ntpHostName, kCFHostAddresses, &nameError)) {
NTP_Logging(@"CFHostStartInfoResolution error %i for %@", (int)nameError.error, ntpDomainName);
CFRelease(ntpHostName);
continue; // couldn't start resolution ...
}
Boolean nameFound;
CFArrayRef ntpHostAddrs = CFHostGetAddressing (ntpHostName, &nameFound);
if (!nameFound) {
NTP_Logging(@"CFHostGetAddressing: %@ NOT resolved", ntpHostName);
CFRelease(ntpHostName);
continue; // resolution failed ...
}
if (ntpHostAddrs == nil) {
NTP_Logging(@"CFHostGetAddressing: no addresses resolved for %@", ntpHostName);
CFRelease(ntpHostName);
continue; // NO addresses were resolved ...
}
/*┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ for each (sockaddr structure wrapped by a CFDataRef/NSData *) associated with the hostname, │
│ drop the IP address string into a Set to remove duplicates. │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘*/
for (NSData * ntpHost in (__bridge NSArray *)ntpHostAddrs) {
[hostAddresses addObject:[GCDAsyncUdpSocket hostFromAddress:ntpHost]];
}
CFRelease(ntpHostName);
}
NTP_Logging(@"%@", hostAddresses); // all the addresses resolved
/*┌──────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ... now start one 'association' (network clock server) for each address. │
└──────────────────────────────────────────────────────────────────────────────────────────────────┘*/
for (NSString * server in hostAddresses) {
NetAssociation * timeAssociation = [[NetAssociation alloc] initWithServerName:server];
[timeAssociations addObject:timeAssociation];
[timeAssociation enable]; // starts are randomized internally
}
}
/*┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Stop all the individual ntp clients associations .. ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛*/
- (void) finishAssociations {
for (NetAssociation * timeAssociation in timeAssociations) [timeAssociation finish];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark -
#pragma mark I n t e r n a l • M e t h o d s
@end