FPRNSURLConnectionDelegateInstrument.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  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. #import "FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLConnectionDelegateInstrument.h"
  15. #import "FirebasePerformance/Sources/Instrumentation/FPRClassInstrumentor.h"
  16. #import "FirebasePerformance/Sources/Instrumentation/FPRInstrument_Private.h"
  17. #import "FirebasePerformance/Sources/Instrumentation/FPRNetworkTrace.h"
  18. #import "FirebasePerformance/Sources/Instrumentation/FPRProxyObjectHelper.h"
  19. #import "FirebasePerformance/Sources/Instrumentation/FPRSelectorInstrumentor.h"
  20. #import "FirebasePerformance/Sources/Instrumentation/Network/Delegates/FPRNSURLConnectionDelegate.h"
  21. #import "FirebasePerformance/Sources/Instrumentation/Network/FPRNetworkInstrumentHelpers.h"
  22. #pragma mark - NSURLConnectionDelegate methods
  23. /** Returns the dispatch queue for all instrumentation to occur on. */
  24. static dispatch_queue_t GetInstrumentationQueue(void) {
  25. static dispatch_queue_t queue;
  26. static dispatch_once_t token;
  27. dispatch_once(&token, ^{
  28. queue = dispatch_queue_create("com.google.FPRNSURLConnectionDelegateInstrument",
  29. DISPATCH_QUEUE_SERIAL);
  30. });
  31. return queue;
  32. }
  33. /** Instruments connection:didFailWithError:.
  34. *
  35. * @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
  36. */
  37. FOUNDATION_STATIC_INLINE
  38. NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
  39. void InstrumentConnectionDidFailWithError(FPRClassInstrumentor *instrumentor) {
  40. SEL selector = @selector(connection:didFailWithError:);
  41. FPRSelectorInstrumentor *selectorInstrumentor =
  42. [instrumentor instrumentorForInstanceSelector:selector];
  43. if (selectorInstrumentor) {
  44. IMP currentIMP = selectorInstrumentor.currentIMP;
  45. [selectorInstrumentor
  46. setReplacingBlock:^(id object, NSURLConnection *connection, NSError *error) {
  47. FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
  48. [trace didCompleteRequestWithResponse:nil error:error];
  49. [FPRNetworkTrace removeNetworkTraceFromObject:connection];
  50. typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSError *);
  51. ((OriginalImp)currentIMP)(object, selector, connection, error);
  52. }];
  53. }
  54. }
  55. #pragma mark - NSURLConnectionDataDelegate methods
  56. /** Instruments connection:willSendRequest:redirectResponse:.
  57. *
  58. * @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
  59. */
  60. FOUNDATION_STATIC_INLINE
  61. NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
  62. void InstrumentConnectionWillSendRequestRedirectResponse(FPRClassInstrumentor *instrumentor) {
  63. SEL selector = @selector(connection:willSendRequest:redirectResponse:);
  64. FPRSelectorInstrumentor *selectorInstrumentor =
  65. [instrumentor instrumentorForInstanceSelector:selector];
  66. if (selectorInstrumentor) {
  67. IMP currentIMP = selectorInstrumentor.currentIMP;
  68. [selectorInstrumentor setReplacingBlock:^(id object, NSURLConnection *connection,
  69. NSURLRequest *request, NSURLResponse *response) {
  70. FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
  71. [trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
  72. typedef NSURLRequest *(*OriginalImp)(id, SEL, NSURLConnection *, NSURLRequest *,
  73. NSURLResponse *);
  74. return ((OriginalImp)currentIMP)(object, selector, connection, request, response);
  75. }];
  76. }
  77. }
  78. /** Instruments connection:didReceiveResponse:.
  79. *
  80. * @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
  81. */
  82. FOUNDATION_STATIC_INLINE
  83. NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
  84. void InstrumentConnectionDidReceiveResponse(FPRClassInstrumentor *instrumentor) {
  85. SEL selector = @selector(connection:didReceiveResponse:);
  86. FPRSelectorInstrumentor *selectorInstrumentor =
  87. [instrumentor instrumentorForInstanceSelector:selector];
  88. if (selectorInstrumentor) {
  89. IMP currentIMP = selectorInstrumentor.currentIMP;
  90. [selectorInstrumentor
  91. setReplacingBlock:^(id object, NSURLConnection *connection, NSURLResponse *response) {
  92. FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
  93. if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
  94. trace.responseCode = (int32_t)((NSHTTPURLResponse *)response).statusCode;
  95. }
  96. [trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
  97. typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSURLResponse *);
  98. ((OriginalImp)currentIMP)(object, selector, connection, response);
  99. }];
  100. }
  101. }
  102. /** Instruments connection:didReceiveData:.
  103. *
  104. * @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
  105. */
  106. FOUNDATION_STATIC_INLINE
  107. NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
  108. void InstrumentConnectionDidReceiveData(FPRClassInstrumentor *instrumentor) {
  109. SEL selector = @selector(connection:didReceiveData:);
  110. FPRSelectorInstrumentor *selectorInstrumentor =
  111. [instrumentor instrumentorForInstanceSelector:selector];
  112. if (selectorInstrumentor) {
  113. IMP currentIMP = selectorInstrumentor.currentIMP;
  114. [selectorInstrumentor
  115. setReplacingBlock:^(id object, NSURLConnection *connection, NSData *data) {
  116. FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
  117. [trace checkpointState:FPRNetworkTraceCheckpointStateResponseReceived];
  118. trace.responseSize += data.length;
  119. typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSData *);
  120. ((OriginalImp)currentIMP)(object, selector, connection, data);
  121. }];
  122. }
  123. }
  124. /** Instruments connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:.
  125. *
  126. * @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
  127. */
  128. FOUNDATION_STATIC_INLINE
  129. NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
  130. void InstrumentConnectionAllTheTotals(FPRClassInstrumentor *instrumentor) {
  131. SEL selector = @selector(connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:);
  132. FPRSelectorInstrumentor *selectorInstrumentor =
  133. [instrumentor instrumentorForInstanceSelector:selector];
  134. if (selectorInstrumentor) {
  135. IMP currentIMP = selectorInstrumentor.currentIMP;
  136. [selectorInstrumentor
  137. setReplacingBlock:^(id object, NSURLConnection *connection, NSInteger bytesWritten,
  138. NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite) {
  139. FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
  140. trace.requestSize = totalBytesWritten;
  141. if (totalBytesWritten >= totalBytesExpectedToWrite) {
  142. [trace checkpointState:FPRNetworkTraceCheckpointStateRequestCompleted];
  143. }
  144. typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSInteger, NSInteger, NSInteger);
  145. ((OriginalImp)currentIMP)(object, selector, connection, bytesWritten, totalBytesWritten,
  146. totalBytesExpectedToWrite);
  147. }];
  148. }
  149. }
  150. /** Instruments connectionDidFinishLoading:.
  151. *
  152. * @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
  153. */
  154. FOUNDATION_STATIC_INLINE
  155. NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
  156. void InstrumentConnectionDidFinishLoading(FPRClassInstrumentor *instrumentor) {
  157. SEL selector = @selector(connectionDidFinishLoading:);
  158. FPRSelectorInstrumentor *selectorInstrumentor =
  159. [instrumentor instrumentorForInstanceSelector:selector];
  160. if (selectorInstrumentor) {
  161. IMP currentIMP = selectorInstrumentor.currentIMP;
  162. [selectorInstrumentor setReplacingBlock:^(id object, NSURLConnection *connection) {
  163. FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
  164. [trace didCompleteRequestWithResponse:nil error:nil];
  165. [FPRNetworkTrace removeNetworkTraceFromObject:connection];
  166. typedef void (*OriginalImp)(id, SEL, NSURLConnection *);
  167. ((OriginalImp)currentIMP)(object, selector, connection);
  168. }];
  169. }
  170. }
  171. /** Instruments connection:didWriteData:totalBytesWritten:expectedTotalBytes:.
  172. *
  173. * @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
  174. */
  175. FOUNDATION_STATIC_INLINE
  176. NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
  177. void InstrumentConnectionDidWriteDataTotalBytesWrittenExpectedTotalBytes(
  178. FPRClassInstrumentor *instrumentor) {
  179. SEL selector = @selector(connection:didWriteData:totalBytesWritten:expectedTotalBytes:);
  180. FPRSelectorInstrumentor *selectorInstrumentor =
  181. [instrumentor instrumentorForInstanceSelector:selector];
  182. if (selectorInstrumentor) {
  183. IMP currentIMP = selectorInstrumentor.currentIMP;
  184. [selectorInstrumentor
  185. setReplacingBlock:^(id object, NSURLConnection *connection, long long bytesWritten,
  186. long long totalBytesWritten, long long expectedTotalBytes) {
  187. FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
  188. trace.requestSize = totalBytesWritten;
  189. typedef void (*OriginalImp)(id, SEL, NSURLConnection *, long long, long long, long long);
  190. ((OriginalImp)currentIMP)(object, selector, connection, bytesWritten, totalBytesWritten,
  191. expectedTotalBytes);
  192. }];
  193. }
  194. }
  195. /** Instruments connectionDidFinishDownloading:destinationURL:.
  196. *
  197. * @param instrumentor The FPRClassInstrumentor to add the selector instrumentor to.
  198. */
  199. FOUNDATION_STATIC_INLINE
  200. NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
  201. void InstrumentConnectionDidFinishDownloadingDestinationURL(FPRClassInstrumentor *instrumentor) {
  202. SEL selector = @selector(connectionDidFinishDownloading:destinationURL:);
  203. FPRSelectorInstrumentor *selectorInstrumentor =
  204. [instrumentor instrumentorForInstanceSelector:selector];
  205. if (selectorInstrumentor) {
  206. IMP currentIMP = selectorInstrumentor.currentIMP;
  207. [selectorInstrumentor
  208. setReplacingBlock:^(id object, NSURLConnection *connection, NSURL *destinationURL) {
  209. FPRNetworkTrace *trace = [FPRNetworkTrace networkTraceFromObject:connection];
  210. [trace didReceiveFileURL:destinationURL];
  211. [trace didCompleteRequestWithResponse:nil error:nil];
  212. [FPRNetworkTrace removeNetworkTraceFromObject:connection];
  213. typedef void (*OriginalImp)(id, SEL, NSURLConnection *, NSURL *);
  214. ((OriginalImp)currentIMP)(object, selector, connection, destinationURL);
  215. }];
  216. }
  217. }
  218. #pragma mark - Helper functions
  219. FOUNDATION_STATIC_INLINE
  220. NS_EXTENSION_UNAVAILABLE("Firebase Performance is not supported for extensions.")
  221. void CopySelector(SEL selector, FPRObjectInstrumentor *instrumentor) {
  222. static Class fromClass = Nil;
  223. static dispatch_once_t onceToken;
  224. dispatch_once(&onceToken, ^{
  225. fromClass = [FPRNSURLConnectionDelegate class];
  226. });
  227. if (![instrumentor.instrumentedObject respondsToSelector:selector]) {
  228. [instrumentor copySelector:selector fromClass:fromClass isClassSelector:NO];
  229. }
  230. }
  231. #pragma mark - FPRNSURLConnectionDelegateInstrument
  232. @implementation FPRNSURLConnectionDelegateInstrument
  233. - (void)registerInstrumentors {
  234. // Do nothing by default. classes will be instrumented on-demand upon discovery.
  235. }
  236. - (void)registerClass:(Class)aClass {
  237. dispatch_sync(GetInstrumentationQueue(), ^{
  238. // If this class has already been instrumented, just return.
  239. FPRClassInstrumentor *instrumentor = [[FPRClassInstrumentor alloc] initWithClass:aClass];
  240. if (![self registerClassInstrumentor:instrumentor]) {
  241. return;
  242. }
  243. InstrumentConnectionDidFailWithError(instrumentor);
  244. InstrumentConnectionWillSendRequestRedirectResponse(instrumentor);
  245. InstrumentConnectionDidReceiveResponse(instrumentor);
  246. InstrumentConnectionDidReceiveData(instrumentor);
  247. InstrumentConnectionAllTheTotals(instrumentor);
  248. InstrumentConnectionDidFinishLoading(instrumentor);
  249. InstrumentConnectionDidWriteDataTotalBytesWrittenExpectedTotalBytes(instrumentor);
  250. InstrumentConnectionDidFinishDownloadingDestinationURL(instrumentor);
  251. [instrumentor swizzle];
  252. });
  253. }
  254. - (void)registerObject:(id)object {
  255. dispatch_sync(GetInstrumentationQueue(), ^{
  256. if ([object respondsToSelector:@selector(gul_class)]) {
  257. return;
  258. }
  259. if (![self isObjectInstrumentable:object]) {
  260. return;
  261. }
  262. FPRObjectInstrumentor *instrumentor = [[FPRObjectInstrumentor alloc] initWithObject:object];
  263. // Register the non-swizzled versions of these methods.
  264. CopySelector(@selector(connection:didFailWithError:), instrumentor);
  265. CopySelector(@selector(connection:willSendRequest:redirectResponse:), instrumentor);
  266. CopySelector(@selector(connection:didReceiveResponse:), instrumentor);
  267. CopySelector(@selector(connection:didReceiveData:), instrumentor);
  268. CopySelector(@selector(connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:),
  269. instrumentor);
  270. if (![object respondsToSelector:@selector(connectionDidFinishDownloading:destinationURL:)]) {
  271. CopySelector(@selector(connectionDidFinishLoading:), instrumentor);
  272. }
  273. CopySelector(@selector(connection:didWriteData:totalBytesWritten:expectedTotalBytes:),
  274. instrumentor);
  275. if (![object respondsToSelector:@selector(connectionDidFinishLoading:)]) {
  276. CopySelector(@selector(connectionDidFinishDownloading:destinationURL:), instrumentor);
  277. }
  278. [instrumentor swizzle];
  279. });
  280. }
  281. - (void)registerProxy:(id)proxy {
  282. [FPRProxyObjectHelper registerProxyObject:proxy
  283. forProtocol:@protocol(NSURLSessionDelegate)
  284. varFoundHandler:^(id ivar) {
  285. [self registerClass:[ivar class]];
  286. [self registerObject:ivar];
  287. }];
  288. }
  289. @end