/*
* Copyright (c) 2017 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#if OCTAGON
#import "CKKSLogger.h"
#import "debugging.h"
#import "CKKS.h"
#import "CKKSViewManager.h"
#import "keychain/ckks/CKKSKeychainView.h"
#include <utilities/SecFileLocations.h>
#import <Security/SFSQLite.h>
#import <os/log.h>
NSString* const CKKSLoggerTableSuccessCount = @"success_count";
NSString* const CKKSLoggerColumnEventType = @"event_type";
NSString* const CKKSLoggerColumnSuccessCount = @"success_count";
NSString* const CKKSLoggerColumnFailureCount = @"failure_count";
NSString* const CKKSLoggerTableFailures = @"failures";
NSString* const CKKSLoggerColumnData = @"data";
NSString* const CKKSLoggerUploadDate = @"upload_date";
NSString* const CKKSLoggerLastClassASync = @"last_class_a_sync";
NSString* const CKKSLoggerLastClassCSync = @"last_class_c_sync";
NSString* const CKKSLoggerDaysSinceLastSyncClassA = @"lastSyncClassA";
NSString* const CKKSLoggerDaysSinceLastSyncClassC = @"lastSyncClassC";
NSString* const CKKSLoggerSplunkTopic = @"topic";
NSString* const CKKSLoggerSplunkEventTime = @"eventTime";
NSString* const CKKSLoggerSplunkPostTime = @"postTime";
NSString* const CKKSLoggerSplunkEvents = @"events";
NSString* const CKKSLoggerSplunkEventType = @"eventType";
NSString* const CKKSLoggerMetricsBase = @"metricsBase";
NSString* const CKKSLoggerValueSuccess = @"success";
#define CKKS_SPLUNK_DEV 0
#if CKKS_SPLUNK_DEV
#define SECONDS_BETWEEN_UPLOADS 10
#else
// three days = 60 seconds times 60 minutes * 72 hours
#define SECONDS_BETWEEN_UPLOADS (60 * 60 * 72)
#endif
NSString* const CKKSLoggingTableSchema = @"CREATE TABLE IF NOT EXISTS failures (\n"
@"id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
@"data BLOB\n"
@");\n"
@"CREATE TRIGGER IF NOT EXISTS maintain_ring_buffer AFTER INSERT ON failures\n"
@"BEGIN\n"
@"DELETE FROM failures WHERE id != NEW.id AND id @"END;\n"
@"CREATE TABLE IF NOT EXISTS success_count (\n"
@"event_type STRING PRIMARY KEY,\n"
@"success_count INTEGER,\n"
@"failure_count INTEGER\n"
@");\n";
static NSString* CKKSLoggingTablePath()
{
return [(__bridge_transfer NSURL*)SecCopyURLForFileInKeychainDirectory((__bridge CFStringRef)@"ckks_analytics_v1.db") path];
}
@interface CKKSLoggerSQLiteStore : SFSQLite
+ (instancetype)sharedStore;
@property (readonly, strong) NSArray* failureRecords;
@property (readwrite, strong) NSDate* uploadDate;
- (void)incrementSuccessCountForEventType:(NSString*)eventType;
- (void)incrementFailureCountForEventType:(NSString*)eventType;
- (NSInteger)successCountForEventType:(NSString*)eventType;
- (NSInteger)failureCountForEventType:(NSString*)eventType;
- (void)addFailureRecord:(NSDictionary*)valueDict;
- (void)clearAllData;
- (NSDictionary*)summaryCounts;
@end
@implementation CKKSLogger {
NSURL* _splunkUploadURL;
NSString* _splunkTopicName;
NSURL* _splunkBagURL;
dispatch_queue_t _queue;
NSInteger _secondsBetweenUploads;
NSDictionary* _metricsBase; // data the server provides and wants us to send back
NSArray* _blacklistedFields;
NSArray* _blacklistedEvents;
unsigned int _allowInsecureSplunkCert:1;
unsigned int _disableLogging:1;
unsigned int _disableUploads:1;
unsigned int _ignoreServersMessagesTellingUsToGoAway:1;
}
@synthesize splunkUploadURL = _splunkUploadURL;
@synthesize splunkBagURL = _splunkBagURL;
@synthesize splunkTopicName = _splunkTopicName;
@synthesize splunkLoggingQueue = _queue;
+ (instancetype)logger
{
#if TARGET_OS_SIMULATOR
return nil;
#endif
static CKKSLogger* __sharedLogger;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__sharedLogger = [[CKKSLogger alloc] init];
});
return __sharedLogger;
}
- (instancetype)init
{
if (self = [super init]) {
_queue = dispatch_queue_create("com.apple.security.ckks.logging", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
_secondsBetweenUploads = SECONDS_BETWEEN_UPLOADS;
NSDictionary* systemDefaultValues = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle bundleWithPath:@"/System/Library/Frameworks/Security.framework"] pathForResource:@"CKKSLogging" ofType:@"plist"]];
_splunkTopicName = systemDefaultValues[@"splunk_topic"];
_splunkUploadURL = [NSURL URLWithString:systemDefaultValues[@"splunk_uploadURL"]];
_splunkBagURL = [NSURL URLWithString:systemDefaultValues[@"splunk_bagURL"]];
_allowInsecureSplunkCert = [[systemDefaultValues valueForKey:@"splunk_allowInsecureCertificate"] boolValue];
NSString* splunkEndpoint = systemDefaultValues[@"splunk_endpointDomain"];
NSUserDefaults* defaults = [[NSUserDefaults alloc] initWithSuiteName:SecCKKSUserDefaultsSuite];
NSString* userDefaultsSplunkTopic = [defaults stringForKey:@"splunk_topic"];
if (userDefaultsSplunkTopic) {
_splunkTopicName = userDefaultsSplunkTopic;
}
NSURL* userDefaultsSplunkUploadURL = [NSURL URLWithString:[defaults stringForKey:@"splunk_uploadURL"]];
if (userDefaultsSplunkUploadURL) {
_splunkUploadURL = userDefaultsSplunkUploadURL;
}
NSURL* userDefaultsSplunkBagURL = [NSURL URLWithString:[defaults stringForKey:@"splunk_bagURL"]];
if (userDefaultsSplunkUploadURL) {
_splunkBagURL = userDefaultsSplunkBagURL;
}
BOOL userDefaultsAllowInsecureSplunkCert = [defaults boolForKey:@"splunk_allowInsecureCertificate"];
_allowInsecureSplunkCert |= userDefaultsAllowInsecureSplunkCert;
NSString* userDefaultsSplunkEndpoint = [defaults stringForKey:@"splunk_endpointDomain"];
if (userDefaultsSplunkEndpoint) {
splunkEndpoint = userDefaultsSplunkEndpoint;
}
#if CKKS_SPLUNK_DEV
_ignoreServersMessagesTellingUsToGoAway = YES;
if (!_splunkUploadURL && splunkEndpoint) {
NSString* urlString = [NSString stringWithFormat:@"https://%@/report/2/ _splunkUploadURL = [NSURL URLWithString:urlString];
}
#else
(void)splunkEndpoint;
#endif
}
return self;
}
- (void)setLastSuccessfulClassASyncDate:(NSDate*)lastSuccessfulClassASyncDate
{
dispatch_sync(_queue, ^{
[[CKKSLoggerSQLiteStore sharedStore] setDateProperty:lastSuccessfulClassASyncDate forKey:CKKSLoggerLastClassASync];
});
}
- (NSDate*)lastSuccessfulClassASyncDate
{
__block NSDate* result = nil;
dispatch_sync(_queue, ^{
result = [self _onQueueLastSuccessfulClassASyncDate];
});
return result;
}
- (NSDate*)_onQueueLastSuccessfulClassASyncDate
{
dispatch_assert_queue(_queue);
return [[CKKSLoggerSQLiteStore sharedStore] datePropertyForKey:CKKSLoggerLastClassASync] ?: [NSDate distantPast];
}
- (void)setLastSuccessfulClassCSyncDate:(NSDate*)lastSuccessfulClassCSyncDate
{
dispatch_sync(_queue, ^{
[[CKKSLoggerSQLiteStore sharedStore] setDateProperty:lastSuccessfulClassCSyncDate forKey:CKKSLoggerLastClassCSync];
});
}
- (NSDate*)lastSuccessfulClassCSyncDate
{
__block NSDate* result = nil;
dispatch_sync(_queue, ^{
result = [self _onQueueLastSuccessfulClassCSyncDate];
});
return result;
}
- (NSDate*)_onQueueLastSuccessfulClassCSyncDate
{
dispatch_assert_queue(_queue);
return [[CKKSLoggerSQLiteStore sharedStore] datePropertyForKey:CKKSLoggerLastClassCSync] ?: [NSDate distantPast];
}
- (void)logSuccessForEventNamed:(NSString*)eventName
{
[self logEventNamed:eventName value:nil isSuccess:YES];
}
- (void)logFailureForEventNamed:(NSString*)eventName withAttributes:(NSDictionary*)attributes
{
[self logEventNamed:eventName value:attributes isSuccess:NO];
}
- (void)logEventNamed:(NSString*)eventName value:(NSDictionary*)valueDict isSuccess:(BOOL)isSuccess
{
__weak __typeof(self) weakSelf = self;
dispatch_async(_queue, ^{
__strong __typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
if (strongSelf->_disableLogging || [strongSelf->_blacklistedEvents containsObject:eventName]) {
return;
}
CKKSLoggerSQLiteStore* store = [CKKSLoggerSQLiteStore sharedStore];
if (isSuccess) {
[store incrementSuccessCountForEventType:eventName];
}
else {
[store incrementFailureCountForEventType:eventName];
NSMutableDictionary* eventDict = valueDict.mutableCopy;
eventDict[CKKSLoggerSplunkTopic] = strongSelf->_splunkTopicName;
eventDict[CKKSLoggerSplunkEventType] = eventName;
eventDict[CKKSLoggerSplunkEventTime] = @([[NSDate date] timeIntervalSince1970] * 1000);
eventDict[CKKSLoggerMetricsBase] = strongSelf->_metricsBase ?: [NSDictionary dictionary];
for (NSString* blacklistedField in strongSelf->_blacklistedFields) {
[eventDict removeObjectForKey:blacklistedField];
}
[store addFailureRecord:eventDict];
}
NSDate* uploadDate = store.uploadDate;
NSDate* nowDate = [NSDate date];
if (uploadDate) {
if ([nowDate compare:uploadDate] == NSOrderedDescending) {
[self _onQueueUploadDataWithError:nil];
}
}
else {
store.uploadDate = [nowDate dateByAddingTimeInterval:strongSelf->_secondsBetweenUploads];
}
});
}
// 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.
// TODO redo this, probably to return a dictionary.
- (NSURL*)splunkUploadURL
{
dispatch_assert_queue(_queue);
if (_splunkUploadURL) {
return _splunkUploadURL;
}
__weak __typeof(self) weakSelf = self;
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
__block NSError* error = nil;
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession* storeBagSession = [NSURLSession sessionWithConfiguration:defaultConfiguration
delegate:self
delegateQueue:nil];
NSURL* requestEndpoint = _splunkBagURL;
__block NSURL* result = nil;
NSURLSessionDataTask* storeBagTask = [storeBagSession dataTaskWithURL:requestEndpoint completionHandler:^(NSData * _Nullable data,
NSURLResponse * _Nullable __unused response,
NSError * _Nullable responseError) {
__strong __typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
if (data && !responseError) {
NSData *responseData = data; // shut up compiler
NSDictionary* responseDict = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
if([responseDict isKindOfClass:NSDictionary.class] && !error) {
if (!self->_ignoreServersMessagesTellingUsToGoAway) {
strongSelf->_disableLogging = [[responseDict valueForKey:@"disabled"] boolValue];
if (strongSelf->_disableLogging || [[responseDict valueForKey:@"sendDisabled"] boolValue]) {
// then don't upload anything right now
secerror("not returning a splunk URL because uploads are disabled");
dispatch_semaphore_signal(sem);
return;
}
NSUInteger millisecondsBetweenUploads = [[responseDict valueForKey:@"postFrequency"] unsignedIntegerValue] / 1000;
if (millisecondsBetweenUploads > 0) {
strongSelf->_secondsBetweenUploads = millisecondsBetweenUploads;
}
strongSelf->_blacklistedEvents = responseDict[@"blacklistedEvents"];
strongSelf->_blacklistedFields = responseDict[@"blacklistedFields"];
}
strongSelf->_metricsBase = responseDict[@"metricsBase"];
NSString* metricsEndpoint = responseDict[@"metricsUrl"];
if([metricsEndpoint isKindOfClass:NSString.class]) {
/* Lives our URL */
NSString* endpoint = [metricsEndpoint stringByAppendingFormat:@"/2/ secnotice("ckks", "got metrics endpoint: NSURL* endpointURL = [NSURL URLWithString:endpoint];
if([endpointURL.scheme isEqualToString:@"https"]) {
result = endpointURL;
}
}
}
}
else {
error = responseError;
}
if(error) {
secnotice("ckks", "Unable to fetch splunk endpoint at URL: }
else if(!result) {
secnotice("ckks", "Malformed iTunes config payload!");
}
dispatch_semaphore_signal(sem);
}];
[storeBagTask resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
return result;
}
- (BOOL)forceUploadWithError:(NSError**)error
{
__block BOOL result = NO;
dispatch_sync(_queue, ^{
result = [self _onQueueUploadDataWithError:error];
});
return result;
}
- (BOOL)_onQueueUploadDataWithError:(NSError**)error
{
dispatch_assert_queue(_queue);
NSData* json = [self _onQueueGetLoggingJSONWithError:error];
if (json && [self _onQueuePostJSON:json error:error]) {
secinfo("ckks", "uploading sync health data:
CKKSLoggerSQLiteStore* store = [CKKSLoggerSQLiteStore sharedStore];
[store clearAllData];
store.uploadDate = [NSDate dateWithTimeIntervalSinceNow:_secondsBetweenUploads];
return YES;
}
else {
return NO;
}
}
- (BOOL)_onQueuePostJSON:(NSData*)json error:(NSError**)error
{
dispatch_assert_queue(_queue);
/*
* Create the NSURLSession
* We use the ephemeral session config because we don't need cookies or cache
*/
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession* postSession = [NSURLSession sessionWithConfiguration:defaultConfiguration
delegate:self
delegateQueue:nil];
/*
* Create the request
*/
NSURL* postEndpoint = self.splunkUploadURL;
if (!postEndpoint) {
secerror("failed to get a splunk upload endpoint - not uploading");
return NO;
}
NSMutableURLRequest* postRequest = [[NSMutableURLRequest alloc] init];
postRequest.URL = postEndpoint;
postRequest.HTTPMethod = @"POST";
postRequest.HTTPBody = json;
/*
* Create the upload task.
*/
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
__block BOOL uploadSuccess = NO;
NSURLSessionDataTask* uploadTask = [postSession dataTaskWithRequest:postRequest
completionHandler:^(NSData * _Nullable __unused data, NSURLResponse * _Nullable response, NSError * _Nullable requestError) {
if(requestError) {
secerror("Error in uploading the events to splunk: }
else if (![response isKindOfClass:NSHTTPURLResponse.class]){
Class class = response.class;
secerror("Received the wrong kind of response: }
else {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
if(httpResponse.statusCode >= 200 && httpResponse.statusCode < 300) {
/* Success */
uploadSuccess = YES;
secnotice("ckks", "Splunk upload success");
}
else {
secnotice("ckks", "Splunk upload unexpected status to URL: }
}
dispatch_semaphore_signal(sem);
}];
secnotice("ckks", "Splunk upload start");
[uploadTask resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
return uploadSuccess;
}
#define SECOND_PER_DAY (60 * 60 * 24)
- (NSInteger)fuzzyDaysSinceDate:(NSDate*)date
{
NSTimeInterval timeIntervalSinceDate = [[NSDate date] timeIntervalSinceDate:date];
if (timeIntervalSinceDate < SECOND_PER_DAY) {
return 0;
}
else if (timeIntervalSinceDate < (SECOND_PER_DAY * 7)) {
return 1;
}
else if (timeIntervalSinceDate < (SECOND_PER_DAY * 30)) {
return 7;
}
else if (timeIntervalSinceDate < (SECOND_PER_DAY * 365)) {
return 30;
}
else {
return 365;
}
}
- (NSData*)getLoggingJSONWithError:(NSError**)error
{
__block NSData* json = nil;
dispatch_sync(_queue, ^{
json = [self _onQueueGetLoggingJSONWithError:error];
});
return json;
}
- (NSData*)_onQueueGetLoggingJSONWithError:(NSError**)error
{
dispatch_assert_queue(_queue);
CKKSLoggerSQLiteStore* store = [CKKSLoggerSQLiteStore sharedStore];
NSArray* failureRecords = [store failureRecords];
NSDictionary* successCounts = [store summaryCounts];
NSInteger totalSuccessCount = 0;
NSInteger totalFailureCount = 0;
for (NSDictionary* perEventTypeSuccessCounts in successCounts.objectEnumerator) {
totalSuccessCount += [perEventTypeSuccessCounts[CKKSLoggerColumnSuccessCount] integerValue];
totalFailureCount += [perEventTypeSuccessCounts[CKKSLoggerColumnFailureCount] integerValue];
}
NSDate* now = [NSDate date];
NSMutableDictionary* healthSummaryEvent = [[NSMutableDictionary alloc] init];
healthSummaryEvent[CKKSLoggerSplunkTopic] = _splunkTopicName ?: [NSNull null];
healthSummaryEvent[CKKSLoggerSplunkEventTime] = @([now timeIntervalSince1970] * 1000);
healthSummaryEvent[CKKSLoggerSplunkEventType] = @"manifestHealthSummary";
healthSummaryEvent[CKKSLoggerColumnSuccessCount] = @(totalSuccessCount);
healthSummaryEvent[CKKSLoggerColumnFailureCount] = @(totalFailureCount);
healthSummaryEvent[CKKSLoggerMetricsBase] = _metricsBase ?: [NSDictionary dictionary];
for (NSString* viewName in [CKKSViewManager viewList]) {
CKKSKeychainView* view = [CKKSViewManager findOrCreateView:viewName];
[healthSummaryEvent setValue:@([self fuzzyDaysSinceDate:[self _onQueueLastSuccessfulClassASyncDate]]) forKey:[NSString stringWithFormat:@" [healthSummaryEvent setValue:@([self fuzzyDaysSinceDate:[self _onQueueLastSuccessfulClassCSyncDate]]) forKey:[NSString stringWithFormat:@" }
NSMutableArray* splunkRecords = failureRecords.mutableCopy;
[splunkRecords addObject:healthSummaryEvent];
NSDictionary* jsonDict = @{CKKSLoggerSplunkPostTime : @([now timeIntervalSince1970] * 1000), @"events" : splunkRecords};
return [NSJSONSerialization dataWithJSONObject:jsonDict options:NSJSONWritingPrettyPrinted error:error];
}
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler {
assert(completionHandler);
(void)session;
secnotice("ckks", "Splunk upload challenge");
NSURLCredential *cred = nil;
SecTrustResultType result = kSecTrustResultInvalid;
if ([challenge previousFailureCount] > 0) {
// Previous failures occurred, bail
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
} else if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
/*
* Evaluate trust for the certificate
*/
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
OSStatus status = SecTrustEvaluate(serverTrust, &result);
if (status == errSecSuccess && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)) {
/*
* All is well, accept the credentials
*/
cred = [NSURLCredential credentialForTrust:serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
} else if (_allowInsecureSplunkCert) {
secnotice("ckks", "Force Accepting Splunk Credential");
cred = [NSURLCredential credentialForTrust:serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
} else {
/*
* An error occurred in evaluating trust, bail
*/
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
} else {
/*
* Just perform the default handling
*/
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
}
- (BOOL)ignoreServerDisablingMessages
{
return _ignoreServersMessagesTellingUsToGoAway;
}
- (void)setIgnoreServerDisablingMessages:(BOOL)ignoreServer
{
_ignoreServersMessagesTellingUsToGoAway = ignoreServer ? YES : NO;
}
@end
@implementation CKKSLoggerSQLiteStore
+ (instancetype)sharedStore
{
static CKKSLoggerSQLiteStore* store = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
store = [[self alloc] initWithPath:CKKSLoggingTablePath() schema:CKKSLoggingTableSchema];
[store open];
});
return store;
}
- (void)dealloc
{
[self close];
}
- (NSInteger)successCountForEventType:(NSString*)eventType
{
return [[[[self select:@[CKKSLoggerColumnSuccessCount] from:CKKSLoggerTableSuccessCount where:@"event_type = ?" bindings:@[eventType]] firstObject] valueForKey:CKKSLoggerColumnSuccessCount] integerValue];
}
- (void)incrementSuccessCountForEventType:(NSString*)eventType
{
@try {
NSInteger successCount = [self successCountForEventType:eventType];
NSInteger failureCount = [self failureCountForEventType:eventType];
[self insertOrReplaceInto:CKKSLoggerTableSuccessCount values:@{CKKSLoggerColumnEventType : eventType, CKKSLoggerColumnSuccessCount : @(successCount + 1), CKKSLoggerColumnFailureCount : @(failureCount)}];
} @catch (id ue) {
secerror("incrementSuccessCountForEventType exception: }
}
- (NSInteger)failureCountForEventType:(NSString*)eventType
{
return [[[[self select:@[CKKSLoggerColumnFailureCount] from:CKKSLoggerTableSuccessCount where:@"event_type = ?" bindings:@[eventType]] firstObject] valueForKey:CKKSLoggerColumnFailureCount] integerValue];
}
- (void)incrementFailureCountForEventType:(NSString*)eventType
{
@try {
NSInteger successCount = [self successCountForEventType:eventType];
NSInteger failureCount = [self failureCountForEventType:eventType];
[self insertOrReplaceInto:CKKSLoggerTableSuccessCount values:@{CKKSLoggerColumnEventType : eventType, CKKSLoggerColumnSuccessCount : @(successCount), CKKSLoggerColumnFailureCount : @(failureCount + 1)}];
} @catch (id ue) {
secerror("incrementFailureCountForEventType exception: }
}
- (NSDictionary*)summaryCounts
{
NSMutableDictionary* successCountsDict = [NSMutableDictionary dictionary];
NSArray* rows = [self selectAllFrom:CKKSLoggerTableSuccessCount where:nil bindings:nil];
for (NSDictionary* rowDict in rows) {
successCountsDict[rowDict[CKKSLoggerColumnEventType]] = @{CKKSLoggerColumnSuccessCount : rowDict[CKKSLoggerColumnSuccessCount], CKKSLoggerColumnFailureCount : rowDict[CKKSLoggerColumnFailureCount]};
}
return successCountsDict;
}
- (NSArray*)failureRecords
{
NSArray* recordBlobs = [self select:@[CKKSLoggerColumnData] from:CKKSLoggerTableFailures];
NSMutableArray* failureRecords = [[NSMutableArray alloc] init];
for (NSDictionary* row in recordBlobs) {
NSDictionary* deserializedRecord = [NSPropertyListSerialization propertyListWithData:row[CKKSLoggerColumnData] options:0 format:nil error:nil];
[failureRecords addObject:deserializedRecord];
}
return failureRecords;
}
- (void)addFailureRecord:(NSDictionary*)valueDict
{
@try {
NSError* error = nil;
NSData* serializedRecord = [NSPropertyListSerialization dataWithPropertyList:valueDict format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error];
if(!error && serializedRecord) {
[self insertOrReplaceInto:CKKSLoggerTableFailures values:@{CKKSLoggerColumnData : serializedRecord}];
}
if(error && !serializedRecord) {
secerror("Couldn't serialize failure record: }
} @catch (id ue) {
secerror("addFailureRecord exception: }
}
- (NSDate*)uploadDate
{
return [self datePropertyForKey:CKKSLoggerUploadDate];
}
- (void)setUploadDate:(NSDate*)uploadDate
{
[self setDateProperty:uploadDate forKey:CKKSLoggerUploadDate];
}
- (void)clearAllData
{
[self deleteFrom:CKKSLoggerTableSuccessCount where:@"event_type like ?" bindings:@[@" [self deleteFrom:CKKSLoggerTableFailures where:@"id >= 0" bindings:nil];
}
@end
#endif // OCTAGON