| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- /*
- * 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
|