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 "SFAnalyticsLogger.h"
28 #import "CKKSViewManager.h"
30 #import <objc/runtime.h>
32 NSString* const SFAnalyticsLoggerTableSuccessCount = @"success_count";
33 NSString* const SFAnalyticsLoggerColumnEventType = @"event_type";
34 NSString* const SFAnalyticsLoggerColumnSuccessCount = @"success_count";
35 NSString* const SFAnalyticsLoggerColumnHardFailureCount = @"hard_failure_count";
36 NSString* const SFAnalyticsLoggerColumnSoftFailureCount = @"soft_failure_count";
38 NSString* const SFAnalyticsLoggerTableHardFailures = @"hard_failures";
39 NSString* const SFAnalyticsLoggerTableSoftFailures = @"soft_failures";
40 NSString* const SFAnalyticsLoggerTableAllEvents = @"all_events";
41 NSString* const SFAnalyticsLoggerColumnDate = @"timestamp";
42 NSString* const SFAnalyticsLoggerColumnData = @"data";
44 NSString* const SFAnalyticsLoggerUploadDate = @"upload_date";
46 NSString* const SFAnalyticsLoggerSplunkTopic = @"topic";
47 NSString* const SFAnalyticsLoggerSplunkEventTime = @"eventTime";
48 NSString* const SFAnalyticsLoggerSplunkPostTime = @"postTime";
49 NSString* const SFAnalyticsLoggerSplunkEventType = @"eventType";
50 NSString* const SFAnalyticsLoggerMetricsBase = @"metricsBase";
51 NSString* const SFAnalyticsLoggerEventClassKey = @"eventClass";
53 NSString* const SFAnalyticsUserDefaultsSuite = @"com.apple.security.analytics";
55 static NSString* const SFAnalyticsLoggerTableSchema = @"CREATE TABLE IF NOT EXISTS hard_failures (\n"
56 @"id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
60 @"CREATE TRIGGER IF NOT EXISTS maintain_ring_buffer_hard_failures AFTER INSERT ON hard_failures\n"
62 @"DELETE FROM hard_failures WHERE id != NEW.id AND id % 999 = NEW.id % 999;\n"
64 @"CREATE TABLE IF NOT EXISTS soft_failures (\n"
65 @"id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
69 @"CREATE TRIGGER IF NOT EXISTS maintain_ring_buffer_soft_failures AFTER INSERT ON soft_failures\n"
71 @"DELETE FROM soft_failures WHERE id != NEW.id AND id % 999 = NEW.id % 999;\n"
73 @"CREATE TABLE IF NOT EXISTS all_events (\n"
74 @"id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
78 @"CREATE TRIGGER IF NOT EXISTS maintain_ring_buffer_all_events AFTER INSERT ON all_events\n"
80 @"DELETE FROM all_events WHERE id != NEW.id AND id % 10000 = NEW.id % 10000;\n"
82 @"CREATE TABLE IF NOT EXISTS success_count (\n"
83 @"event_type STRING PRIMARY KEY,\n"
84 @"success_count INTEGER,\n"
85 @"hard_failure_count INTEGER,\n"
86 @"soft_failure_count INTEGER\n"
89 #define SFANALYTICS_SPLUNK_DEV 0
90 #define SFANALYTICS_MAX_EVENTS_TO_REPORT 999
92 #if SFANALYTICS_SPLUNK_DEV
93 #define SECONDS_BETWEEN_UPLOADS 10
95 // three days = 60 seconds times 60 minutes * 72 hours
96 #define SECONDS_BETWEEN_UPLOADS (60 * 60 * 72)
99 #define SECONDS_PER_DAY (60 * 60 * 24)
101 typedef NS_ENUM(NSInteger, SFAnalyticsEventClass) {
102 SFAnalyticsEventClassSuccess,
103 SFAnalyticsEventClassHardFailure,
104 SFAnalyticsEventClassSoftFailure,
105 SFAnalyticsEventClassNote
108 @interface SFAnalyticsLoggerSQLiteStore : SFSQLite
110 @property (readonly, strong) NSArray* failureRecords;
111 @property (readonly, strong) NSArray* allEvents;
112 @property (readwrite, strong) NSDate* uploadDate;
114 + (instancetype)storeWithPath:(NSString*)path schema:(NSString*)schema;
116 - (void)incrementSuccessCountForEventType:(NSString*)eventType;
117 - (void)incrementHardFailureCountForEventType:(NSString*)eventType;
118 - (void)incrementSoftFailureCountForEventType:(NSString*)eventType;
119 - (NSInteger)successCountForEventType:(NSString*)eventType;
120 - (NSInteger)hardFailureCountForEventType:(NSString*)eventType;
121 - (NSInteger)softFailureCountForEventType:(NSString*)eventType;
122 - (void)addEventDict:(NSDictionary*)eventDict toTable:(NSString*)table;
123 - (void)clearAllData;
125 - (NSDictionary*)summaryCounts;
129 @implementation SFAnalyticsLogger {
130 SFAnalyticsLoggerSQLiteStore* _database;
131 NSURL* _splunkUploadURL;
132 NSString* _splunkTopicName;
133 NSURL* _splunkBagURL;
134 dispatch_queue_t _queue;
135 NSInteger _secondsBetweenUploads;
136 NSDictionary* _metricsBase; // data the server provides and wants us to send back
137 NSArray* _blacklistedFields;
138 NSArray* _blacklistedEvents;
140 unsigned int _allowInsecureSplunkCert:1;
141 unsigned int _disableLogging:1;
142 unsigned int _disableUploads:1;
143 unsigned int _ignoreServersMessagesTellingUsToGoAway:1;
146 @synthesize splunkUploadURL = _splunkUploadURL;
147 @synthesize splunkBagURL = _splunkBagURL;
148 @synthesize splunkTopicName = _splunkTopicName;
149 @synthesize splunkLoggingQueue = _queue;
151 + (instancetype)logger
153 #if TARGET_OS_SIMULATOR
157 if (self == [SFAnalyticsLogger class]) {
158 secerror("attempt to instatiate abstract class SFAnalyticsLogger");
162 SFAnalyticsLogger* logger = nil;
163 @synchronized(self) {
164 logger = objc_getAssociatedObject(self, "SFAnalyticsLoggerInstance");
166 logger = [[self alloc] init];
167 objc_setAssociatedObject(self, "SFAnalyticsLoggerInstance", logger, OBJC_ASSOCIATION_RETAIN);
174 + (NSString*)databasePath
179 + (NSInteger)fuzzyDaysSinceDate:(NSDate*)date
181 NSTimeInterval timeIntervalSinceDate = [[NSDate date] timeIntervalSinceDate:date];
182 if (timeIntervalSinceDate < SECONDS_PER_DAY) {
185 else if (timeIntervalSinceDate < (SECONDS_PER_DAY * 7)) {
188 else if (timeIntervalSinceDate < (SECONDS_PER_DAY * 30)) {
191 else if (timeIntervalSinceDate < (SECONDS_PER_DAY * 365)) {
201 if (self = [super init]) {
202 _database = [SFAnalyticsLoggerSQLiteStore storeWithPath:self.class.databasePath schema:SFAnalyticsLoggerTableSchema];
203 _queue = dispatch_queue_create("com.apple.security.analytics", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
204 _secondsBetweenUploads = SECONDS_BETWEEN_UPLOADS;
206 NSDictionary* systemDefaultValues = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle bundleWithPath:@"/System/Library/Frameworks/Security.framework"] pathForResource:@"SFAnalyticsLogging" ofType:@"plist"]];
207 _splunkTopicName = systemDefaultValues[@"splunk_topic"];
208 _splunkUploadURL = [NSURL URLWithString:systemDefaultValues[@"splunk_uploadURL"]];
209 _splunkBagURL = [NSURL URLWithString:systemDefaultValues[@"splunk_bagURL"]];
210 _allowInsecureSplunkCert = [[systemDefaultValues valueForKey:@"splunk_allowInsecureCertificate"] boolValue];
211 NSString* splunkEndpoint = systemDefaultValues[@"splunk_endpointDomain"];
213 NSUserDefaults* defaults = [[NSUserDefaults alloc] initWithSuiteName:SFAnalyticsUserDefaultsSuite];
214 NSString* userDefaultsSplunkTopic = [defaults stringForKey:@"splunk_topic"];
215 if (userDefaultsSplunkTopic) {
216 _splunkTopicName = userDefaultsSplunkTopic;
219 NSURL* userDefaultsSplunkUploadURL = [NSURL URLWithString:[defaults stringForKey:@"splunk_uploadURL"]];
220 if (userDefaultsSplunkUploadURL) {
221 _splunkUploadURL = userDefaultsSplunkUploadURL;
224 NSURL* userDefaultsSplunkBagURL = [NSURL URLWithString:[defaults stringForKey:@"splunk_bagURL"]];
225 if (userDefaultsSplunkUploadURL) {
226 _splunkBagURL = userDefaultsSplunkBagURL;
229 BOOL userDefaultsAllowInsecureSplunkCert = [defaults boolForKey:@"splunk_allowInsecureCertificate"];
230 _allowInsecureSplunkCert |= userDefaultsAllowInsecureSplunkCert;
232 NSString* userDefaultsSplunkEndpoint = [defaults stringForKey:@"splunk_endpointDomain"];
233 if (userDefaultsSplunkEndpoint) {
234 splunkEndpoint = userDefaultsSplunkEndpoint;
237 #if SFANALYTICS_SPLUNK_DEV
238 _ignoreServersMessagesTellingUsToGoAway = YES;
240 if (!_splunkUploadURL && splunkEndpoint) {
241 NSString* urlString = [NSString stringWithFormat:@"https://%@/report/2/%@", splunkEndpoint, _splunkTopicName];
242 _splunkUploadURL = [NSURL URLWithString:urlString];
245 (void)splunkEndpoint;
252 - (void)logSuccessForEventNamed:(NSString*)eventName
254 [self logEventNamed:eventName class:SFAnalyticsEventClassSuccess attributes:nil];
257 - (void)logHardFailureForEventNamed:(NSString*)eventName withAttributes:(NSDictionary*)attributes
259 [self logEventNamed:eventName class:SFAnalyticsEventClassSoftFailure attributes:attributes];
262 - (void)logSoftFailureForEventNamed:(NSString*)eventName withAttributes:(NSDictionary*)attributes
264 [self logEventNamed:eventName class:SFAnalyticsEventClassHardFailure attributes:attributes];
267 - (void)noteEventNamed:(NSString*)eventName
269 [self logEventNamed:eventName class:SFAnalyticsEventClassNote attributes:nil];
272 - (void)logEventNamed:(NSString*)eventName class:(SFAnalyticsEventClass)class attributes:(NSDictionary*)attributes
275 secinfo("SFAnalytics", "attempt to log an event with no name");
279 __block NSDate* uploadDate = nil;
280 __weak __typeof(self) weakSelf = self;
281 dispatch_sync(_queue, ^{
282 __strong __typeof(self) strongSelf = weakSelf;
283 if (!strongSelf || strongSelf->_disableLogging || [strongSelf->_blacklistedEvents containsObject:eventName]) {
287 NSDictionary* eventDict = [self eventDictForEventName:eventName withAttributes:attributes eventClass:class];
288 [strongSelf->_database addEventDict:eventDict toTable:SFAnalyticsLoggerTableAllEvents];
290 if (class == SFAnalyticsEventClassHardFailure) {
291 NSDictionary* strippedDict = [self eventDictWithBlacklistedFieldsStrippedFrom:eventDict];
292 [strongSelf->_database addEventDict:strippedDict toTable:SFAnalyticsLoggerTableHardFailures];
293 [strongSelf->_database incrementHardFailureCountForEventType:eventName];
295 else if (class == SFAnalyticsEventClassSoftFailure) {
296 NSDictionary* strippedDict = [self eventDictWithBlacklistedFieldsStrippedFrom:eventDict];
297 [strongSelf->_database addEventDict:strippedDict toTable:SFAnalyticsLoggerTableSoftFailures];
298 [strongSelf->_database incrementSoftFailureCountForEventType:eventName];
300 else if (class == SFAnalyticsEventClassSuccess || class == SFAnalyticsEventClassNote) {
301 [strongSelf->_database incrementSuccessCountForEventType:eventName];
304 uploadDate = strongSelf->_database.uploadDate;
307 NSDate* nowDate = [NSDate date];
309 if ([nowDate compare:uploadDate] == NSOrderedDescending) {
310 NSError* error = nil;
311 BOOL uploadSuccess = [self forceUploadWithError:&error];
313 secinfo("SFAnalytics", "uploaded sync health data");
314 [self resetUploadDate:YES];
318 secerror("SFAnalytics: failed to upload json to analytics server with error: %@", error);
323 [self resetUploadDate:NO];
327 - (void)resetUploadDate:(BOOL)clearData
329 __weak __typeof(self) weakSelf = self;
330 dispatch_sync(_queue, ^{
331 __strong __typeof(self) strongSelf = weakSelf;
337 [strongSelf->_database clearAllData];
339 strongSelf->_database.uploadDate = [NSDate dateWithTimeIntervalSinceNow:strongSelf->_secondsBetweenUploads];
343 - (NSDictionary*)eventDictForEventName:(NSString*)eventName withAttributes:(NSDictionary*)attributes eventClass:(SFAnalyticsEventClass)eventClass
345 NSMutableDictionary* eventDict = attributes ? attributes.mutableCopy : [NSMutableDictionary dictionary];
346 eventDict[SFAnalyticsLoggerSplunkTopic] = _splunkTopicName;
347 eventDict[SFAnalyticsLoggerSplunkEventType] = eventName;
348 eventDict[SFAnalyticsLoggerSplunkEventTime] = @([[NSDate date] timeIntervalSince1970] * 1000);
349 eventDict[SFAnalyticsLoggerEventClassKey] = @(eventClass);
351 [_metricsBase enumerateKeysAndObjectsUsingBlock:^(NSString* key, id object, BOOL* stop) {
352 if (!eventDict[key]) {
353 eventDict[key] = object;
360 - (NSDictionary*)eventDictWithBlacklistedFieldsStrippedFrom:(NSDictionary*)eventDict
362 NSMutableDictionary* strippedDict = eventDict.mutableCopy;
363 for (NSString* blacklistedField in _blacklistedFields) {
364 [strippedDict removeObjectForKey:blacklistedField];
369 - (void)setDateProperty:(NSDate*)date forKey:(NSString*)key
371 dispatch_sync(_queue, ^{
372 [self->_database setDateProperty:date forKey:key];
376 - (NSDate*)datePropertyForKey:(NSString*)key
378 __block NSDate* result = nil;
379 dispatch_sync(_queue, ^{
380 result = [self->_database datePropertyForKey:key];
385 - (NSDictionary*)extraValuesToUploadToServer
387 return [NSDictionary dictionary];
390 // this method is kind of evil for the fact that it has side-effects in pulling other things besides the metricsURL from the server, and as such should NOT be memoized.
391 // TODO redo this, probably to return a dictionary.
392 - (NSURL*)splunkUploadURL
394 dispatch_assert_queue(_queue);
396 if (_splunkUploadURL) {
397 return _splunkUploadURL;
400 __weak __typeof(self) weakSelf = self;
401 dispatch_semaphore_t sem = dispatch_semaphore_create(0);
403 __block NSError* error = nil;
404 NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
405 NSURLSession* storeBagSession = [NSURLSession sessionWithConfiguration:defaultConfiguration
409 NSURL* requestEndpoint = _splunkBagURL;
410 __block NSURL* result = nil;
411 NSURLSessionDataTask* storeBagTask = [storeBagSession dataTaskWithURL:requestEndpoint completionHandler:^(NSData * _Nullable data,
412 NSURLResponse * _Nullable __unused response,
413 NSError * _Nullable responseError) {
415 __strong __typeof(self) strongSelf = weakSelf;
420 if (data && !responseError) {
421 NSData *responseData = data; // shut up compiler
422 NSDictionary* responseDict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
423 if([responseDict isKindOfClass:NSDictionary.class] && !error) {
424 if (!self->_ignoreServersMessagesTellingUsToGoAway) {
425 strongSelf->_disableLogging = [[responseDict valueForKey:@"disabled"] boolValue];
426 if (strongSelf->_disableLogging || [[responseDict valueForKey:@"sendDisabled"] boolValue]) {
427 // then don't upload anything right now
428 secerror("not returning a splunk URL because uploads are disabled");
429 dispatch_semaphore_signal(sem);
433 NSUInteger millisecondsBetweenUploads = [[responseDict valueForKey:@"postFrequency"] unsignedIntegerValue] / 1000;
434 if (millisecondsBetweenUploads > 0) {
435 strongSelf->_secondsBetweenUploads = millisecondsBetweenUploads;
438 strongSelf->_blacklistedEvents = responseDict[@"blacklistedEvents"];
439 strongSelf->_blacklistedFields = responseDict[@"blacklistedFields"];
442 strongSelf->_metricsBase = responseDict[@"metricsBase"];
444 NSString* metricsEndpoint = responseDict[@"metricsUrl"];
445 if([metricsEndpoint isKindOfClass:NSString.class]) {
447 NSString* endpoint = [metricsEndpoint stringByAppendingFormat:@"/2/%@", strongSelf->_splunkTopicName];
448 secnotice("ckks", "got metrics endpoint: %@", endpoint);
449 NSURL* endpointURL = [NSURL URLWithString:endpoint];
450 if([endpointURL.scheme isEqualToString:@"https"]) {
451 result = endpointURL;
457 error = responseError;
460 secnotice("ckks", "Unable to fetch splunk endpoint at URL: %@ -- error: %@", requestEndpoint, error.description);
463 secnotice("ckks", "Malformed iTunes config payload!");
466 dispatch_semaphore_signal(sem);
469 [storeBagTask resume];
470 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
475 - (BOOL)forceUploadWithError:(NSError**)error
477 __block BOOL result = NO;
478 NSData* json = [self getLoggingJSONWithError:error];
479 dispatch_sync(_queue, ^{
480 if (json && [self _onQueuePostJSON:json error:error]) {
481 secinfo("ckks", "uploading sync health data: %@", json);
483 [self->_database clearAllData];
484 self->_database.uploadDate = [NSDate dateWithTimeIntervalSinceNow:self->_secondsBetweenUploads];
495 - (BOOL)_onQueuePostJSON:(NSData*)json error:(NSError**)error
497 dispatch_assert_queue(_queue);
500 * Create the NSURLSession
501 * We use the ephemeral session config because we don't need cookies or cache
503 NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
504 NSURLSession* postSession = [NSURLSession sessionWithConfiguration:defaultConfiguration
511 NSURL* postEndpoint = self.splunkUploadURL;
513 secerror("failed to get a splunk upload endpoint - not uploading");
517 NSMutableURLRequest* postRequest = [[NSMutableURLRequest alloc] init];
518 postRequest.URL = postEndpoint;
519 postRequest.HTTPMethod = @"POST";
520 postRequest.HTTPBody = json;
523 * Create the upload task.
525 dispatch_semaphore_t sem = dispatch_semaphore_create(0);
526 __block BOOL uploadSuccess = NO;
527 NSURLSessionDataTask* uploadTask = [postSession dataTaskWithRequest:postRequest
528 completionHandler:^(NSData * _Nullable __unused data, NSURLResponse * _Nullable response, NSError * _Nullable requestError) {
530 secerror("Error in uploading the events to splunk: %@", requestError);
532 else if (![response isKindOfClass:NSHTTPURLResponse.class]){
533 Class class = response.class;
534 secerror("Received the wrong kind of response: %@", NSStringFromClass(class));
537 NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
538 if(httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
541 secnotice("ckks", "Splunk upload success");
544 secnotice("ckks", "Splunk upload unexpected status to URL: %@ -- status: %d", postEndpoint, (int)(httpResponse.statusCode));
547 dispatch_semaphore_signal(sem);
550 secnotice("ckks", "Splunk upload start");
552 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
553 return uploadSuccess;
556 - (NSString*)stringForEventClass:(SFAnalyticsEventClass)eventClass
558 if (eventClass == SFAnalyticsEventClassNote) {
561 else if (eventClass == SFAnalyticsEventClassSuccess) {
562 return @"EventSuccess";
564 else if (eventClass == SFAnalyticsEventClassHardFailure) {
565 return @"EventHardFailure";
567 else if (eventClass == SFAnalyticsEventClassSoftFailure) {
568 return @"EventSoftFailure";
571 return @"EventUnknown";
575 - (NSString*)sysdiagnoseStringForEventRecord:(NSDictionary*)eventRecord
577 NSMutableDictionary* mutableEventRecord = eventRecord.mutableCopy;
578 [mutableEventRecord removeObjectForKey:SFAnalyticsLoggerSplunkTopic];
580 NSDate* eventDate = [NSDate dateWithTimeIntervalSince1970:[[eventRecord valueForKey:SFAnalyticsLoggerSplunkEventTime] doubleValue] / 1000];
581 [mutableEventRecord removeObjectForKey:SFAnalyticsLoggerSplunkEventTime];
583 NSString* eventName = eventRecord[SFAnalyticsLoggerSplunkEventType];
584 [mutableEventRecord removeObjectForKey:SFAnalyticsLoggerSplunkEventType];
586 SFAnalyticsEventClass eventClass = [[eventRecord valueForKey:SFAnalyticsLoggerEventClassKey] integerValue];
587 NSString* eventClassString = [self stringForEventClass:eventClass];
588 [mutableEventRecord removeObjectForKey:SFAnalyticsLoggerEventClassKey];
590 NSMutableString* additionalAttributesString = [NSMutableString string];
591 if (mutableEventRecord.count > 0) {
592 [additionalAttributesString appendString:@" - Attributes: {" ];
593 __block BOOL firstAttribute = YES;
594 [mutableEventRecord enumerateKeysAndObjectsUsingBlock:^(NSString* key, id object, BOOL* stop) {
595 NSString* openingString = firstAttribute ? @"" : @", ";
596 [additionalAttributesString appendString:[NSString stringWithFormat:@"%@%@ : %@", openingString, key, object]];
599 [additionalAttributesString appendString:@" }"];
602 return [NSString stringWithFormat:@"%@ %@: %@%@", eventDate, eventClassString, eventName, additionalAttributesString];
605 - (NSString*)getSysdiagnoseDumpWithError:(NSError**)error
607 NSMutableString* sysdiagnose = [[NSMutableString alloc] init];
609 NSDictionary* extraValues = self.extraValuesToUploadToServer;
610 [extraValues enumerateKeysAndObjectsUsingBlock:^(NSString* key, id object, BOOL* stop) {
611 [sysdiagnose appendFormat:@"Key: %@, Value: %@\n", key, object];
614 [sysdiagnose appendString:@"\n"];
616 dispatch_sync(_queue, ^{
617 NSArray* allEvents = self->_database.allEvents;
618 for (NSDictionary* eventRecord in allEvents) {
619 [sysdiagnose appendFormat:@"%@\n", [self sysdiagnoseStringForEventRecord:eventRecord]];
626 - (NSData*)getLoggingJSONWithError:(NSError**)error
628 __block NSData* json = nil;
629 NSDictionary* extraValues = self.extraValuesToUploadToServer;
630 dispatch_sync(_queue, ^{
631 NSArray* failureRecords = self->_database.failureRecords;
633 NSDictionary* successCounts = self->_database.summaryCounts;
634 NSInteger totalSuccessCount = 0;
635 NSInteger totalHardFailureCount = 0;
636 NSInteger totalSoftFailureCount = 0;
637 for (NSDictionary* perEventTypeSuccessCounts in successCounts.objectEnumerator) {
638 totalSuccessCount += [perEventTypeSuccessCounts[SFAnalyticsLoggerColumnSuccessCount] integerValue];
639 totalHardFailureCount += [perEventTypeSuccessCounts[SFAnalyticsLoggerColumnHardFailureCount] integerValue];
640 totalSoftFailureCount += [perEventTypeSuccessCounts[SFAnalyticsLoggerColumnSoftFailureCount] integerValue];
643 NSDate* now = [NSDate date];
645 NSMutableDictionary* healthSummaryEvent = extraValues ? extraValues.mutableCopy : [[NSMutableDictionary alloc] init];
646 healthSummaryEvent[SFAnalyticsLoggerSplunkTopic] = self->_splunkTopicName ?: [NSNull null];
647 healthSummaryEvent[SFAnalyticsLoggerSplunkEventTime] = @([now timeIntervalSince1970] * 1000);
648 healthSummaryEvent[SFAnalyticsLoggerSplunkEventType] = @"ckksHealthSummary";
649 healthSummaryEvent[SFAnalyticsLoggerColumnSuccessCount] = @(totalSuccessCount);
650 healthSummaryEvent[SFAnalyticsLoggerColumnHardFailureCount] = @(totalHardFailureCount);
651 healthSummaryEvent[SFAnalyticsLoggerColumnSoftFailureCount] = @(totalSoftFailureCount);
653 NSMutableArray* splunkRecords = failureRecords.mutableCopy;
654 [splunkRecords addObject:healthSummaryEvent];
656 NSDictionary* jsonDict = @{SFAnalyticsLoggerSplunkPostTime : @([now timeIntervalSince1970] * 1000), @"events" : splunkRecords};
658 json = [NSJSONSerialization dataWithJSONObject:jsonDict options:NSJSONWritingPrettyPrinted error:error];
664 - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
665 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {
666 assert(completionHandler);
668 secnotice("ckks", "Splunk upload challenge");
669 NSURLCredential *cred = nil;
670 SecTrustResultType result = kSecTrustResultInvalid;
672 if ([challenge previousFailureCount] > 0) {
673 // Previous failures occurred, bail
674 completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
676 } else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
678 * Evaluate trust for the certificate
681 SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
682 SecTrustEvaluate(serverTrust, &result);
683 if (_allowInsecureSplunkCert || (result == kSecTrustResultProceed) || (result == kSecTrustResultUnspecified)) {
685 * All is well, accept the credentials
687 if(_allowInsecureSplunkCert) {
688 secnotice("ckks", "Force Accepting Splunk Credential");
690 cred = [NSURLCredential credentialForTrust:serverTrust];
691 completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
695 * An error occurred in evaluating trust, bail
697 completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
701 * Just perform the default handling
703 completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
708 - (BOOL)ignoreServerDisablingMessages
710 return _ignoreServersMessagesTellingUsToGoAway;
713 - (void)setIgnoreServerDisablingMessages:(BOOL)ignoreServer
715 _ignoreServersMessagesTellingUsToGoAway = ignoreServer ? YES : NO;
718 - (BOOL)allowsInsecureSplunkCert
720 return _allowInsecureSplunkCert;
723 - (void)setAllowsInsecureSplunkCert:(BOOL)allowsInsecureSplunkCert
725 _allowInsecureSplunkCert = allowsInsecureSplunkCert ? YES : NO;
730 @implementation SFAnalyticsLoggerSQLiteStore
732 + (instancetype)storeWithPath:(NSString*)path schema:(NSString*)schema
734 SFAnalyticsLoggerSQLiteStore* store = nil;
735 @synchronized([SFAnalyticsLoggerSQLiteStore class]) {
736 static NSMutableDictionary* loggingStores = nil;
737 static dispatch_once_t onceToken;
738 dispatch_once(&onceToken, ^{
739 loggingStores = [[NSMutableDictionary alloc] init];
742 NSString* standardizedPath = path.stringByStandardizingPath;
743 store = loggingStores[standardizedPath];
745 store = [[self alloc] initWithPath:standardizedPath schema:schema];
746 loggingStores[standardizedPath] = store;
760 - (NSInteger)successCountForEventType:(NSString*)eventType
762 return [[[[self select:@[SFAnalyticsLoggerColumnSuccessCount] from:SFAnalyticsLoggerTableSuccessCount where:@"event_type = ?" bindings:@[eventType]] firstObject] valueForKey:SFAnalyticsLoggerColumnSuccessCount] integerValue];
765 - (void)incrementSuccessCountForEventType:(NSString*)eventType
767 NSInteger successCount = [self successCountForEventType:eventType];
768 NSInteger hardFailureCount = [self hardFailureCountForEventType:eventType];
769 NSInteger softFailureCount = [self softFailureCountForEventType:eventType];
770 [self insertOrReplaceInto:SFAnalyticsLoggerTableSuccessCount values:@{SFAnalyticsLoggerColumnEventType : eventType, SFAnalyticsLoggerColumnSuccessCount : @(successCount + 1), SFAnalyticsLoggerColumnHardFailureCount : @(hardFailureCount), SFAnalyticsLoggerColumnSoftFailureCount : @(softFailureCount)}];
773 - (NSInteger)hardFailureCountForEventType:(NSString*)eventType
775 return [[[[self select:@[SFAnalyticsLoggerColumnHardFailureCount] from:SFAnalyticsLoggerTableSuccessCount where:@"event_type = ?" bindings:@[eventType]] firstObject] valueForKey:SFAnalyticsLoggerColumnHardFailureCount] integerValue];
778 - (NSInteger)softFailureCountForEventType:(NSString*)eventType
780 return [[[[self select:@[SFAnalyticsLoggerColumnSoftFailureCount] from:SFAnalyticsLoggerTableSuccessCount where:@"event_type = ?" bindings:@[eventType]] firstObject] valueForKey:SFAnalyticsLoggerColumnSoftFailureCount] integerValue];
783 - (void)incrementHardFailureCountForEventType:(NSString*)eventType
785 NSInteger successCount = [self successCountForEventType:eventType];
786 NSInteger hardFailureCount = [self hardFailureCountForEventType:eventType];
787 NSInteger softFailureCount = [self softFailureCountForEventType:eventType];
788 [self insertOrReplaceInto:SFAnalyticsLoggerTableSuccessCount values:@{SFAnalyticsLoggerColumnEventType : eventType, SFAnalyticsLoggerColumnSuccessCount : @(successCount), SFAnalyticsLoggerColumnHardFailureCount : @(hardFailureCount + 1), SFAnalyticsLoggerColumnSoftFailureCount : @(softFailureCount)}];
791 - (void)incrementSoftFailureCountForEventType:(NSString*)eventType
793 NSInteger successCount = [self successCountForEventType:eventType];
794 NSInteger hardFailureCount = [self hardFailureCountForEventType:eventType];
795 NSInteger softFailureCount = [self softFailureCountForEventType:eventType];
796 [self insertOrReplaceInto:SFAnalyticsLoggerTableSuccessCount values:@{SFAnalyticsLoggerColumnEventType : eventType, SFAnalyticsLoggerColumnSuccessCount : @(successCount), SFAnalyticsLoggerColumnHardFailureCount : @(hardFailureCount), SFAnalyticsLoggerColumnSoftFailureCount : @(softFailureCount + 1)}];
799 - (NSDictionary*)summaryCounts
801 NSMutableDictionary* successCountsDict = [NSMutableDictionary dictionary];
802 NSArray* rows = [self selectAllFrom:SFAnalyticsLoggerTableSuccessCount where:nil bindings:nil];
803 for (NSDictionary* rowDict in rows) {
804 NSString* eventName = rowDict[SFAnalyticsLoggerColumnEventType];
806 secinfo("SFAnalytics", "ignoring entry in success counts table without an event name");
810 successCountsDict[eventName] = @{SFAnalyticsLoggerTableSuccessCount : rowDict[SFAnalyticsLoggerColumnSuccessCount], SFAnalyticsLoggerColumnHardFailureCount : rowDict[SFAnalyticsLoggerColumnHardFailureCount], SFAnalyticsLoggerColumnSoftFailureCount : rowDict[SFAnalyticsLoggerColumnSoftFailureCount]};
813 return successCountsDict;
816 - (NSArray*)failureRecords
818 NSArray* recordBlobs = [self select:@[SFAnalyticsLoggerColumnData] from:SFAnalyticsLoggerTableHardFailures];
819 if (recordBlobs.count < SFANALYTICS_MAX_EVENTS_TO_REPORT) {
820 NSArray* softFailureBlobs = [self select:@[SFAnalyticsLoggerColumnData] from:SFAnalyticsLoggerTableSoftFailures];
821 if (softFailureBlobs.count > 0) {
822 NSInteger numSoftFailuresToReport = SFANALYTICS_MAX_EVENTS_TO_REPORT - recordBlobs.count;
823 recordBlobs = [recordBlobs arrayByAddingObjectsFromArray:[softFailureBlobs subarrayWithRange:NSMakeRange(softFailureBlobs.count - numSoftFailuresToReport, numSoftFailuresToReport)]];
827 NSMutableArray* failureRecords = [[NSMutableArray alloc] init];
828 for (NSDictionary* row in recordBlobs) {
829 NSDictionary* deserializedRecord = [NSPropertyListSerialization propertyListWithData:row[SFAnalyticsLoggerColumnData] options:0 format:nil error:nil];
830 [failureRecords addObject:deserializedRecord];
833 return failureRecords;
836 - (NSArray*)allEvents
838 NSArray* recordBlobs = [self select:@[SFAnalyticsLoggerColumnData] from:SFAnalyticsLoggerTableAllEvents];
839 NSMutableArray* records = [[NSMutableArray alloc] init];
840 for (NSDictionary* row in recordBlobs) {
841 NSDictionary* deserializedRecord = [NSPropertyListSerialization propertyListWithData:row[SFAnalyticsLoggerColumnData] options:0 format:nil error:nil];
842 [records addObject:deserializedRecord];
847 - (void)addEventDict:(NSDictionary*)eventDict toTable:(NSString*)table
849 NSError* error = nil;
850 NSData* serializedRecord = [NSPropertyListSerialization dataWithPropertyList:eventDict format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error];
851 if(!error && serializedRecord) {
852 [self insertOrReplaceInto:table values:@{SFAnalyticsLoggerColumnDate : [NSDate date], SFAnalyticsLoggerColumnData : serializedRecord}];
854 if(error && !serializedRecord) {
855 secerror("Couldn't serialize failure record: %@", error);
859 - (NSDate*)uploadDate
861 return [self datePropertyForKey:SFAnalyticsLoggerUploadDate];
864 - (void)setUploadDate:(NSDate*)uploadDate
866 [self setDateProperty:uploadDate forKey:SFAnalyticsLoggerUploadDate];
871 [self deleteFrom:SFAnalyticsLoggerTableSuccessCount where:@"event_type like ?" bindings:@[@"%"]];
872 [self deleteFrom:SFAnalyticsLoggerTableHardFailures where:@"id >= 0" bindings:nil];
873 [self deleteFrom:SFAnalyticsLoggerTableSoftFailures where:@"id >= 0" bindings:nil];