Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
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