Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// Copyright 2020 Google LLC
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
 
15
#import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.h"
16
#import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace+Private.h"
17
 
18
#import "FirebasePerformance/Sources/AppActivity/FPRSessionManager.h"
19
#import "FirebasePerformance/Sources/Common/FPRConstants.h"
20
#import "FirebasePerformance/Sources/Common/FPRDiagnostics.h"
21
#import "FirebasePerformance/Sources/Configurations/FPRConfigurations.h"
22
#import "FirebasePerformance/Sources/FPRClient.h"
23
#import "FirebasePerformance/Sources/FPRConsoleLogger.h"
24
#import "FirebasePerformance/Sources/FPRDataUtils.h"
25
#import "FirebasePerformance/Sources/FPRURLFilter.h"
26
#import "FirebasePerformance/Sources/Gauges/FPRGaugeManager.h"
27
 
28
#import <GoogleUtilities/GULObjectSwizzler.h>
29
 
30
NSString *const kFPRNetworkTracePropertyName = @"fpr_networkTrace";
31
 
32
@interface FPRNetworkTrace ()
33
 
34
@property(nonatomic, readwrite) NSURLRequest *URLRequest;
35
 
36
@property(nonatomic, readwrite, nullable) NSError *responseError;
37
 
38
/** State to know if the trace has started. */
39
@property(nonatomic) BOOL traceStarted;
40
 
41
/** State to know if the trace has completed. */
42
@property(nonatomic) BOOL traceCompleted;
43
 
44
/** Background activity tracker to know the background state of the trace. */
45
@property(nonatomic) FPRTraceBackgroundActivityTracker *backgroundActivityTracker;
46
 
47
/** Custom attribute managed internally. */
48
@property(nonatomic) NSMutableDictionary<NSString *, NSString *> *customAttributes;
49
 
50
/** @brief Serial queue to manage the updation of session Ids. */
51
@property(nonatomic, readwrite) dispatch_queue_t sessionIdSerialQueue;
52
 
53
/**
54
 * Updates the current trace with the current session details.
55
 * @param sessionDetails Updated session details of the currently active session.
56
 */
57
- (void)updateTraceWithCurrentSession:(FPRSessionDetails *)sessionDetails;
58
 
59
@end
60
 
61
@implementation FPRNetworkTrace {
62
  /**
63
   * @brief Object containing different states of the network request. Stores the information about
64
   * the state of a network request (defined in FPRNetworkTraceCheckpointState) and the time at
65
   * which the event happened.
66
   */
67
  NSMutableDictionary<NSString *, NSNumber *> *_states;
68
}
69
 
70
- (nullable instancetype)initWithURLRequest:(NSURLRequest *)URLRequest {
71
  if (URLRequest.URL == nil) {
72
    FPRLogError(kFPRNetworkTraceInvalidInputs, @"Invalid URL. URL is nil.");
73
    return nil;
74
  }
75
 
76
  // Fail early instead of creating a trace here.
77
  // IMPORTANT: Order is important here. This check needs to be done before looking up on remote
78
  // config. Reference bug: b/141861005.
79
  if (![[FPRURLFilter sharedInstance] shouldInstrumentURL:URLRequest.URL.absoluteString]) {
80
    return nil;
81
  }
82
 
83
  BOOL tracingEnabled = [FPRConfigurations sharedInstance].isDataCollectionEnabled;
84
  if (!tracingEnabled) {
85
    FPRLogInfo(kFPRTraceDisabled, @"Trace feature is disabled.");
86
    return nil;
87
  }
88
 
89
  BOOL sdkEnabled = [[FPRConfigurations sharedInstance] sdkEnabled];
90
  if (!sdkEnabled) {
91
    FPRLogInfo(kFPRTraceDisabled, @"Dropping event since Performance SDK is disabled.");
92
    return nil;
93
  }
94
 
95
  NSString *trimmedURLString = [FPRNetworkTrace stringByTrimmingURLString:URLRequest];
96
  if (!trimmedURLString || trimmedURLString.length <= 0) {
97
    FPRLogWarning(kFPRNetworkTraceURLLengthExceeds, @"URL length outside limits, returning nil.");
98
    return nil;
99
  }
100
 
101
  if (![URLRequest.URL.absoluteString isEqualToString:trimmedURLString]) {
102
    FPRLogInfo(kFPRNetworkTraceURLLengthTruncation,
103
               @"URL length exceeds limits, truncating recorded URL - %@.", trimmedURLString);
104
  }
105
 
106
  self = [super init];
107
  if (self) {
108
    _URLRequest = URLRequest;
109
    _trimmedURLString = trimmedURLString;
110
    _states = [[NSMutableDictionary<NSString *, NSNumber *> alloc] init];
111
    _hasValidResponseCode = NO;
112
    _customAttributes = [[NSMutableDictionary<NSString *, NSString *> alloc] init];
113
    _syncQueue =
114
        dispatch_queue_create("com.google.perf.networkTrace.metric", DISPATCH_QUEUE_SERIAL);
115
    _sessionIdSerialQueue =
116
        dispatch_queue_create("com.google.perf.sessionIds.networkTrace", DISPATCH_QUEUE_SERIAL);
117
    _activeSessions = [[NSMutableArray<FPRSessionDetails *> alloc] init];
118
    if (![FPRNetworkTrace isCompleteAndValidTrimmedURLString:_trimmedURLString
119
                                                  URLRequest:_URLRequest]) {
120
      return nil;
121
    };
122
  }
123
  return self;
124
}
125
 
126
- (instancetype)init {
127
  FPRAssert(NO, @"Not a designated initializer.");
128
  return nil;
129
}
130
 
131
- (void)dealloc {
132
  // Safety net to ensure the notifications are not received anymore.
133
  FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
134
  [sessionManager.sessionNotificationCenter removeObserver:self
135
                                                      name:kFPRSessionIdUpdatedNotification
136
                                                    object:sessionManager];
137
}
138
 
139
- (NSString *)description {
140
  return [NSString stringWithFormat:@"Request: %@", _URLRequest];
141
}
142
 
143
- (void)sessionChanged:(NSNotification *)notification {
144
  if (self.traceStarted && !self.traceCompleted) {
145
    NSDictionary<NSString *, FPRSessionDetails *> *userInfo = notification.userInfo;
146
    FPRSessionDetails *sessionDetails = [userInfo valueForKey:kFPRSessionIdNotificationKey];
147
    if (sessionDetails) {
148
      [self updateTraceWithCurrentSession:sessionDetails];
149
    }
150
  }
151
}
152
 
153
- (void)updateTraceWithCurrentSession:(FPRSessionDetails *)sessionDetails {
154
  if (sessionDetails != nil) {
155
    dispatch_sync(self.sessionIdSerialQueue, ^{
156
      [self.activeSessions addObject:sessionDetails];
157
    });
158
  }
159
}
160
 
161
- (NSArray<FPRSessionDetails *> *)sessions {
162
  __block NSArray<FPRSessionDetails *> *sessionInfos = nil;
163
  dispatch_sync(self.sessionIdSerialQueue, ^{
164
    sessionInfos = [self.activeSessions copy];
165
  });
166
  return sessionInfos;
167
}
168
 
169
- (NSDictionary<NSString *, NSNumber *> *)checkpointStates {
170
  __block NSDictionary<NSString *, NSNumber *> *copiedStates;
171
  dispatch_sync(self.syncQueue, ^{
172
    copiedStates = [_states copy];
173
  });
174
  return copiedStates;
175
}
176
 
177
- (void)checkpointState:(FPRNetworkTraceCheckpointState)state {
178
  if (!self.traceCompleted && self.traceStarted) {
179
    NSString *stateKey = @(state).stringValue;
180
    if (stateKey) {
181
      dispatch_sync(self.syncQueue, ^{
182
        NSNumber *existingState = _states[stateKey];
183
 
184
        if (existingState == nil) {
185
          double intervalSinceEpoch = [[NSDate date] timeIntervalSince1970];
186
          [_states setObject:@(intervalSinceEpoch) forKey:stateKey];
187
        }
188
      });
189
    } else {
190
      FPRAssert(NO, @"stateKey wasn't created for checkpoint state %ld", (long)state);
191
    }
192
  }
193
}
194
 
195
- (void)start {
196
  if (!self.traceCompleted) {
197
    [[FPRGaugeManager sharedInstance] collectAllGauges];
198
    self.traceStarted = YES;
199
    self.backgroundActivityTracker = [[FPRTraceBackgroundActivityTracker alloc] init];
200
    [self checkpointState:FPRNetworkTraceCheckpointStateInitiated];
201
 
202
    if ([self.URLRequest.HTTPMethod isEqualToString:@"POST"] ||
203
        [self.URLRequest.HTTPMethod isEqualToString:@"PUT"]) {
204
      self.requestSize = self.URLRequest.HTTPBody.length;
205
    }
206
    FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
207
    [self updateTraceWithCurrentSession:[sessionManager.sessionDetails copy]];
208
    [sessionManager.sessionNotificationCenter addObserver:self
209
                                                 selector:@selector(sessionChanged:)
210
                                                     name:kFPRSessionIdUpdatedNotification
211
                                                   object:sessionManager];
212
  }
213
}
214
 
215
- (FPRTraceState)backgroundTraceState {
216
  FPRTraceBackgroundActivityTracker *backgroundActivityTracker = self.backgroundActivityTracker;
217
  if (backgroundActivityTracker) {
218
    return backgroundActivityTracker.traceBackgroundState;
219
  }
220
 
221
  return FPRTraceStateUnknown;
222
}
223
 
224
- (NSTimeInterval)startTimeSinceEpoch {
225
  NSString *stateKey =
226
      [NSString stringWithFormat:@"%lu", (unsigned long)FPRNetworkTraceCheckpointStateInitiated];
227
  __block NSTimeInterval timeSinceEpoch;
228
  dispatch_sync(self.syncQueue, ^{
229
    timeSinceEpoch = [[_states objectForKey:stateKey] doubleValue];
230
  });
231
  return timeSinceEpoch;
232
}
233
 
234
#pragma mark - Overrides
235
 
236
- (void)setResponseCode:(int32_t)responseCode {
237
  _responseCode = responseCode;
238
  if (responseCode != 0) {
239
    _hasValidResponseCode = YES;
240
  }
241
}
242
 
243
#pragma mark - FPRNetworkResponseHandler methods
244
 
245
- (void)didCompleteRequestWithResponse:(NSURLResponse *)response error:(NSError *)error {
246
  if (!self.traceCompleted && self.traceStarted) {
247
    // Extract needed fields for the trace object.
248
    if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
249
      NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response;
250
      self.responseCode = (int32_t)HTTPResponse.statusCode;
251
    }
252
    self.responseError = error;
253
    self.responseContentType = response.MIMEType;
254
    [self checkpointState:FPRNetworkTraceCheckpointStateResponseCompleted];
255
 
256
    // Send the network trace for logging.
257
    [[FPRGaugeManager sharedInstance] collectAllGauges];
258
    [[FPRClient sharedInstance] logNetworkTrace:self];
259
 
260
    self.traceCompleted = YES;
261
  }
262
 
263
  FPRSessionManager *sessionManager = [FPRSessionManager sharedInstance];
264
  [sessionManager.sessionNotificationCenter removeObserver:self
265
                                                      name:kFPRSessionIdUpdatedNotification
266
                                                    object:sessionManager];
267
}
268
 
269
- (void)didUploadFileWithURL:(NSURL *)URL {
270
  NSNumber *value = nil;
271
  NSError *error = nil;
272
 
273
  if ([URL getResourceValue:&value forKey:NSURLFileSizeKey error:&error]) {
274
    if (error) {
275
      FPRLogNotice(kFPRNetworkTraceFileError, @"Unable to determine the size of file.");
276
    } else {
277
      self.requestSize = value.unsignedIntegerValue;
278
    }
279
  }
280
}
281
 
282
- (void)didReceiveData:(NSData *)data {
283
  self.responseSize = data.length;
284
}
285
 
286
- (void)didReceiveFileURL:(NSURL *)URL {
287
  NSNumber *value = nil;
288
  NSError *error = nil;
289
 
290
  if ([URL getResourceValue:&value forKey:NSURLFileSizeKey error:&error]) {
291
    if (error) {
292
      FPRLogNotice(kFPRNetworkTraceFileError, @"Unable to determine the size of file.");
293
    } else {
294
      self.responseSize = value.unsignedIntegerValue;
295
    }
296
  }
297
}
298
 
299
- (NSTimeInterval)timeIntervalBetweenCheckpointState:(FPRNetworkTraceCheckpointState)startState
300
                                            andState:(FPRNetworkTraceCheckpointState)endState {
301
  __block NSNumber *startStateTime;
302
  __block NSNumber *endStateTime;
303
  dispatch_sync(self.syncQueue, ^{
304
    startStateTime = [_states objectForKey:[@(startState) stringValue]];
305
    endStateTime = [_states objectForKey:[@(endState) stringValue]];
306
  });
307
  // Fail fast. If any of the times do not exist, return 0.
308
  if (startStateTime == nil || endStateTime == nil) {
309
    return 0;
310
  }
311
 
312
  NSTimeInterval timeDiff = (endStateTime.doubleValue - startStateTime.doubleValue);
313
  return timeDiff;
314
}
315
 
316
/** Trims and validates the URL string of a given NSURLRequest.
317
 *
318
 *  @param URLRequest The NSURLRequest containing the URL string to trim.
319
 *  @return The trimmed string.
320
 */
321
+ (NSString *)stringByTrimmingURLString:(NSURLRequest *)URLRequest {
322
  NSURLComponents *components = [NSURLComponents componentsWithURL:URLRequest.URL
323
                                           resolvingAgainstBaseURL:NO];
324
  components.query = nil;
325
  components.fragment = nil;
326
  components.user = nil;
327
  components.password = nil;
328
  NSURL *trimmedURL = [components URL];
329
  NSString *truncatedURLString = FPRTruncatedURLString(trimmedURL.absoluteString);
330
 
331
  NSURL *truncatedURL = [NSURL URLWithString:truncatedURLString];
332
  if (!truncatedURL || truncatedURL.host == nil) {
333
    return nil;
334
  }
335
  return truncatedURLString;
336
}
337
 
338
/** Validates the trace object by checking that it's http or https, and not a denied URL.
339
 *
340
 *  @param trimmedURLString A trimmed URL string from the URLRequest.
341
 *  @param URLRequest The NSURLRequest that this trace will operate on.
342
 *  @return YES if the trace object is valid, NO otherwise.
343
 */
344
+ (BOOL)isCompleteAndValidTrimmedURLString:(NSString *)trimmedURLString
345
                                URLRequest:(NSURLRequest *)URLRequest {
346
  if (![[FPRURLFilter sharedInstance] shouldInstrumentURL:trimmedURLString]) {
347
    return NO;
348
  }
349
 
350
  // Check the URL begins with http or https.
351
  NSURLComponents *components = [NSURLComponents componentsWithURL:URLRequest.URL
352
                                           resolvingAgainstBaseURL:NO];
353
  NSString *scheme = components.scheme;
354
  if (!scheme || !([scheme caseInsensitiveCompare:@"HTTP"] == NSOrderedSame ||
355
                   [scheme caseInsensitiveCompare:@"HTTPS"] == NSOrderedSame)) {
356
    FPRLogError(kFPRNetworkTraceInvalidInputs, @"Invalid URL - %@, returning nil.", URLRequest.URL);
357
    return NO;
358
  }
359
 
360
  return YES;
361
}
362
 
363
#pragma mark - Custom attributes related methods
364
 
365
- (NSDictionary<NSString *, NSString *> *)attributes {
366
  return [self.customAttributes copy];
367
}
368
 
369
- (void)setValue:(NSString *)value forAttribute:(nonnull NSString *)attribute {
370
  BOOL canAddAttribute = YES;
371
  if (self.traceCompleted) {
372
    FPRLogError(kFPRTraceAlreadyStopped,
373
                @"Failed to set attribute %@ because network request %@ has already stopped.",
374
                attribute, self.URLRequest.URL);
375
    canAddAttribute = NO;
376
  }
377
 
378
  NSString *validatedName = FPRReservableAttributeName(attribute);
379
  NSString *validatedValue = FPRValidatedAttributeValue(value);
380
 
381
  if (validatedName == nil) {
382
    FPRLogError(kFPRAttributeNoName,
383
                @"Failed to initialize because of a nil or zero length attribute name.");
384
    canAddAttribute = NO;
385
  }
386
 
387
  if (validatedValue == nil) {
388
    FPRLogError(kFPRAttributeNoValue,
389
                @"Failed to initialize because of a nil or zero length attribute value.");
390
    canAddAttribute = NO;
391
  }
392
 
393
  if (self.customAttributes.allKeys.count >= kFPRMaxGlobalCustomAttributesCount) {
394
    FPRLogError(kFPRMaxAttributesReached,
395
                @"Only %d attributes allowed. Already reached maximum attribute count.",
396
                kFPRMaxGlobalCustomAttributesCount);
397
    canAddAttribute = NO;
398
  }
399
 
400
  if (canAddAttribute) {
401
    // Ensure concurrency during update of attributes.
402
    dispatch_sync(self.syncQueue, ^{
403
      self.customAttributes[validatedName] = validatedValue;
404
      FPRLogDebug(kFPRClientMetricLogged, @"Setting attribute %@ to %@ on network request %@",
405
                  validatedName, validatedValue, self.URLRequest.URL);
406
    });
407
  }
408
}
409
 
410
- (NSString *)valueForAttribute:(NSString *)attribute {
411
  // TODO(b/175053654): Should this be happening on the serial queue for thread safety?
412
  return self.customAttributes[attribute];
413
}
414
 
415
- (void)removeAttribute:(NSString *)attribute {
416
  if (self.traceCompleted) {
417
    FPRLogError(kFPRTraceAlreadyStopped,
418
                @"Failed to remove attribute %@ because network request %@ has already stopped.",
419
                attribute, self.URLRequest.URL);
420
    return;
421
  }
422
 
423
  [self.customAttributes removeObjectForKey:attribute];
424
}
425
 
426
#pragma mark - Class methods related to object association.
427
 
428
+ (void)addNetworkTrace:(FPRNetworkTrace *)networkTrace toObject:(id)object {
429
  if (object != nil && networkTrace != nil) {
430
    [GULObjectSwizzler setAssociatedObject:object
431
                                       key:kFPRNetworkTracePropertyName
432
                                     value:networkTrace
433
                               association:GUL_ASSOCIATION_RETAIN_NONATOMIC];
434
  }
435
}
436
 
437
+ (FPRNetworkTrace *)networkTraceFromObject:(id)object {
438
  FPRNetworkTrace *networkTrace = nil;
439
  if (object != nil) {
440
    id traceObject = [GULObjectSwizzler getAssociatedObject:object
441
                                                        key:kFPRNetworkTracePropertyName];
442
    if ([traceObject isKindOfClass:[FPRNetworkTrace class]]) {
443
      networkTrace = (FPRNetworkTrace *)traceObject;
444
    }
445
  }
446
 
447
  return networkTrace;
448
}
449
 
450
+ (void)removeNetworkTraceFromObject:(id)object {
451
  if (object != nil) {
452
    [GULObjectSwizzler setAssociatedObject:object
453
                                       key:kFPRNetworkTracePropertyName
454
                                     value:nil
455
                               association:GUL_ASSOCIATION_RETAIN_NONATOMIC];
456
  }
457
}
458
 
459
- (BOOL)isValid {
460
  return _hasValidResponseCode;
461
}
462
 
463
@end