@logbrew/react-native 0.1.23 → 0.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -5
- package/android/CMakeLists.txt +7 -0
- package/android/build.gradle +12 -0
- package/android/src/main/cpp/android_diagnostics.cpp +363 -0
- package/android/src/main/java/co/logbrew/reactnative/AndroidDiagnosticsRuntime.java +214 -0
- package/android/src/main/java/co/logbrew/reactnative/AndroidNativeDiagnostics.java +363 -0
- package/android/src/main/java/co/logbrew/reactnative/AndroidNativeSignalStore.java +233 -0
- package/android/src/main/java/co/logbrew/reactnative/AndroidParentDirectorySync.java +12 -12
- package/android/src/main/java/co/logbrew/reactnative/EventRecordStore.java +15 -5
- package/android/src/main/java/co/logbrew/reactnative/FatalStoreModuleImpl.java +111 -156
- package/android/src/newarch/java/co/logbrew/reactnative/FatalStoreModule.java +15 -20
- package/android/src/oldarch/java/co/logbrew/reactnative/FatalStoreModule.java +15 -20
- package/android-native-diagnostics.d.ts +29 -0
- package/android-native-diagnostics.js +176 -0
- package/fatal-replay.cjs +35 -3
- package/global-errors.d.cts +3 -3
- package/global-errors.d.ts +3 -3
- package/global-errors.native.js +1 -12
- package/index.cjs +1 -1
- package/index.native.d.ts +4 -0
- package/index.native.js +21 -3
- package/ios/AppleDiagnostics/LBRNAppleNativeDiagnostics.swift +1 -1
- package/ios/GeneratedAppleDiagnostics/LogBrew/LogBrewLogger.swift +2 -10
- package/ios/GeneratedAppleDiagnostics/LogBrew/OperationTrace.swift +56 -0
- package/ios/GeneratedAppleDiagnostics/LogBrew/URLSessionTracer.swift +48 -56
- package/ios/GeneratedAppleDiagnostics/LogBrew/Validation.swift +6 -0
- package/ios/GeneratedAppleDiagnostics/LogBrewCrash/NativeCrashCorrelation.swift +0 -1
- package/ios/GeneratedAppleDiagnostics/SOURCE-MANIFEST.json +5 -4
- package/ios/LBRNFatalStoreModule.mm +15 -86
- package/package.json +17 -5
- package/persistent-delivery.native.js +4 -3
- package/src/NativeLogBrewFatalStore.ts +5 -4
- package/android/src/main/java/co/logbrew/reactnative/FatalRecordStore.java +0 -623
- package/ios/LBRNFatalRecordStore.h +0 -25
- package/ios/LBRNFatalRecordStore.m +0 -542
|
@@ -1,542 +0,0 @@
|
|
|
1
|
-
#import "LBRNFatalRecordStore.h"
|
|
2
|
-
|
|
3
|
-
#import <errno.h>
|
|
4
|
-
#import <fcntl.h>
|
|
5
|
-
#import <math.h>
|
|
6
|
-
#import <stdint.h>
|
|
7
|
-
#import <sys/stat.h>
|
|
8
|
-
#import <unistd.h>
|
|
9
|
-
|
|
10
|
-
NSString *const LBRNFatalRecordFileName = @"fatal-js-v1.record";
|
|
11
|
-
NSString *const LBRNFatalRecordTemporaryFileName = @"fatal-js-v1.tmp";
|
|
12
|
-
|
|
13
|
-
static const NSUInteger LBRNMaximumRecordBytes = 16 * 1024;
|
|
14
|
-
static const NSUInteger LBRNMaximumFrames = 24;
|
|
15
|
-
static const NSUInteger LBRNMaximumFilenameBytes = 512;
|
|
16
|
-
static const NSUInteger LBRNMaximumIdentifierBytes = 96;
|
|
17
|
-
static const int32_t LBRNMaximumCounter = INT32_MAX;
|
|
18
|
-
|
|
19
|
-
@interface LBRNFatalRecordStore ()
|
|
20
|
-
@property (nonatomic, readonly) NSURL *directoryURL;
|
|
21
|
-
@property (nonatomic, readonly, copy)
|
|
22
|
-
LBRNFatalDirectoryPreparation directoryPreparation;
|
|
23
|
-
@end
|
|
24
|
-
|
|
25
|
-
@implementation LBRNFatalRecordStore
|
|
26
|
-
|
|
27
|
-
- (instancetype)initWithDirectoryURL:(NSURL *)directoryURL
|
|
28
|
-
{
|
|
29
|
-
return [self initWithDirectoryURL:directoryURL
|
|
30
|
-
directoryPreparation:^BOOL(__unused NSURL *preparedURL) {
|
|
31
|
-
return NO;
|
|
32
|
-
}];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
- (instancetype)initWithDirectoryURL:(NSURL *)directoryURL
|
|
36
|
-
directoryPreparation:(LBRNFatalDirectoryPreparation)directoryPreparation
|
|
37
|
-
{
|
|
38
|
-
self = [super init];
|
|
39
|
-
if (self != nil) {
|
|
40
|
-
_directoryURL = directoryURL;
|
|
41
|
-
_directoryPreparation = [directoryPreparation copy];
|
|
42
|
-
}
|
|
43
|
-
return self;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
- (NSDictionary *)writeRecord:(NSDictionary *)record
|
|
47
|
-
{
|
|
48
|
-
@synchronized(self) {
|
|
49
|
-
NSDictionary *validated = [self validatedRecord:record requireZeroCounters:YES];
|
|
50
|
-
if (validated == nil) {
|
|
51
|
-
return @{ @"status" : @"invalid_record" };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
int directoryFD = [self openPrivateDirectory];
|
|
55
|
-
if (directoryFD < 0) {
|
|
56
|
-
return @{ @"status" : @"storage_error" };
|
|
57
|
-
}
|
|
58
|
-
@try {
|
|
59
|
-
BOOL directoryPrepared = NO;
|
|
60
|
-
@try {
|
|
61
|
-
directoryPrepared = self.directoryPreparation(self.directoryURL);
|
|
62
|
-
} @catch (__unused NSException *exception) {
|
|
63
|
-
directoryPrepared = NO;
|
|
64
|
-
}
|
|
65
|
-
if (!directoryPrepared || ![self directoryFDMatchesDirectoryURL:directoryFD]) {
|
|
66
|
-
return @{ @"status" : @"storage_error" };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
NSDictionary *existing = [self readRecordFromDirectory:directoryFD];
|
|
70
|
-
NSString *status = existing[@"status"];
|
|
71
|
-
if ([status isEqualToString:@"storage_error"]) {
|
|
72
|
-
return existing;
|
|
73
|
-
}
|
|
74
|
-
if ([status isEqualToString:@"pending"]) {
|
|
75
|
-
NSMutableDictionary *preserved = [existing[@"record"] mutableCopy];
|
|
76
|
-
int32_t dropped = [preserved[@"droppedRecords"] intValue];
|
|
77
|
-
if (dropped < LBRNMaximumCounter) {
|
|
78
|
-
dropped += 1;
|
|
79
|
-
}
|
|
80
|
-
preserved[@"droppedRecords"] = @(dropped);
|
|
81
|
-
if (![self atomicallyWriteRecord:preserved directoryFD:directoryFD]) {
|
|
82
|
-
return @{ @"status" : @"storage_error" };
|
|
83
|
-
}
|
|
84
|
-
return @{
|
|
85
|
-
@"status" : @"dropped_pending",
|
|
86
|
-
@"recordId" : preserved[@"id"],
|
|
87
|
-
@"droppedRecords" : @(dropped),
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
NSMutableDictionary *stored = [validated mutableCopy];
|
|
92
|
-
BOOL recoveredCorruption = [status isEqualToString:@"corrupt_discarded"];
|
|
93
|
-
if (recoveredCorruption) {
|
|
94
|
-
stored[@"corruptRecords"] = @1;
|
|
95
|
-
}
|
|
96
|
-
if (![self atomicallyWriteRecord:stored directoryFD:directoryFD]) {
|
|
97
|
-
return @{ @"status" : @"storage_error" };
|
|
98
|
-
}
|
|
99
|
-
return @{
|
|
100
|
-
@"status" : recoveredCorruption ? @"stored_after_corruption" : @"stored",
|
|
101
|
-
@"recordId" : stored[@"id"],
|
|
102
|
-
@"corruptRecords" : stored[@"corruptRecords"],
|
|
103
|
-
};
|
|
104
|
-
} @finally {
|
|
105
|
-
close(directoryFD);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
- (NSDictionary *)readRecord
|
|
111
|
-
{
|
|
112
|
-
@synchronized(self) {
|
|
113
|
-
int directoryFD = [self openPrivateDirectory];
|
|
114
|
-
if (directoryFD < 0) {
|
|
115
|
-
return @{ @"status" : @"storage_error" };
|
|
116
|
-
}
|
|
117
|
-
@try {
|
|
118
|
-
return [self readRecordFromDirectory:directoryFD];
|
|
119
|
-
} @finally {
|
|
120
|
-
close(directoryFD);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
- (NSDictionary *)acknowledgeRecordId:(NSString *)recordId
|
|
126
|
-
{
|
|
127
|
-
@synchronized(self) {
|
|
128
|
-
if (![self validIdentifier:recordId]) {
|
|
129
|
-
return @{ @"status" : @"id_mismatch" };
|
|
130
|
-
}
|
|
131
|
-
int directoryFD = [self openPrivateDirectory];
|
|
132
|
-
if (directoryFD < 0) {
|
|
133
|
-
return @{ @"status" : @"storage_error" };
|
|
134
|
-
}
|
|
135
|
-
@try {
|
|
136
|
-
NSDictionary *existing = [self readRecordFromDirectory:directoryFD];
|
|
137
|
-
if (![existing[@"status"] isEqualToString:@"pending"]) {
|
|
138
|
-
return existing;
|
|
139
|
-
}
|
|
140
|
-
NSDictionary *record = existing[@"record"];
|
|
141
|
-
if (![record[@"id"] isEqualToString:recordId]) {
|
|
142
|
-
return @{
|
|
143
|
-
@"status" : @"id_mismatch",
|
|
144
|
-
@"recordId" : record[@"id"],
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
if (unlinkat(directoryFD, LBRNFatalRecordFileName.UTF8String, 0) != 0
|
|
148
|
-
|| fsync(directoryFD) != 0) {
|
|
149
|
-
return @{ @"status" : @"storage_error" };
|
|
150
|
-
}
|
|
151
|
-
return @{
|
|
152
|
-
@"status" : @"acknowledged",
|
|
153
|
-
@"recordId" : recordId,
|
|
154
|
-
};
|
|
155
|
-
} @finally {
|
|
156
|
-
close(directoryFD);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
- (NSDictionary *)discardRecord
|
|
162
|
-
{
|
|
163
|
-
@synchronized(self) {
|
|
164
|
-
int directoryFD = [self openPrivateDirectory];
|
|
165
|
-
if (directoryFD < 0) {
|
|
166
|
-
return @{ @"status" : @"storage_error" };
|
|
167
|
-
}
|
|
168
|
-
@try {
|
|
169
|
-
NSDictionary *existing = [self readRecordFromDirectory:directoryFD];
|
|
170
|
-
if (![existing[@"status"] isEqualToString:@"pending"]) {
|
|
171
|
-
return existing;
|
|
172
|
-
}
|
|
173
|
-
NSDictionary *record = existing[@"record"];
|
|
174
|
-
if (unlinkat(directoryFD, LBRNFatalRecordFileName.UTF8String, 0) != 0
|
|
175
|
-
|| fsync(directoryFD) != 0) {
|
|
176
|
-
return @{ @"status" : @"storage_error" };
|
|
177
|
-
}
|
|
178
|
-
return @{
|
|
179
|
-
@"status" : @"discarded",
|
|
180
|
-
@"recordId" : record[@"id"],
|
|
181
|
-
};
|
|
182
|
-
} @finally {
|
|
183
|
-
close(directoryFD);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
- (int)openPrivateDirectory
|
|
189
|
-
{
|
|
190
|
-
if (!self.directoryURL.isFileURL) {
|
|
191
|
-
return -1;
|
|
192
|
-
}
|
|
193
|
-
const char *path = self.directoryURL.fileSystemRepresentation;
|
|
194
|
-
struct stat info;
|
|
195
|
-
if (lstat(path, &info) != 0) {
|
|
196
|
-
if (errno != ENOENT || mkdir(path, 0700) != 0) {
|
|
197
|
-
return -1;
|
|
198
|
-
}
|
|
199
|
-
} else if (!S_ISDIR(info.st_mode) || S_ISLNK(info.st_mode)) {
|
|
200
|
-
return -1;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
int directoryFD = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC | O_NOFOLLOW);
|
|
204
|
-
if (directoryFD < 0) {
|
|
205
|
-
return -1;
|
|
206
|
-
}
|
|
207
|
-
if (fstat(directoryFD, &info) != 0 || !S_ISDIR(info.st_mode)
|
|
208
|
-
|| fchmod(directoryFD, 0700) != 0) {
|
|
209
|
-
close(directoryFD);
|
|
210
|
-
return -1;
|
|
211
|
-
}
|
|
212
|
-
return directoryFD;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
- (BOOL)directoryFDMatchesDirectoryURL:(int)directoryFD
|
|
216
|
-
{
|
|
217
|
-
struct stat openedInfo;
|
|
218
|
-
struct stat pathInfo;
|
|
219
|
-
if (fstat(directoryFD, &openedInfo) != 0
|
|
220
|
-
|| lstat(self.directoryURL.fileSystemRepresentation, &pathInfo) != 0) {
|
|
221
|
-
return NO;
|
|
222
|
-
}
|
|
223
|
-
return S_ISDIR(openedInfo.st_mode) && S_ISDIR(pathInfo.st_mode)
|
|
224
|
-
&& !S_ISLNK(pathInfo.st_mode) && openedInfo.st_dev == pathInfo.st_dev
|
|
225
|
-
&& openedInfo.st_ino == pathInfo.st_ino;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
- (NSDictionary *)readRecordFromDirectory:(int)directoryFD
|
|
229
|
-
{
|
|
230
|
-
if (![self removeStaleTemporaryFile:directoryFD]) {
|
|
231
|
-
return @{ @"status" : @"storage_error" };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
struct stat pathInfo;
|
|
235
|
-
if (fstatat(
|
|
236
|
-
directoryFD,
|
|
237
|
-
LBRNFatalRecordFileName.UTF8String,
|
|
238
|
-
&pathInfo,
|
|
239
|
-
AT_SYMLINK_NOFOLLOW)
|
|
240
|
-
!= 0) {
|
|
241
|
-
return errno == ENOENT ? @{ @"status" : @"empty" }
|
|
242
|
-
: @{ @"status" : @"storage_error" };
|
|
243
|
-
}
|
|
244
|
-
if (!S_ISREG(pathInfo.st_mode) || S_ISLNK(pathInfo.st_mode)) {
|
|
245
|
-
return @{ @"status" : @"storage_error" };
|
|
246
|
-
}
|
|
247
|
-
if (pathInfo.st_size < 0 || (uint64_t)pathInfo.st_size > LBRNMaximumRecordBytes) {
|
|
248
|
-
return [self discardCorruptRecord:directoryFD];
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
int fileFD = openat(
|
|
252
|
-
directoryFD,
|
|
253
|
-
LBRNFatalRecordFileName.UTF8String,
|
|
254
|
-
O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
|
|
255
|
-
if (fileFD < 0) {
|
|
256
|
-
return @{ @"status" : @"storage_error" };
|
|
257
|
-
}
|
|
258
|
-
NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)pathInfo.st_size];
|
|
259
|
-
BOOL readSucceeded = YES;
|
|
260
|
-
NSUInteger offset = 0;
|
|
261
|
-
while (offset < data.length) {
|
|
262
|
-
ssize_t count =
|
|
263
|
-
read(fileFD, (uint8_t *)data.mutableBytes + offset, data.length - offset);
|
|
264
|
-
if (count <= 0) {
|
|
265
|
-
readSucceeded = NO;
|
|
266
|
-
break;
|
|
267
|
-
}
|
|
268
|
-
offset += (NSUInteger)count;
|
|
269
|
-
}
|
|
270
|
-
struct stat openedInfo;
|
|
271
|
-
if (fstat(fileFD, &openedInfo) != 0 || openedInfo.st_dev != pathInfo.st_dev
|
|
272
|
-
|| openedInfo.st_ino != pathInfo.st_ino || !S_ISREG(openedInfo.st_mode)) {
|
|
273
|
-
readSucceeded = NO;
|
|
274
|
-
}
|
|
275
|
-
close(fileFD);
|
|
276
|
-
if (!readSucceeded) {
|
|
277
|
-
return @{ @"status" : @"storage_error" };
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
NSError *error = nil;
|
|
281
|
-
id object = [NSPropertyListSerialization propertyListWithData:data
|
|
282
|
-
options:NSPropertyListImmutable
|
|
283
|
-
format:nil
|
|
284
|
-
error:&error];
|
|
285
|
-
NSDictionary *record =
|
|
286
|
-
[object isKindOfClass:[NSDictionary class]]
|
|
287
|
-
? [self validatedRecord:(NSDictionary *)object requireZeroCounters:NO]
|
|
288
|
-
: nil;
|
|
289
|
-
if (record == nil) {
|
|
290
|
-
return [self discardCorruptRecord:directoryFD];
|
|
291
|
-
}
|
|
292
|
-
return @{
|
|
293
|
-
@"status" : @"pending",
|
|
294
|
-
@"record" : record,
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
- (NSDictionary *)discardCorruptRecord:(int)directoryFD
|
|
299
|
-
{
|
|
300
|
-
if (unlinkat(directoryFD, LBRNFatalRecordFileName.UTF8String, 0) != 0
|
|
301
|
-
|| fsync(directoryFD) != 0) {
|
|
302
|
-
return @{ @"status" : @"storage_error" };
|
|
303
|
-
}
|
|
304
|
-
return @{
|
|
305
|
-
@"status" : @"corrupt_discarded",
|
|
306
|
-
@"corruptRecords" : @1,
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
- (BOOL)removeStaleTemporaryFile:(int)directoryFD
|
|
311
|
-
{
|
|
312
|
-
struct stat info;
|
|
313
|
-
if (fstatat(
|
|
314
|
-
directoryFD,
|
|
315
|
-
LBRNFatalRecordTemporaryFileName.UTF8String,
|
|
316
|
-
&info,
|
|
317
|
-
AT_SYMLINK_NOFOLLOW)
|
|
318
|
-
!= 0) {
|
|
319
|
-
return errno == ENOENT;
|
|
320
|
-
}
|
|
321
|
-
if (!S_ISREG(info.st_mode) || S_ISLNK(info.st_mode)) {
|
|
322
|
-
return NO;
|
|
323
|
-
}
|
|
324
|
-
return unlinkat(directoryFD, LBRNFatalRecordTemporaryFileName.UTF8String, 0) == 0;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
- (BOOL)atomicallyWriteRecord:(NSDictionary *)record directoryFD:(int)directoryFD
|
|
328
|
-
{
|
|
329
|
-
NSError *serializationError = nil;
|
|
330
|
-
NSData *data =
|
|
331
|
-
[NSPropertyListSerialization dataWithPropertyList:record
|
|
332
|
-
format:NSPropertyListBinaryFormat_v1_0
|
|
333
|
-
options:0
|
|
334
|
-
error:&serializationError];
|
|
335
|
-
if (data == nil || data.length == 0 || data.length > LBRNMaximumRecordBytes) {
|
|
336
|
-
return NO;
|
|
337
|
-
}
|
|
338
|
-
if (![self removeStaleTemporaryFile:directoryFD]) {
|
|
339
|
-
return NO;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
int fileFD = openat(
|
|
343
|
-
directoryFD,
|
|
344
|
-
LBRNFatalRecordTemporaryFileName.UTF8String,
|
|
345
|
-
O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW,
|
|
346
|
-
0600);
|
|
347
|
-
if (fileFD < 0) {
|
|
348
|
-
return NO;
|
|
349
|
-
}
|
|
350
|
-
BOOL succeeded = fchmod(fileFD, 0600) == 0;
|
|
351
|
-
NSUInteger offset = 0;
|
|
352
|
-
while (succeeded && offset < data.length) {
|
|
353
|
-
ssize_t count =
|
|
354
|
-
write(fileFD, (const uint8_t *)data.bytes + offset, data.length - offset);
|
|
355
|
-
if (count <= 0) {
|
|
356
|
-
succeeded = NO;
|
|
357
|
-
} else {
|
|
358
|
-
offset += (NSUInteger)count;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
if (succeeded) {
|
|
362
|
-
succeeded = fsync(fileFD) == 0;
|
|
363
|
-
}
|
|
364
|
-
if (close(fileFD) != 0) {
|
|
365
|
-
succeeded = NO;
|
|
366
|
-
}
|
|
367
|
-
if (succeeded) {
|
|
368
|
-
succeeded = renameat(
|
|
369
|
-
directoryFD,
|
|
370
|
-
LBRNFatalRecordTemporaryFileName.UTF8String,
|
|
371
|
-
directoryFD,
|
|
372
|
-
LBRNFatalRecordFileName.UTF8String)
|
|
373
|
-
== 0;
|
|
374
|
-
}
|
|
375
|
-
if (succeeded) {
|
|
376
|
-
succeeded = fsync(directoryFD) == 0;
|
|
377
|
-
}
|
|
378
|
-
if (!succeeded) {
|
|
379
|
-
unlinkat(directoryFD, LBRNFatalRecordTemporaryFileName.UTF8String, 0);
|
|
380
|
-
}
|
|
381
|
-
return succeeded;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
- (nullable NSDictionary *)validatedRecord:(NSDictionary *)record
|
|
385
|
-
requireZeroCounters:(BOOL)requireZeroCounters
|
|
386
|
-
{
|
|
387
|
-
NSSet *expectedKeys = [NSSet setWithArray:@[
|
|
388
|
-
@"schemaVersion",
|
|
389
|
-
@"id",
|
|
390
|
-
@"timestamp",
|
|
391
|
-
@"errorName",
|
|
392
|
-
@"stackFrames",
|
|
393
|
-
@"droppedRecords",
|
|
394
|
-
@"corruptRecords",
|
|
395
|
-
]];
|
|
396
|
-
if (![expectedKeys isEqualToSet:[NSSet setWithArray:record.allKeys]]
|
|
397
|
-
|| ![record[@"schemaVersion"] isEqual:@1]
|
|
398
|
-
|| ![self validIdentifier:record[@"id"]]
|
|
399
|
-
|| ![self validTimestamp:record[@"timestamp"]]
|
|
400
|
-
|| ![self validErrorName:record[@"errorName"]]
|
|
401
|
-
|| ![record[@"stackFrames"] isKindOfClass:[NSArray class]]) {
|
|
402
|
-
return nil;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
NSArray *frames = record[@"stackFrames"];
|
|
406
|
-
if (frames.count > LBRNMaximumFrames) {
|
|
407
|
-
return nil;
|
|
408
|
-
}
|
|
409
|
-
NSMutableArray *validatedFrames = [NSMutableArray arrayWithCapacity:frames.count];
|
|
410
|
-
for (id candidate in frames) {
|
|
411
|
-
if (![candidate isKindOfClass:[NSDictionary class]]) {
|
|
412
|
-
return nil;
|
|
413
|
-
}
|
|
414
|
-
NSDictionary *frame = candidate;
|
|
415
|
-
NSSet *frameKeys =
|
|
416
|
-
[NSSet setWithArray:@[ @"filename", @"line", @"column" ]];
|
|
417
|
-
if (![frameKeys isEqualToSet:[NSSet setWithArray:frame.allKeys]]
|
|
418
|
-
|| ![self validFilename:frame[@"filename"]]
|
|
419
|
-
|| ![self validPositiveInteger:frame[@"line"]]
|
|
420
|
-
|| ![self validPositiveInteger:frame[@"column"]]) {
|
|
421
|
-
return nil;
|
|
422
|
-
}
|
|
423
|
-
[validatedFrames addObject:@{
|
|
424
|
-
@"filename" : frame[@"filename"],
|
|
425
|
-
@"line" : frame[@"line"],
|
|
426
|
-
@"column" : frame[@"column"],
|
|
427
|
-
}];
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
NSNumber *dropped = record[@"droppedRecords"];
|
|
431
|
-
NSNumber *corrupt = record[@"corruptRecords"];
|
|
432
|
-
if (![self validCounter:dropped] || ![self validCounter:corrupt]
|
|
433
|
-
|| (requireZeroCounters && (dropped.intValue != 0 || corrupt.intValue != 0))) {
|
|
434
|
-
return nil;
|
|
435
|
-
}
|
|
436
|
-
return @{
|
|
437
|
-
@"schemaVersion" : @1,
|
|
438
|
-
@"id" : record[@"id"],
|
|
439
|
-
@"timestamp" : record[@"timestamp"],
|
|
440
|
-
@"errorName" : record[@"errorName"],
|
|
441
|
-
@"stackFrames" : validatedFrames,
|
|
442
|
-
@"droppedRecords" : @(dropped.intValue),
|
|
443
|
-
@"corruptRecords" : @(corrupt.intValue),
|
|
444
|
-
};
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
- (BOOL)validIdentifier:(id)value
|
|
448
|
-
{
|
|
449
|
-
if (![value isKindOfClass:[NSString class]]
|
|
450
|
-
|| [(NSString *)value lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > LBRNMaximumIdentifierBytes) {
|
|
451
|
-
return NO;
|
|
452
|
-
}
|
|
453
|
-
NSRegularExpression *pattern =
|
|
454
|
-
[NSRegularExpression regularExpressionWithPattern:@"^evt_rn_fatal_[a-z0-9]+(?:_[a-z0-9]+)*$"
|
|
455
|
-
options:0
|
|
456
|
-
error:nil];
|
|
457
|
-
NSString *identifier = value;
|
|
458
|
-
return [pattern firstMatchInString:identifier
|
|
459
|
-
options:0
|
|
460
|
-
range:NSMakeRange(0, identifier.length)]
|
|
461
|
-
!= nil;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
- (BOOL)validTimestamp:(id)value
|
|
465
|
-
{
|
|
466
|
-
if (![value isKindOfClass:[NSString class]]) {
|
|
467
|
-
return NO;
|
|
468
|
-
}
|
|
469
|
-
NSString *timestamp = value;
|
|
470
|
-
if (timestamp.length < 20 || timestamp.length > 35) {
|
|
471
|
-
return NO;
|
|
472
|
-
}
|
|
473
|
-
NSRegularExpression *pattern =
|
|
474
|
-
[NSRegularExpression regularExpressionWithPattern:
|
|
475
|
-
@"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{1,9})?Z$"
|
|
476
|
-
options:0
|
|
477
|
-
error:nil];
|
|
478
|
-
return [pattern firstMatchInString:timestamp
|
|
479
|
-
options:0
|
|
480
|
-
range:NSMakeRange(0, timestamp.length)]
|
|
481
|
-
!= nil;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
- (BOOL)validErrorName:(id)value
|
|
485
|
-
{
|
|
486
|
-
static NSSet *allowedNames;
|
|
487
|
-
static dispatch_once_t onceGuard;
|
|
488
|
-
dispatch_once(&onceGuard, ^{
|
|
489
|
-
allowedNames = [NSSet setWithArray:@[
|
|
490
|
-
@"Error",
|
|
491
|
-
@"EvalError",
|
|
492
|
-
@"RangeError",
|
|
493
|
-
@"ReferenceError",
|
|
494
|
-
@"SyntaxError",
|
|
495
|
-
@"TypeError",
|
|
496
|
-
@"URIError",
|
|
497
|
-
]];
|
|
498
|
-
});
|
|
499
|
-
return [value isKindOfClass:[NSString class]] && [allowedNames containsObject:value];
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
- (BOOL)validFilename:(id)value
|
|
503
|
-
{
|
|
504
|
-
if (![value isKindOfClass:[NSString class]]) {
|
|
505
|
-
return NO;
|
|
506
|
-
}
|
|
507
|
-
NSString *filename = value;
|
|
508
|
-
if (filename.length == 0
|
|
509
|
-
|| [filename lengthOfBytesUsingEncoding:NSUTF8StringEncoding] > LBRNMaximumFilenameBytes
|
|
510
|
-
|| [filename hasPrefix:@"/"] || [filename containsString:@"\\"]
|
|
511
|
-
|| [filename containsString:@"://"] || [filename containsString:@"?"]
|
|
512
|
-
|| [filename containsString:@"#"]) {
|
|
513
|
-
return NO;
|
|
514
|
-
}
|
|
515
|
-
for (NSString *component in [filename componentsSeparatedByString:@"/"]) {
|
|
516
|
-
if ([component isEqualToString:@".."]) {
|
|
517
|
-
return NO;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
NSCharacterSet *control = [NSCharacterSet controlCharacterSet];
|
|
521
|
-
return [filename rangeOfCharacterFromSet:control].location == NSNotFound;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
- (BOOL)validPositiveInteger:(id)value
|
|
525
|
-
{
|
|
526
|
-
if (![value isKindOfClass:[NSNumber class]]) {
|
|
527
|
-
return NO;
|
|
528
|
-
}
|
|
529
|
-
double number = [value doubleValue];
|
|
530
|
-
return isfinite(number) && number >= 1 && number <= INT32_MAX && floor(number) == number;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
- (BOOL)validCounter:(id)value
|
|
534
|
-
{
|
|
535
|
-
if (![value isKindOfClass:[NSNumber class]]) {
|
|
536
|
-
return NO;
|
|
537
|
-
}
|
|
538
|
-
double number = [value doubleValue];
|
|
539
|
-
return isfinite(number) && number >= 0 && number <= LBRNMaximumCounter && floor(number) == number;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
@end
|