]> git.saurik.com Git - apple/security.git/blob - Analytics/SFAnalyticsSQLiteStore.m
Security-59754.80.3.tar.gz
[apple/security.git] / Analytics / SFAnalyticsSQLiteStore.m
1 /*
2 * Copyright (c) 2017 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #if __OBJC2__
25
26 #import "SFAnalyticsSQLiteStore.h"
27 #import "NSDate+SFAnalytics.h"
28 #import "Analytics/SFAnalyticsDefines.h"
29 #import "utilities/debugging.h"
30
31 NSString* const SFAnalyticsColumnEventType = @"event_type";
32 NSString* const SFAnalyticsColumnDate = @"timestamp";
33 NSString* const SFAnalyticsColumnData = @"data";
34 NSString* const SFAnalyticsUploadDate = @"upload_date";
35
36 @implementation SFAnalyticsSQLiteStore
37
38 + (instancetype)storeWithPath:(NSString*)path schema:(NSString*)schema
39 {
40 if (![path length]) {
41 seccritical("Cannot init db with empty path");
42 return nil;
43 }
44 if (![schema length]) {
45 seccritical("Cannot init db without schema");
46 return nil;
47 }
48
49 SFAnalyticsSQLiteStore* store = nil;
50 @synchronized([SFAnalyticsSQLiteStore class]) {
51 static NSMutableDictionary* loggingStores = nil;
52 static dispatch_once_t onceToken;
53 dispatch_once(&onceToken, ^{
54 loggingStores = [[NSMutableDictionary alloc] init];
55 });
56
57 NSString* standardizedPath = path.stringByStandardizingPath;
58 store = loggingStores[standardizedPath];
59 if (!store) {
60 store = [[self alloc] initWithPath:standardizedPath schema:schema];
61 loggingStores[standardizedPath] = store;
62 }
63 NSError* error = nil;
64 if (![store openWithError:&error] && !(error && error.code == SQLITE_AUTH)) {
65 secerror("SFAnalytics: could not open db at init, will try again later. Error: %@", error);
66 }
67 }
68
69 return store;
70 }
71
72 - (void)dealloc
73 {
74 [self close];
75 }
76
77 - (BOOL)tryToOpenDatabase
78 {
79 if (!self.isOpen) {
80 NSError* error = nil;
81 if (![self openWithError:&error]) {
82 return NO;
83 }
84 secnotice("SFAnalytics", "successfully opened analytics db");
85 }
86 return YES;
87 }
88
89 - (NSInteger)successCountForEventType:(NSString*)eventType
90 {
91 if (![self tryToOpenDatabase]) {
92 return 0;
93 }
94 return [[[[self select:@[SFAnalyticsColumnSuccessCount] from:SFAnalyticsTableSuccessCount where:@"event_type = ?" bindings:@[eventType]] firstObject] valueForKey:SFAnalyticsColumnSuccessCount] integerValue];
95 }
96
97 - (void)incrementSuccessCountForEventType:(NSString*)eventType
98 {
99 if (![self tryToOpenDatabase]) {
100 return;
101 }
102 NSInteger successCount = [self successCountForEventType:eventType];
103 NSInteger hardFailureCount = [self hardFailureCountForEventType:eventType];
104 NSInteger softFailureCount = [self softFailureCountForEventType:eventType];
105 [self insertOrReplaceInto:SFAnalyticsTableSuccessCount values:@{SFAnalyticsColumnEventType : eventType, SFAnalyticsColumnSuccessCount : @(successCount + 1), SFAnalyticsColumnHardFailureCount : @(hardFailureCount), SFAnalyticsColumnSoftFailureCount : @(softFailureCount)}];
106 }
107
108 - (NSInteger)hardFailureCountForEventType:(NSString*)eventType
109 {
110 if (![self tryToOpenDatabase]) {
111 return 0;
112 }
113 return [[[[self select:@[SFAnalyticsColumnHardFailureCount] from:SFAnalyticsTableSuccessCount where:@"event_type = ?" bindings:@[eventType]] firstObject] valueForKey:SFAnalyticsColumnHardFailureCount] integerValue];
114 }
115
116 - (NSInteger)softFailureCountForEventType:(NSString*)eventType
117 {
118 if (![self tryToOpenDatabase]) {
119 return 0;
120 }
121 return [[[[self select:@[SFAnalyticsColumnSoftFailureCount] from:SFAnalyticsTableSuccessCount where:@"event_type = ?" bindings:@[eventType]] firstObject] valueForKey:SFAnalyticsColumnSoftFailureCount] integerValue];
122 }
123
124 - (void)incrementHardFailureCountForEventType:(NSString*)eventType
125 {
126 if (![self tryToOpenDatabase]) {
127 return;
128 }
129 NSInteger successCount = [self successCountForEventType:eventType];
130 NSInteger hardFailureCount = [self hardFailureCountForEventType:eventType];
131 NSInteger softFailureCount = [self softFailureCountForEventType:eventType];
132 [self insertOrReplaceInto:SFAnalyticsTableSuccessCount values:@{SFAnalyticsColumnEventType : eventType, SFAnalyticsColumnSuccessCount : @(successCount), SFAnalyticsColumnHardFailureCount : @(hardFailureCount + 1), SFAnalyticsColumnSoftFailureCount : @(softFailureCount)}];
133 }
134
135 - (void)incrementSoftFailureCountForEventType:(NSString*)eventType
136 {
137 if (![self tryToOpenDatabase]) {
138 return;
139 }
140 NSInteger successCount = [self successCountForEventType:eventType];
141 NSInteger hardFailureCount = [self hardFailureCountForEventType:eventType];
142 NSInteger softFailureCount = [self softFailureCountForEventType:eventType];
143 [self insertOrReplaceInto:SFAnalyticsTableSuccessCount values:@{SFAnalyticsColumnEventType : eventType, SFAnalyticsColumnSuccessCount : @(successCount), SFAnalyticsColumnHardFailureCount : @(hardFailureCount), SFAnalyticsColumnSoftFailureCount : @(softFailureCount + 1)}];
144 }
145
146 - (NSDictionary*)summaryCounts
147 {
148 if (![self tryToOpenDatabase]) {
149 return [NSDictionary new];
150 }
151 NSMutableDictionary* successCountsDict = [NSMutableDictionary dictionary];
152 NSArray* rows = [self selectAllFrom:SFAnalyticsTableSuccessCount where:nil bindings:nil];
153 for (NSDictionary* rowDict in rows) {
154 NSString* eventName = rowDict[SFAnalyticsColumnEventType];
155 if (!eventName) {
156 secinfo("SFAnalytics", "ignoring entry in success counts table without an event name");
157 continue;
158 }
159
160 successCountsDict[eventName] = @{SFAnalyticsTableSuccessCount : rowDict[SFAnalyticsColumnSuccessCount], SFAnalyticsColumnHardFailureCount : rowDict[SFAnalyticsColumnHardFailureCount], SFAnalyticsColumnSoftFailureCount : rowDict[SFAnalyticsColumnSoftFailureCount]};
161 }
162
163 return successCountsDict;
164 }
165
166 - (NSArray*)deserializedRecords:(NSArray*)recordBlobs
167 {
168 if (![self tryToOpenDatabase]) {
169 return [NSArray new];
170 }
171 NSMutableArray* records = [NSMutableArray new];
172 for (NSDictionary* row in recordBlobs) {
173 NSMutableDictionary* deserializedRecord = [NSPropertyListSerialization propertyListWithData:row[SFAnalyticsColumnData] options:NSPropertyListMutableContainers format:nil error:nil];
174 [records addObject:deserializedRecord];
175 }
176 return records;
177 }
178
179 - (NSArray*)hardFailures
180 {
181 if (![self tryToOpenDatabase]) {
182 return [NSArray new];
183 }
184 return [self deserializedRecords:[self select:@[SFAnalyticsColumnData] from:SFAnalyticsTableHardFailures]];
185 }
186
187 - (NSArray*)softFailures
188 {
189 if (![self tryToOpenDatabase]) {
190 return [NSArray new];
191 }
192 return [self deserializedRecords:[self select:@[SFAnalyticsColumnData] from:SFAnalyticsTableSoftFailures]];
193 }
194
195 - (NSArray*)allEvents
196 {
197 if (![self tryToOpenDatabase]) {
198 return [NSArray new];
199 }
200
201 [self begin];
202
203 NSMutableArray<NSDictionary *> *all = [NSMutableArray new];
204
205 NSArray<NSDictionary *> *hard = [self select:@[SFAnalyticsColumnDate, SFAnalyticsColumnData] from:SFAnalyticsTableHardFailures];
206 [all addObjectsFromArray:hard];
207 hard = nil;
208
209 NSArray<NSDictionary *> *soft = [self select:@[SFAnalyticsColumnDate, SFAnalyticsColumnData] from:SFAnalyticsTableSoftFailures];
210 [all addObjectsFromArray:soft];
211 soft = nil;
212
213 NSArray<NSDictionary *> *notes = [self select:@[SFAnalyticsColumnDate, SFAnalyticsColumnData] from:SFAnalyticsTableNotes];
214 [all addObjectsFromArray:notes];
215 notes = nil;
216
217 [self end];
218
219 [all sortUsingComparator:^NSComparisonResult(NSDictionary *_Nonnull obj1, NSDictionary *_Nonnull obj2) {
220 NSDate *date1 = obj1[SFAnalyticsColumnDate];
221 NSDate *date2 = obj2[SFAnalyticsColumnDate];
222 return [date1 compare:date2];
223 }];
224 return [self deserializedRecords:all];
225 }
226
227 - (NSArray*)samples
228 {
229 if (![self tryToOpenDatabase]) {
230 return [NSArray new];
231 }
232 return [self select:@[SFAnalyticsColumnSampleName, SFAnalyticsColumnSampleValue] from:SFAnalyticsTableSamples];
233 }
234
235 - (void)addEventDict:(NSDictionary*)eventDict toTable:(NSString*)table timestampBucket:(SFAnalyticsTimestampBucket)bucket
236 {
237 if (![self tryToOpenDatabase]) {
238 return;
239 }
240
241 NSTimeInterval timestamp = [[NSDate date] timeIntervalSince1970WithBucket:bucket];
242 NSError* error = nil;
243 NSData* serializedRecord = [NSPropertyListSerialization dataWithPropertyList:eventDict format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error];
244 if(!error && serializedRecord) {
245 [self insertOrReplaceInto:table values:@{SFAnalyticsColumnDate : @(timestamp), SFAnalyticsColumnData : serializedRecord}];
246 }
247 if(error && !serializedRecord) {
248 secerror("Couldn't serialize failure record: %@", error);
249 }
250 }
251
252 - (void)addEventDict:(NSDictionary*)eventDict toTable:(NSString*)table
253 {
254 [self addEventDict:eventDict toTable:table timestampBucket:SFAnalyticsTimestampBucketSecond];
255 }
256
257 - (void)addSample:(NSNumber*)value forName:(NSString*)name
258 {
259 if (![self tryToOpenDatabase]) {
260 return;
261 }
262 [self insertOrReplaceInto:SFAnalyticsTableSamples values:@{SFAnalyticsColumnDate : @([[NSDate date] timeIntervalSince1970]), SFAnalyticsColumnSampleName : name, SFAnalyticsColumnSampleValue : value}];
263 }
264
265 - (void)removeAllSamplesForName:(NSString*)name
266 {
267 if (![self tryToOpenDatabase]) {
268 return;
269 }
270 [self deleteFrom:SFAnalyticsTableSamples where:[NSString stringWithFormat:@"name == '%@'", name] bindings:nil];
271 }
272
273 - (NSDate*)uploadDate
274 {
275 if (![self tryToOpenDatabase]) {
276 return nil; // In other cases return default object but nil is better here to avoid entering the upload flow
277 }
278 return [self datePropertyForKey:SFAnalyticsUploadDate];
279 }
280
281 - (void)setUploadDate:(NSDate*)uploadDate
282 {
283 if (![self tryToOpenDatabase]) {
284 return;
285 }
286 [self setDateProperty:uploadDate forKey:SFAnalyticsUploadDate];
287 }
288
289 - (void)clearAllData
290 {
291 if (![self tryToOpenDatabase]) {
292 return;
293 }
294 [self deleteFrom:SFAnalyticsTableSuccessCount where:@"event_type like ?" bindings:@[@"%"]];
295 [self deleteFrom:SFAnalyticsTableHardFailures where:@"id >= 0" bindings:nil];
296 [self deleteFrom:SFAnalyticsTableSoftFailures where:@"id >= 0" bindings:nil];
297 [self deleteFrom:SFAnalyticsTableSamples where:@"id >= 0" bindings:nil];
298 }
299
300 @end
301
302 #endif // OBJC2