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 {
57 if ((self = [super init])) {
58 NSMutableSet *attributeClasses = [[[self class] supportedAttributeClasses] mutableCopy];
59 [attributeClasses addObject:[NSDictionary class]];
61 _eventName = [coder decodeObjectOfClass:[NSString class] forKey:@"eventName"];
62 _attributes = [coder decodeObjectOfClasses:attributeClasses forKey:@"attributes"];
64 if (!_eventName || !_attributes) {
71 - (void)encodeWithCoder:(NSCoder *)coder {
72 [coder encodeObject:self.eventName forKey:@"eventName"];
73 [coder encodeObject:self.attributes forKey:@"attributes"];
76 + (NSSet *)supportedAttributeClasses {
77 static NSSet *supported = NULL;
78 static dispatch_once_t onceToken;
79 dispatch_once(&onceToken, ^{
86 supported = [NSSet setWithArray:clss];
91 - (void)setObject:(nullable id)object forKeyedSubscript:(NSString *)key {
97 for (Class cls in [[self class] supportedAttributeClasses]) {
98 if ([object isKindOfClass:cls]) {
104 os_log(OS_LOG_DEFAULT, "genericMetric %{public}@ with unhandled metric type: %{public}@", key, NSStringFromClass([object class]));
108 @synchronized(self) {
109 self.attributes[key] = object;
112 - (void)setMetricValue:(nullable id)metric forKey:(NSString *)key {
116 - (uint64_t)convertTimeIntervalToServerTime:(NSTimeInterval)timeInterval
118 return (uint64_t)((timeInterval + NSTimeIntervalSince1970) * 1000.);
121 - (SECC2MPGenericEvent *)genericEvent {
122 SECC2MPGenericEvent* genericEvent = [[SECC2MPGenericEvent alloc] init];
124 genericEvent.name = self.eventName;
125 genericEvent.type = SECC2MPGenericEvent_Type_cloudkit_client;
127 genericEvent.timestampStart = 0;
128 genericEvent.timestampEnd = 0;
129 [self.attributes enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
130 SECC2MPGenericEventMetric* metric = [[SECC2MPGenericEventMetric alloc] init];
133 metric.value = [[SECC2MPGenericEventMetricValue alloc] init];
134 if ([obj isKindOfClass:[NSError class]]) {
135 metric.value.errorValue = [self generateError:obj];
136 } else if ([obj isKindOfClass:[NSDate class]]) {
137 metric.value.dateValue = [self convertTimeIntervalToServerTime:[obj timeIntervalSinceReferenceDate]];
138 } else if ([obj isKindOfClass:[NSNumber class]]) {
139 metric.value.doubleValue = [obj doubleValue];
140 } else if ([obj isKindOfClass:[NSString class]]) {
141 metric.value.stringValue = obj;
143 // should never happen since setObject:forKeyedSubscript: validates the input and rejects invalid input
147 [genericEvent addMetric:metric];
153 - (SECC2MPError*) generateError:(NSError*)error
155 SECC2MPError* generatedError = [[SECC2MPError alloc] init];
156 generatedError.errorDomain = error.domain;
157 generatedError.errorCode = error.code;
158 if ([SecC2DeviceInfo isAppleInternal]) {
159 generatedError.errorDescription = error.userInfo[NSLocalizedDescriptionKey];
161 NSError* underlyingError = error.userInfo[NSUnderlyingErrorKey];
162 if (underlyingError) {
163 generatedError.underlyingError = [self generateError:underlyingError];
165 return generatedError;