1 |
efrain |
1 |
/*
|
|
|
2 |
* Copyright 2018 Google
|
|
|
3 |
*
|
|
|
4 |
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
5 |
* you may not use this file except in compliance with the License.
|
|
|
6 |
* You may obtain a copy of the License at
|
|
|
7 |
*
|
|
|
8 |
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
9 |
*
|
|
|
10 |
* Unless required by applicable law or agreed to in writing, software
|
|
|
11 |
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
12 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
13 |
* See the License for the specific language governing permissions and
|
|
|
14 |
* limitations under the License.
|
|
|
15 |
*/
|
|
|
16 |
|
|
|
17 |
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h"
|
|
|
18 |
|
|
|
19 |
#import <sys/sysctl.h>
|
|
|
20 |
|
|
|
21 |
// Using a monotonic clock is necessary because CFAbsoluteTimeGetCurrent(), NSDate, and related all
|
|
|
22 |
// are subject to drift. That it to say, multiple consecutive calls do not always result in a
|
|
|
23 |
// time that is in the future. Clocks may be adjusted by the user, NTP, or any number of external
|
|
|
24 |
// factors. This class attempts to determine the wall-clock time at the time of the event by
|
|
|
25 |
// capturing the kernel start and time since boot to determine a wallclock time in UTC.
|
|
|
26 |
//
|
|
|
27 |
// Timezone offsets at the time of a snapshot are also captured in order to provide local-time
|
|
|
28 |
// details. Other classes in this library depend on comparing times at some time in the future to
|
|
|
29 |
// a time captured in the past, and this class needs to provide a mechanism to do that.
|
|
|
30 |
//
|
|
|
31 |
// TL;DR: This class attempts to accomplish two things: 1. Provide accurate event times. 2. Provide
|
|
|
32 |
// a monotonic clock mechanism to accurately check if some clock snapshot was before or after
|
|
|
33 |
// by using a shared reference point (kernel boot time).
|
|
|
34 |
//
|
|
|
35 |
// Note: Much of the mach time stuff doesn't work properly in the simulator. So this class can be
|
|
|
36 |
// difficult to unit test.
|
|
|
37 |
|
|
|
38 |
/** Returns the kernel boottime property from sysctl.
|
|
|
39 |
*
|
|
|
40 |
* Inspired by https://stackoverflow.com/a/40497811
|
|
|
41 |
*
|
|
|
42 |
* @return The KERN_BOOTTIME property from sysctl, in nanoseconds.
|
|
|
43 |
*/
|
|
|
44 |
static int64_t KernelBootTimeInNanoseconds() {
|
|
|
45 |
// Caching the result is not possible because clock drift would not be accounted for.
|
|
|
46 |
struct timeval boottime;
|
|
|
47 |
int mib[2] = {CTL_KERN, KERN_BOOTTIME};
|
|
|
48 |
size_t size = sizeof(boottime);
|
|
|
49 |
int rc = sysctl(mib, 2, &boottime, &size, NULL, 0);
|
|
|
50 |
if (rc != 0) {
|
|
|
51 |
return 0;
|
|
|
52 |
}
|
|
|
53 |
return (int64_t)boottime.tv_sec * NSEC_PER_SEC + (int64_t)boottime.tv_usec * NSEC_PER_USEC;
|
|
|
54 |
}
|
|
|
55 |
|
|
|
56 |
/** Returns value of gettimeofday, in nanoseconds.
|
|
|
57 |
*
|
|
|
58 |
* Inspired by https://stackoverflow.com/a/40497811
|
|
|
59 |
*
|
|
|
60 |
* @return The value of gettimeofday, in nanoseconds.
|
|
|
61 |
*/
|
|
|
62 |
static int64_t UptimeInNanoseconds() {
|
|
|
63 |
int64_t before_now_nsec;
|
|
|
64 |
int64_t after_now_nsec;
|
|
|
65 |
struct timeval now;
|
|
|
66 |
|
|
|
67 |
before_now_nsec = KernelBootTimeInNanoseconds();
|
|
|
68 |
// Addresses a race condition in which the system time has updated, but the boottime has not.
|
|
|
69 |
do {
|
|
|
70 |
gettimeofday(&now, NULL);
|
|
|
71 |
after_now_nsec = KernelBootTimeInNanoseconds();
|
|
|
72 |
} while (after_now_nsec != before_now_nsec);
|
|
|
73 |
return (int64_t)now.tv_sec * NSEC_PER_SEC + (int64_t)now.tv_usec * NSEC_PER_USEC -
|
|
|
74 |
before_now_nsec;
|
|
|
75 |
}
|
|
|
76 |
|
|
|
77 |
// TODO: Consider adding a 'trustedTime' property that can be populated by the response from a BE.
|
|
|
78 |
@implementation GDTCORClock
|
|
|
79 |
|
|
|
80 |
- (instancetype)init {
|
|
|
81 |
self = [super init];
|
|
|
82 |
if (self) {
|
|
|
83 |
_kernelBootTimeNanoseconds = KernelBootTimeInNanoseconds();
|
|
|
84 |
_uptimeNanoseconds = UptimeInNanoseconds();
|
|
|
85 |
_timeMillis =
|
|
|
86 |
(int64_t)((CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) * NSEC_PER_USEC);
|
|
|
87 |
_timezoneOffsetSeconds = [[NSTimeZone systemTimeZone] secondsFromGMT];
|
|
|
88 |
}
|
|
|
89 |
return self;
|
|
|
90 |
}
|
|
|
91 |
|
|
|
92 |
+ (GDTCORClock *)snapshot {
|
|
|
93 |
return [[GDTCORClock alloc] init];
|
|
|
94 |
}
|
|
|
95 |
|
|
|
96 |
+ (instancetype)clockSnapshotInTheFuture:(uint64_t)millisInTheFuture {
|
|
|
97 |
GDTCORClock *snapshot = [self snapshot];
|
|
|
98 |
snapshot->_timeMillis += millisInTheFuture;
|
|
|
99 |
return snapshot;
|
|
|
100 |
}
|
|
|
101 |
|
|
|
102 |
- (BOOL)isAfter:(GDTCORClock *)otherClock {
|
|
|
103 |
// These clocks are trivially comparable when they share a kernel boot time.
|
|
|
104 |
if (_kernelBootTimeNanoseconds == otherClock->_kernelBootTimeNanoseconds) {
|
|
|
105 |
int64_t timeDiff = (_timeMillis + _timezoneOffsetSeconds) -
|
|
|
106 |
(otherClock->_timeMillis + otherClock->_timezoneOffsetSeconds);
|
|
|
107 |
return timeDiff > 0;
|
|
|
108 |
} else {
|
|
|
109 |
int64_t kernelBootTimeDiff =
|
|
|
110 |
otherClock->_kernelBootTimeNanoseconds - _kernelBootTimeNanoseconds;
|
|
|
111 |
// This isn't a great solution, but essentially, if the other clock's boot time is 'later', NO
|
|
|
112 |
// is returned. This can be altered by changing the system time and rebooting.
|
|
|
113 |
return kernelBootTimeDiff < 0 ? YES : NO;
|
|
|
114 |
}
|
|
|
115 |
}
|
|
|
116 |
|
|
|
117 |
- (int64_t)uptimeMilliseconds {
|
|
|
118 |
return self.uptimeNanoseconds / NSEC_PER_MSEC;
|
|
|
119 |
}
|
|
|
120 |
|
|
|
121 |
- (NSUInteger)hash {
|
|
|
122 |
return [@(_kernelBootTimeNanoseconds) hash] ^ [@(_uptimeNanoseconds) hash] ^
|
|
|
123 |
[@(_timeMillis) hash] ^ [@(_timezoneOffsetSeconds) hash];
|
|
|
124 |
}
|
|
|
125 |
|
|
|
126 |
- (BOOL)isEqual:(id)object {
|
|
|
127 |
return [self hash] == [object hash];
|
|
|
128 |
}
|
|
|
129 |
|
|
|
130 |
#pragma mark - NSSecureCoding
|
|
|
131 |
|
|
|
132 |
/** NSKeyedCoder key for timeMillis property. */
|
|
|
133 |
static NSString *const kGDTCORClockTimeMillisKey = @"GDTCORClockTimeMillis";
|
|
|
134 |
|
|
|
135 |
/** NSKeyedCoder key for timezoneOffsetMillis property. */
|
|
|
136 |
static NSString *const kGDTCORClockTimezoneOffsetSeconds = @"GDTCORClockTimezoneOffsetSeconds";
|
|
|
137 |
|
|
|
138 |
/** NSKeyedCoder key for _kernelBootTime ivar. */
|
|
|
139 |
static NSString *const kGDTCORClockKernelBootTime = @"GDTCORClockKernelBootTime";
|
|
|
140 |
|
|
|
141 |
/** NSKeyedCoder key for _uptimeNanoseconds ivar. */
|
|
|
142 |
static NSString *const kGDTCORClockUptime = @"GDTCORClockUptime";
|
|
|
143 |
|
|
|
144 |
+ (BOOL)supportsSecureCoding {
|
|
|
145 |
return YES;
|
|
|
146 |
}
|
|
|
147 |
|
|
|
148 |
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
|
|
|
149 |
self = [super init];
|
|
|
150 |
if (self) {
|
|
|
151 |
// TODO: If the kernelBootTimeNanoseconds is more recent, we need to change the kernel boot time
|
|
|
152 |
// and uptimeMillis ivars
|
|
|
153 |
_timeMillis = [aDecoder decodeInt64ForKey:kGDTCORClockTimeMillisKey];
|
|
|
154 |
_timezoneOffsetSeconds = [aDecoder decodeInt64ForKey:kGDTCORClockTimezoneOffsetSeconds];
|
|
|
155 |
_kernelBootTimeNanoseconds = [aDecoder decodeInt64ForKey:kGDTCORClockKernelBootTime];
|
|
|
156 |
_uptimeNanoseconds = [aDecoder decodeInt64ForKey:kGDTCORClockUptime];
|
|
|
157 |
}
|
|
|
158 |
return self;
|
|
|
159 |
}
|
|
|
160 |
|
|
|
161 |
- (void)encodeWithCoder:(NSCoder *)aCoder {
|
|
|
162 |
[aCoder encodeInt64:_timeMillis forKey:kGDTCORClockTimeMillisKey];
|
|
|
163 |
[aCoder encodeInt64:_timezoneOffsetSeconds forKey:kGDTCORClockTimezoneOffsetSeconds];
|
|
|
164 |
[aCoder encodeInt64:_kernelBootTimeNanoseconds forKey:kGDTCORClockKernelBootTime];
|
|
|
165 |
[aCoder encodeInt64:_uptimeNanoseconds forKey:kGDTCORClockUptime];
|
|
|
166 |
}
|
|
|
167 |
|
|
|
168 |
#pragma mark - Deprecated properties
|
|
|
169 |
|
|
|
170 |
- (int64_t)kernelBootTime {
|
|
|
171 |
return self.kernelBootTimeNanoseconds;
|
|
|
172 |
}
|
|
|
173 |
|
|
|
174 |
- (int64_t)uptime {
|
|
|
175 |
return self.uptimeNanoseconds;
|
|
|
176 |
}
|
|
|
177 |
|
|
|
178 |
@end
|