AutorÃa | Ultima modificación | Ver Log |
/** Copyright 2018 Google** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h"#import <sys/sysctl.h>// Using a monotonic clock is necessary because CFAbsoluteTimeGetCurrent(), NSDate, and related all// are subject to drift. That it to say, multiple consecutive calls do not always result in a// time that is in the future. Clocks may be adjusted by the user, NTP, or any number of external// factors. This class attempts to determine the wall-clock time at the time of the event by// capturing the kernel start and time since boot to determine a wallclock time in UTC.//// Timezone offsets at the time of a snapshot are also captured in order to provide local-time// details. Other classes in this library depend on comparing times at some time in the future to// a time captured in the past, and this class needs to provide a mechanism to do that.//// TL;DR: This class attempts to accomplish two things: 1. Provide accurate event times. 2. Provide// a monotonic clock mechanism to accurately check if some clock snapshot was before or after// by using a shared reference point (kernel boot time).//// Note: Much of the mach time stuff doesn't work properly in the simulator. So this class can be// difficult to unit test./** Returns the kernel boottime property from sysctl.** Inspired by https://stackoverflow.com/a/40497811** @return The KERN_BOOTTIME property from sysctl, in nanoseconds.*/static int64_t KernelBootTimeInNanoseconds() {// Caching the result is not possible because clock drift would not be accounted for.struct timeval boottime;int mib[2] = {CTL_KERN, KERN_BOOTTIME};size_t size = sizeof(boottime);int rc = sysctl(mib, 2, &boottime, &size, NULL, 0);if (rc != 0) {return 0;}return (int64_t)boottime.tv_sec * NSEC_PER_SEC + (int64_t)boottime.tv_usec * NSEC_PER_USEC;}/** Returns value of gettimeofday, in nanoseconds.** Inspired by https://stackoverflow.com/a/40497811** @return The value of gettimeofday, in nanoseconds.*/static int64_t UptimeInNanoseconds() {int64_t before_now_nsec;int64_t after_now_nsec;struct timeval now;before_now_nsec = KernelBootTimeInNanoseconds();// Addresses a race condition in which the system time has updated, but the boottime has not.do {gettimeofday(&now, NULL);after_now_nsec = KernelBootTimeInNanoseconds();} while (after_now_nsec != before_now_nsec);return (int64_t)now.tv_sec * NSEC_PER_SEC + (int64_t)now.tv_usec * NSEC_PER_USEC -before_now_nsec;}// TODO: Consider adding a 'trustedTime' property that can be populated by the response from a BE.@implementation GDTCORClock- (instancetype)init {self = [super init];if (self) {_kernelBootTimeNanoseconds = KernelBootTimeInNanoseconds();_uptimeNanoseconds = UptimeInNanoseconds();_timeMillis =(int64_t)((CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) * NSEC_PER_USEC);_timezoneOffsetSeconds = [[NSTimeZone systemTimeZone] secondsFromGMT];}return self;}+ (GDTCORClock *)snapshot {return [[GDTCORClock alloc] init];}+ (instancetype)clockSnapshotInTheFuture:(uint64_t)millisInTheFuture {GDTCORClock *snapshot = [self snapshot];snapshot->_timeMillis += millisInTheFuture;return snapshot;}- (BOOL)isAfter:(GDTCORClock *)otherClock {// These clocks are trivially comparable when they share a kernel boot time.if (_kernelBootTimeNanoseconds == otherClock->_kernelBootTimeNanoseconds) {int64_t timeDiff = (_timeMillis + _timezoneOffsetSeconds) -(otherClock->_timeMillis + otherClock->_timezoneOffsetSeconds);return timeDiff > 0;} else {int64_t kernelBootTimeDiff =otherClock->_kernelBootTimeNanoseconds - _kernelBootTimeNanoseconds;// This isn't a great solution, but essentially, if the other clock's boot time is 'later', NO// is returned. This can be altered by changing the system time and rebooting.return kernelBootTimeDiff < 0 ? YES : NO;}}- (int64_t)uptimeMilliseconds {return self.uptimeNanoseconds / NSEC_PER_MSEC;}- (NSUInteger)hash {return [@(_kernelBootTimeNanoseconds) hash] ^ [@(_uptimeNanoseconds) hash] ^[@(_timeMillis) hash] ^ [@(_timezoneOffsetSeconds) hash];}- (BOOL)isEqual:(id)object {return [self hash] == [object hash];}#pragma mark - NSSecureCoding/** NSKeyedCoder key for timeMillis property. */static NSString *const kGDTCORClockTimeMillisKey = @"GDTCORClockTimeMillis";/** NSKeyedCoder key for timezoneOffsetMillis property. */static NSString *const kGDTCORClockTimezoneOffsetSeconds = @"GDTCORClockTimezoneOffsetSeconds";/** NSKeyedCoder key for _kernelBootTime ivar. */static NSString *const kGDTCORClockKernelBootTime = @"GDTCORClockKernelBootTime";/** NSKeyedCoder key for _uptimeNanoseconds ivar. */static NSString *const kGDTCORClockUptime = @"GDTCORClockUptime";+ (BOOL)supportsSecureCoding {return YES;}- (instancetype)initWithCoder:(NSCoder *)aDecoder {self = [super init];if (self) {// TODO: If the kernelBootTimeNanoseconds is more recent, we need to change the kernel boot time// and uptimeMillis ivars_timeMillis = [aDecoder decodeInt64ForKey:kGDTCORClockTimeMillisKey];_timezoneOffsetSeconds = [aDecoder decodeInt64ForKey:kGDTCORClockTimezoneOffsetSeconds];_kernelBootTimeNanoseconds = [aDecoder decodeInt64ForKey:kGDTCORClockKernelBootTime];_uptimeNanoseconds = [aDecoder decodeInt64ForKey:kGDTCORClockUptime];}return self;}- (void)encodeWithCoder:(NSCoder *)aCoder {[aCoder encodeInt64:_timeMillis forKey:kGDTCORClockTimeMillisKey];[aCoder encodeInt64:_timezoneOffsetSeconds forKey:kGDTCORClockTimezoneOffsetSeconds];[aCoder encodeInt64:_kernelBootTimeNanoseconds forKey:kGDTCORClockKernelBootTime];[aCoder encodeInt64:_uptimeNanoseconds forKey:kGDTCORClockUptime];}#pragma mark - Deprecated properties- (int64_t)kernelBootTime {return self.kernelBootTimeNanoseconds;}- (int64_t)uptime {return self.uptimeNanoseconds;}@end