2 * Copyright (c) 2018 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
25 #import "keychain/analytics/SecEventMetric.h"
26 #import "keychain/analytics/SecC2DeviceInfo.h"
27 #import "keychain/analytics/C2Metric/SECC2MPMetric.h"
28 #import "keychain/analytics/C2Metric/SECC2MPGenericEvent.h"
29 #import "keychain/analytics/C2Metric/SECC2MPGenericEventMetric.h"
30 #import "keychain/analytics/C2Metric/SECC2MPGenericEventMetricValue.h"
31 #import "keychain/analytics/C2Metric/SECC2MPError.h"
35 @interface SecEventMetric ()
36 @property NSString *eventName;
37 @property NSMutableDictionary *attributes;
40 @implementation SecEventMetric
42 + (BOOL)supportsSecureCoding {
46 - (instancetype)initWithEventName:(NSString *)name
48 if ((self = [super init]) == NULL) {
51 self.eventName = name;
52 self.attributes = [NSMutableDictionary dictionary];
56 - (instancetype)initWithCoder:(NSCoder *)coder {
59 NSMutableSet *attributeClasses = [[[self class] supportedAttributeClasses] mutableCopy];
60 [attributeClasses addObject:[NSDictionary class]];
62 _eventName = [coder decodeObjectOfClass:[NSString class] forKey:@"eventName"];
63 _attributes = [coder decodeObjectOfClasses:attributeClasses forKey:@"attributes"];
65 if (!_eventName || !_attributes) {
72 - (void)encodeWithCoder:(NSCoder *)coder {
73 [coder encodeObject:self.eventName forKey:@"eventName"];
74 [coder encodeObject:self.attributes forKey:@"attributes"];
77 + (NSSet *)supportedAttributeClasses {
78 static NSSet *supported = NULL;
79 static dispatch_once_t onceToken;
80 dispatch_once(&onceToken, ^{
87 supported = [NSSet setWithArray:clss];
92 - (void)setObject:(nullable id)object forKeyedSubscript:(NSString *)key {
98 for (Class cls in [[self class] supportedAttributeClasses]) {
99 if ([object isKindOfClass:cls]) {
105 os_log(OS_LOG_DEFAULT, "genericMetric %{public}@ with unhandled metric type: %{public}@", key, NSStringFromClass([object class]));
109 @synchronized(self) {
110 self.attributes[key] = object;
113 - (void)setMetricValue:(nullable id)metric forKey:(NSString *)key {
117 - (uint64_t)convertTimeIntervalToServerTime:(NSTimeInterval)timeInterval
119 return (uint64_t)((timeInterval + NSTimeIntervalSince1970) * 1000.);
122 - (SECC2MPGenericEvent *)genericEvent {
123 SECC2MPGenericEvent* genericEvent = [[SECC2MPGenericEvent alloc] init];
125 genericEvent.name = self.eventName;
126 genericEvent.type = SECC2MPGenericEvent_Type_cloudkit_client;
128 genericEvent.timestampStart = 0;
129 genericEvent.timestampEnd = 0;
130 [self.attributes enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
131 SECC2MPGenericEventMetric* metric = [[SECC2MPGenericEventMetric alloc] init];
134 metric.value = [[SECC2MPGenericEventMetricValue alloc] init];
135 if ([obj isKindOfClass:[NSError class]]) {
136 metric.value.errorValue = [self generateError:obj];
137 } else if ([obj isKindOfClass:[NSDate class]]) {
138 metric.value.dateValue = [self convertTimeIntervalToServerTime:[obj timeIntervalSinceReferenceDate]];
139 } else if ([obj isKindOfClass:[NSNumber class]]) {
140 metric.value.doubleValue = [obj doubleValue];
141 } else if ([obj isKindOfClass:[NSString class]]) {
142 metric.value.stringValue = obj;
144 // should never happen since setObject:forKeyedSubscript: validates the input and rejects invalid input
148 [genericEvent addMetric:metric];
154 - (SECC2MPError*) generateError:(NSError*)error
156 SECC2MPError* generatedError = [[SECC2MPError alloc] init];
157 generatedError.errorDomain = error.domain;
158 generatedError.errorCode = error.code;
159 if ([SecC2DeviceInfo isAppleInternal]) {
160 generatedError.errorDescription = error.userInfo[NSLocalizedDescriptionKey];
162 NSError* underlyingError = error.userInfo[NSUnderlyingErrorKey];
163 if (underlyingError) {
164 generatedError.underlyingError = [self generateError:underlyingError];
166 return generatedError;