2 * Copyright (c) 2017 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@
26 #import <CloudKit/CloudKit.h>
27 #import <CloudKit/CloudKit_Private.h>
30 #import "keychain/ckks/CKKSAnalytics.h"
31 #import "keychain/ot/OTDefines.h"
32 #import "keychain/ckks/CKKS.h"
33 #import "keychain/ckks/CKKSViewManager.h"
34 #import "keychain/ckks/CKKSKeychainView.h"
35 #include <utilities/SecFileLocations.h>
38 NSString* const CKKSAnalyticsInCircle = @"inCircle";
39 NSString* const CKKSAnalyticsHasTLKs = @"TLKs";
40 NSString* const CKKSAnalyticsSyncedClassARecently = @"inSyncA";
41 NSString* const CKKSAnalyticsSyncedClassCRecently = @"inSyncC";
42 NSString* const CKKSAnalyticsIncomingQueueIsErrorFree = @"IQNOE";
43 NSString* const CKKSAnalyticsOutgoingQueueIsErrorFree = @"OQNOE";
44 NSString* const CKKSAnalyticsInSync = @"inSync";
45 NSString* const CKKSAnalyticsValidCredentials = @"validCredentials";
46 NSString* const CKKSAnalyticsLastUnlock = @"lastUnlock";
47 NSString* const CKKSAnalyticsLastKeystateReady = @"lastKSR";
48 NSString* const CKKSAnalyticsLastInCircle = @"lastInCircle";
50 static NSString* const CKKSAnalyticsAttributeRecoverableError = @"recoverableError";
51 static NSString* const CKKSAnalyticsAttributeZoneName = @"zone";
52 static NSString* const CKKSAnalyticsAttributeErrorDomain = @"errorDomain";
53 static NSString* const CKKSAnalyticsAttributeErrorCode = @"errorCode";
54 static NSString* const CKKSAnalyticsAttributeErrorChain = @"errorChain";
56 CKKSAnalyticsFailableEvent* const CKKSEventProcessIncomingQueueClassA = (CKKSAnalyticsFailableEvent*)@"CKKSEventProcessIncomingQueueClassA";
57 CKKSAnalyticsFailableEvent* const CKKSEventProcessIncomingQueueClassC = (CKKSAnalyticsFailableEvent*)@"CKKSEventProcessIncomingQueueClassC";
58 CKKSAnalyticsFailableEvent* const CKKSEventProcessOutgoingQueue = (CKKSAnalyticsFailableEvent*)@"CKKSEventProcessOutgoingQueue";
59 CKKSAnalyticsFailableEvent* const CKKSEventUploadChanges = (CKKSAnalyticsFailableEvent*)@"CKKSEventUploadChanges";
60 CKKSAnalyticsFailableEvent* const CKKSEventStateError = (CKKSAnalyticsFailableEvent*)@"CKKSEventStateError";
61 CKKSAnalyticsFailableEvent* const CKKSEventProcessHealKeyHierarchy = (CKKSAnalyticsFailableEvent *)@"CKKSEventProcessHealKeyHierarchy";
63 NSString* const OctagonEventFailureReason = @"FailureReason";
65 CKKSAnalyticsFailableEvent* const OctagonEventPreflightBottle = (CKKSAnalyticsFailableEvent*)@"OctagonEventPreflightBottle";
66 CKKSAnalyticsFailableEvent* const OctagonEventLaunchBottle = (CKKSAnalyticsFailableEvent*)@"OctagonEventLaunchBottle";
67 CKKSAnalyticsFailableEvent* const OctagonEventRestoreBottle = (CKKSAnalyticsFailableEvent*)@"OctagonEventRestoreBottle";
68 CKKSAnalyticsFailableEvent* const OctagonEventScrubBottle = (CKKSAnalyticsFailableEvent*)@"OctagonEventScrubBottle";
69 CKKSAnalyticsFailableEvent* const OctagonEventSignIn = (CKKSAnalyticsFailableEvent *)@"OctagonEventSignIn";
70 CKKSAnalyticsFailableEvent* const OctagonEventSignOut = (CKKSAnalyticsFailableEvent *)@"OctagonEventSignIn";
71 CKKSAnalyticsFailableEvent* const OctagonEventRamp = (CKKSAnalyticsFailableEvent *)@"OctagonEventRamp";
72 CKKSAnalyticsFailableEvent* const OctagonEventBottleCheck = (CKKSAnalyticsFailableEvent *)@"OctagonEventBottleCheck";
73 CKKSAnalyticsFailableEvent* const OctagonEventCoreFollowUp = (CKKSAnalyticsFailableEvent *)@"OctagonEventCoreFollowUp";
75 CKKSAnalyticsSignpostEvent* const CKKSEventPushNotificationReceived = (CKKSAnalyticsSignpostEvent*)@"CKKSEventPushNotificationReceived";
76 CKKSAnalyticsSignpostEvent* const CKKSEventItemAddedToOutgoingQueue = (CKKSAnalyticsSignpostEvent*)@"CKKSEventItemAddedToOutgoingQueue";
77 CKKSAnalyticsSignpostEvent* const CKKSEventMissingLocalItemsFound = (CKKSAnalyticsSignpostEvent*)@"CKKSEventMissingLocalItemsFound";
78 CKKSAnalyticsSignpostEvent* const CKKSEventReachabilityTimerExpired = (CKKSAnalyticsSignpostEvent *)@"CKKSEventReachabilityTimerExpired";
80 CKKSAnalyticsActivity* const CKKSActivityOTFetchRampState = (CKKSAnalyticsActivity *)@"CKKSActivityOTFetchRampState";
81 CKKSAnalyticsActivity* const CKKSActivityOctagonSignIn = (CKKSAnalyticsActivity *)@"CKKSActivityOctagonSignIn";
82 CKKSAnalyticsActivity* const CKKSActivityOctagonPreflightBottle = (CKKSAnalyticsActivity *)@"CKKSActivityOctagonPreflightBottle";
83 CKKSAnalyticsActivity* const CKKSActivityOctagonLaunchBottle = (CKKSAnalyticsActivity *)@"CKKSActivityOctagonLaunchBottle";
84 CKKSAnalyticsActivity* const CKKSActivityOctagonRestore = (CKKSAnalyticsActivity *)@"CKKSActivityOctagonRestore";
85 CKKSAnalyticsActivity* const CKKSActivityScrubBottle = (CKKSAnalyticsActivity *)@"CKKSActivityScrubBottle";
86 CKKSAnalyticsActivity* const CKKSActivityBottleCheck = (CKKSAnalyticsActivity *)@"CKKSActivityBottleCheck";
88 @implementation CKKSAnalytics
90 + (NSString*)databasePath
92 // This block exists because we moved database locations in 11.3 for easier sandboxing of securityuploadd, so we're cleaning up.
93 static dispatch_once_t onceToken;
94 dispatch_once(&onceToken, ^{
95 WithPathInKeychainDirectory(CFSTR("ckks_analytics_v2.db"), ^(const char *filename) {
98 WithPathInKeychainDirectory(CFSTR("ckks_analytics_v2.db-wal"), ^(const char *filename) {
101 WithPathInKeychainDirectory(CFSTR("ckks_analytics_v2.db-shm"), ^(const char *filename) {
105 return [CKKSAnalytics defaultAnalyticsDatabasePath:@"ckks_analytics"];
108 + (instancetype)logger
110 // just here because I want it in the header for discoverability
111 return [super logger];
114 - (void)logSuccessForEvent:(CKKSAnalyticsFailableEvent*)event inView:(CKKSKeychainView*)view
116 [self logSuccessForEventNamed:[NSString stringWithFormat:@"%@-%@", view.zoneName, event]];
117 [self setDateProperty:[NSDate date] forKey:[NSString stringWithFormat:@"last_success_%@-%@", view.zoneName, event]];
120 - (bool)isCKPartialError:(NSError *)error
122 return [error.domain isEqualToString:CKErrorDomain] && error.code == CKErrorPartialFailure;
125 - (void)addCKPartialError:(NSMutableDictionary *)errorDictionary error:(NSError *)error depth:(NSUInteger)depth
127 // capture one random underlaying error
128 if ([self isCKPartialError:error]) {
129 NSDictionary<NSString *,NSError *> *partialErrors = error.userInfo[CKPartialErrorsByItemIDKey];
130 if ([partialErrors isKindOfClass:[NSDictionary class]]) {
131 for (NSString *key in partialErrors) {
132 NSError* ckError = partialErrors[key];
133 if (![ckError isKindOfClass:[NSError class]])
135 if ([ckError.domain isEqualToString:CKErrorDomain] && ckError.code == CKErrorBatchRequestFailed) {
138 NSDictionary *res = [self errorChain:ckError depth:(depth + 1)];
140 errorDictionary[@"oneCloudKitPartialFailure"] = res;
148 // if we have underlying errors, capture the chain below the top-most error
149 - (NSDictionary *)errorChain:(NSError *)error depth:(NSUInteger)depth
151 NSMutableDictionary *errorDictionary = nil;
153 if (depth > 5 || ![error isKindOfClass:[NSError class]])
156 errorDictionary = [@{
157 @"domain" : error.domain,
158 @"code" : @(error.code),
161 errorDictionary[@"child"] = [self errorChain:error.userInfo[NSUnderlyingErrorKey] depth:(depth + 1)];
162 [self addCKPartialError:errorDictionary error:error depth:(depth + 1)];
164 return errorDictionary;
166 - (void)logRecoverableError:(NSError*)error forEvent:(CKKSAnalyticsFailableEvent*)event zoneName:(NSString*)zoneName withAttributes:(NSDictionary *)attributes
171 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
173 /* Don't allow caller to overwrite our attributes, lets merge them first */
175 [eventAttributes setValuesForKeysWithDictionary:attributes];
178 [eventAttributes setValuesForKeysWithDictionary:@{
179 CKKSAnalyticsAttributeRecoverableError : @(YES),
180 CKKSAnalyticsAttributeZoneName : zoneName,
181 CKKSAnalyticsAttributeErrorDomain : error.domain,
182 CKKSAnalyticsAttributeErrorCode : @(error.code)
185 eventAttributes[CKKSAnalyticsAttributeErrorChain] = [self errorChain:error.userInfo[NSUnderlyingErrorKey] depth:0];
186 [self addCKPartialError:eventAttributes error:error depth:0];
188 [super logSoftFailureForEventNamed:event withAttributes:eventAttributes];
190 - (void)logRecoverableError:(NSError*)error forEvent:(CKKSAnalyticsFailableEvent*)event inView:(CKKSKeychainView*)view withAttributes:(NSDictionary *)attributes
195 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
197 /* Don't allow caller to overwrite our attributes, lets merge them first */
199 [eventAttributes setValuesForKeysWithDictionary:attributes];
202 [eventAttributes setValuesForKeysWithDictionary:@{
203 CKKSAnalyticsAttributeRecoverableError : @(YES),
204 CKKSAnalyticsAttributeZoneName : view.zoneName,
205 CKKSAnalyticsAttributeErrorDomain : error.domain,
206 CKKSAnalyticsAttributeErrorCode : @(error.code)
209 eventAttributes[CKKSAnalyticsAttributeErrorChain] = [self errorChain:error.userInfo[NSUnderlyingErrorKey] depth:0];
210 [self addCKPartialError:eventAttributes error:error depth:0];
212 [super logSoftFailureForEventNamed:event withAttributes:eventAttributes];
215 - (void)logUnrecoverableError:(NSError*)error forEvent:(CKKSAnalyticsFailableEvent*)event inView:(CKKSKeychainView*)view withAttributes:(NSDictionary *)attributes
220 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
222 [eventAttributes setValuesForKeysWithDictionary:attributes];
225 eventAttributes[CKKSAnalyticsAttributeErrorChain] = [self errorChain:error.userInfo[NSUnderlyingErrorKey] depth:0];
226 [self addCKPartialError:eventAttributes error:error depth:0];
228 [eventAttributes setValuesForKeysWithDictionary:@{
229 CKKSAnalyticsAttributeRecoverableError : @(NO),
230 CKKSAnalyticsAttributeZoneName : view.zoneName,
231 CKKSAnalyticsAttributeErrorDomain : error.domain,
232 CKKSAnalyticsAttributeErrorCode : @(error.code)
235 [self logHardFailureForEventNamed:event withAttributes:eventAttributes];
238 - (void)logUnrecoverableError:(NSError*)error forEvent:(CKKSAnalyticsFailableEvent*)event withAttributes:(NSDictionary *)attributes
243 NSMutableDictionary* eventAttributes = [NSMutableDictionary dictionary];
245 /* Don't allow caller to overwrite our attributes, lets merge them first */
247 [eventAttributes setValuesForKeysWithDictionary:attributes];
250 eventAttributes[CKKSAnalyticsAttributeErrorChain] = [self errorChain:error.userInfo[NSUnderlyingErrorKey] depth:0];
251 [self addCKPartialError:eventAttributes error:error depth:0];
253 [eventAttributes setValuesForKeysWithDictionary:@{
254 CKKSAnalyticsAttributeRecoverableError : @(NO),
255 CKKSAnalyticsAttributeZoneName : OctagonEventAttributeZoneName,
256 CKKSAnalyticsAttributeErrorDomain : error.domain,
257 CKKSAnalyticsAttributeErrorCode : @(error.code)
260 [self logHardFailureForEventNamed:event withAttributes:eventAttributes];
263 - (void)noteEvent:(CKKSAnalyticsSignpostEvent*)event
265 [self noteEventNamed:event];
267 - (void)noteEvent:(CKKSAnalyticsSignpostEvent*)event inView:(CKKSKeychainView*)view
269 [self noteEventNamed:[NSString stringWithFormat:@"%@-%@", view.zoneName, event]];
272 - (NSDate*)dateOfLastSuccessForEvent:(CKKSAnalyticsFailableEvent*)event inView:(CKKSKeychainView*)view
274 return [self datePropertyForKey:[NSString stringWithFormat:@"last_success_%@-%@", view.zoneName, event]];
277 - (void)setDateProperty:(NSDate*)date forKey:(NSString*)key inView:(CKKSKeychainView *)view
279 [self setDateProperty:date forKey:[NSString stringWithFormat:@"%@-%@", key, view.zoneName]];
281 - (NSDate *)datePropertyForKey:(NSString *)key inView:(CKKSKeychainView *)view
283 return [self datePropertyForKey:[NSString stringWithFormat:@"%@-%@", key, view.zoneName]];