AutorÃa | Ultima modificación | Ver Log |
/** Copyright 2019 Google** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/#import <objc/runtime.h>#include <sys/utsname.h>#import <GoogleDataTransport/GoogleDataTransport.h>#import <GoogleUtilities/GULAppEnvironmentUtil.h>#import <GoogleUtilities/GULHeartbeatDateStorage.h>#import <GoogleUtilities/GULLogger.h>#import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsData.h"#import "Interop/CoreDiagnostics/Public/FIRCoreDiagnosticsInterop.h"#import <nanopb/pb.h>#import <nanopb/pb_decode.h>#import <nanopb/pb_encode.h>#import "Firebase/CoreDiagnostics/FIRCDLibrary/Protogen/nanopb/firebasecore.nanopb.h"/** The logger service string to use when printing to the console. */static GULLoggerService kFIRCoreDiagnostics = @"[FirebaseCoreDiagnostics/FIRCoreDiagnostics]";#ifdef FIREBASE_BUILD_ZIP_FILEstatic BOOL kUsingZipFile = YES;#else // FIREBASE_BUILD_ZIP_FILEstatic BOOL kUsingZipFile = NO;#endif // FIREBASE_BUILD_ZIP_FILE#if SWIFT_PACKAGE#define kDeploymentType logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_SPM#elif FIREBASE_BUILD_CARTHAGE#define kDeploymentType logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_CARTHAGE#elif FIREBASE_BUILD_ZIP_FILE#define kDeploymentType logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_ZIP_FILE#else#define kDeploymentType logs_proto_mobilesdk_ios_ICoreConfiguration_DeploymentType_COCOAPODS#endifstatic NSString *const kFIRServiceMLModelInterpreter = @"MLModelInterpreter";static NSString *const kFIRServiceAdMob = @"AdMob";static NSString *const kFIRServiceAuth = @"Auth";static NSString *const kFIRServiceAuthUI = @"AuthUI";static NSString *const kFIRServiceCrash = @"Crash";static NSString *const kFIRServiceDatabase = @"Database";static NSString *const kFIRServiceDynamicLinks = @"DynamicLinks";static NSString *const kFIRServiceFirestore = @"Firestore";static NSString *const kFIRServiceFunctions = @"Functions";static NSString *const kFIRServiceIAM = @"InAppMessaging";static NSString *const kFIRServiceInstanceID = @"InstanceID";static NSString *const kFIRServiceInvites = @"Invites";static NSString *const kFIRServiceMessaging = @"Messaging";static NSString *const kFIRServiceMeasurement = @"Measurement";static NSString *const kFIRServicePerformance = @"Performance";static NSString *const kFIRServiceRemoteConfig = @"RemoteConfig";static NSString *const kFIRServiceStorage = @"Storage";static NSString *const kGGLServiceAnalytics = @"Analytics";static NSString *const kGGLServiceSignIn = @"SignIn";static NSString *const kFIRAppDiagnosticsConfigurationTypeKey =@"FIRAppDiagnosticsConfigurationTypeKey";static NSString *const kFIRAppDiagnosticsFIRAppKey = @"FIRAppDiagnosticsFIRAppKey";static NSString *const kFIRAppDiagnosticsSDKNameKey = @"FIRAppDiagnosticsSDKNameKey";static NSString *const kFIRAppDiagnosticsSDKVersionKey = @"FIRAppDiagnosticsSDKVersionKey";static NSString *const kFIRCoreDiagnosticsHeartbeatTag = @"FIRCoreDiagnostics";/*** The file name to the recent heartbeat date.*/NSString *const kFIRCoreDiagnosticsHeartbeatDateFileName = @"FIREBASE_DIAGNOSTICS_HEARTBEAT_DATE";/*** @note This should implement the GDTCOREventDataObject protocol, but can't because of* weak-linking.*/@interface FIRCoreDiagnosticsLog : NSObject/** The config that will be converted to proto bytes. */@property(nonatomic) logs_proto_mobilesdk_ios_ICoreConfiguration config;@end@implementation FIRCoreDiagnosticsLog- (instancetype)initWithConfig:(logs_proto_mobilesdk_ios_ICoreConfiguration)config {self = [super init];if (self) {_config = config;}return self;}// Provided and required by the GDTCOREventDataObject protocol.- (NSData *)transportBytes {pb_ostream_t sizestream = PB_OSTREAM_SIZING;// Encode 1 time to determine the size.if (!pb_encode(&sizestream, logs_proto_mobilesdk_ios_ICoreConfiguration_fields, &_config)) {GDTCORLogError(GDTCORMCETransportBytesError, @"Error in nanopb encoding for size: %s",PB_GET_ERROR(&sizestream));}// Encode a 2nd time to actually get the bytes from it.size_t bufferSize = sizestream.bytes_written;CFMutableDataRef dataRef = CFDataCreateMutable(CFAllocatorGetDefault(), bufferSize);CFDataSetLength(dataRef, bufferSize);pb_ostream_t ostream = pb_ostream_from_buffer((void *)CFDataGetBytePtr(dataRef), bufferSize);if (!pb_encode(&ostream, logs_proto_mobilesdk_ios_ICoreConfiguration_fields, &_config)) {GDTCORLogError(GDTCORMCETransportBytesError, @"Error in nanopb encoding for bytes: %s",PB_GET_ERROR(&ostream));}CFDataSetLength(dataRef, ostream.bytes_written);return CFBridgingRelease(dataRef);}- (void)dealloc {pb_release(logs_proto_mobilesdk_ios_ICoreConfiguration_fields, &_config);}@endNS_ASSUME_NONNULL_BEGIN/** This class produces a protobuf containing diagnostics and usage data to be logged. */@interface FIRCoreDiagnostics : NSObject <FIRCoreDiagnosticsInterop>/** The queue on which all diagnostics collection will occur. */@property(nonatomic, readonly) dispatch_queue_t diagnosticsQueue;/** The transport object used to send data. */@property(nonatomic, readonly) GDTCORTransport *transport;/** The storage to store the date of the last sent heartbeat. */@property(nonatomic, readonly) GULHeartbeatDateStorage *heartbeatDateStorage;@endNS_ASSUME_NONNULL_END@implementation FIRCoreDiagnostics+ (instancetype)sharedInstance {static FIRCoreDiagnostics *sharedInstance;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{sharedInstance = [[FIRCoreDiagnostics alloc] init];});return sharedInstance;}- (instancetype)init {GDTCORTransport *transport = [[GDTCORTransport alloc] initWithMappingID:@"137"transformers:niltarget:kGDTCORTargetFLL];GULHeartbeatDateStorage *dateStorage =[[GULHeartbeatDateStorage alloc] initWithFileName:kFIRCoreDiagnosticsHeartbeatDateFileName];return [self initWithTransport:transport heartbeatDateStorage:dateStorage];}/** Initializer for unit tests.** @param transport A `GDTCORTransport` instance which that be used to send event.* @param heartbeatDateStorage An instanse of date storage to track heartbeat sending.* @return Returns the initialized `FIRCoreDiagnostics` instance.*/- (instancetype)initWithTransport:(GDTCORTransport *)transportheartbeatDateStorage:(GULHeartbeatDateStorage *)heartbeatDateStorage {self = [super init];if (self) {_diagnosticsQueue =dispatch_queue_create("com.google.FIRCoreDiagnostics", DISPATCH_QUEUE_SERIAL);_transport = transport;_heartbeatDateStorage = heartbeatDateStorage;}return self;}#pragma mark - nanopb helper functions/** Callocs a pb_bytes_array and copies the given NSString's bytes into the bytes array.** @note Memory needs to be free manually, through pb_free or pb_release.* @param string The string to encode as pb_bytes.*/pb_bytes_array_t *FIREncodeString(NSString *string) {NSData *stringBytes = [string dataUsingEncoding:NSUTF8StringEncoding];return FIREncodeData(stringBytes);}/** Callocs a pb_bytes_array and copies the given NSData bytes into the bytes array.** @note Memory needs to be free manually, through pb_free or pb_release.* @param data The data to copy into the new bytes array.*/pb_bytes_array_t *FIREncodeData(NSData *data) {pb_bytes_array_t *pbBytesArray = calloc(1, PB_BYTES_ARRAY_T_ALLOCSIZE(data.length));if (pbBytesArray != NULL) {[data getBytes:pbBytesArray->bytes length:data.length];pbBytesArray->size = (pb_size_t)data.length;}return pbBytesArray;}/** Maps a service string to the representative nanopb enum.** @param serviceString The SDK service string to convert.* @return The representative nanopb enum.*/logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType FIRMapFromServiceStringToTypeEnum(NSString *serviceString) {static NSDictionary<NSString *, NSNumber *> *serviceStringToTypeEnum;if (serviceStringToTypeEnum == nil) {serviceStringToTypeEnum = @{kFIRServiceAdMob : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ADMOB),kFIRServiceMessaging : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MESSAGING),kFIRServiceMeasurement :@(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_MEASUREMENT),kFIRServiceRemoteConfig :@(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_REMOTE_CONFIG),kFIRServiceDatabase : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_DATABASE),kFIRServiceDynamicLinks :@(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_DYNAMIC_LINKS),kFIRServiceAuth : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_AUTH),kFIRServiceAuthUI : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_AUTH_UI),kFIRServiceFirestore : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_FIRESTORE),kFIRServiceFunctions : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_FUNCTIONS),kFIRServicePerformance :@(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_PERFORMANCE),kFIRServiceStorage : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_STORAGE),kFIRServiceMLModelInterpreter :@(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ML_MODEL_INTERPRETER),kGGLServiceAnalytics : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ANALYTICS),kGGLServiceSignIn : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_SIGN_IN),kFIRServiceIAM : @(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_IN_APP_MESSAGING),};}if (serviceStringToTypeEnum[serviceString] != nil) {return (int32_t)serviceStringToTypeEnum[serviceString].longLongValue;}return logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_UNKNOWN_SDK_SERVICE;}#pragma mark - Proto population functions/** Populates the given proto with data related to an SDK logDiagnostics call from the* diagnosticObjects dictionary.** @param config The proto to populate* @param diagnosticObjects The dictionary of diagnostics objects.*/void FIRPopulateProtoWithInfoFromUserInfoParams(logs_proto_mobilesdk_ios_ICoreConfiguration *config,NSDictionary<NSString *, id> *diagnosticObjects) {NSNumber *configurationType = diagnosticObjects[kFIRCDConfigurationTypeKey];if (configurationType != nil) {switch (configurationType.integerValue) {case logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE:config->configuration_type =logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_CORE;config->has_configuration_type = 1;break;case logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_SDK:config->configuration_type =logs_proto_mobilesdk_ios_ICoreConfiguration_ConfigurationType_SDK;config->has_configuration_type = 1;break;default:break;}}NSString *sdkName = diagnosticObjects[kFIRCDSdkNameKey];if (sdkName) {config->sdk_name = FIRMapFromServiceStringToTypeEnum(sdkName);config->has_sdk_name = 1;}NSString *version = diagnosticObjects[kFIRCDSdkVersionKey];if (version) {config->sdk_version = FIREncodeString(version);}}/** Populates the given proto with data from the calling FIRApp using the given* diagnosticObjects dictionary.** @param config The proto to populate* @param diagnosticObjects The dictionary of diagnostics objects.*/void FIRPopulateProtoWithCommonInfoFromApp(logs_proto_mobilesdk_ios_ICoreConfiguration *config,NSDictionary<NSString *, id> *diagnosticObjects) {config->pod_name = logs_proto_mobilesdk_ios_ICoreConfiguration_PodName_FIREBASE;config->has_pod_name = 1;if (!diagnosticObjects[kFIRCDllAppsCountKey]) {GDTCORLogError(GDTCORMCEGeneralError, @"%@",@"App count is a required value in the data dict.");}config->app_count = (int32_t)[diagnosticObjects[kFIRCDllAppsCountKey] integerValue];config->has_app_count = 1;NSString *googleAppID = diagnosticObjects[kFIRCDGoogleAppIDKey];if (googleAppID.length) {config->app_id = FIREncodeString(googleAppID);}NSString *bundleID = diagnosticObjects[kFIRCDBundleIDKey];if (bundleID.length) {config->bundle_id = FIREncodeString(bundleID);}NSString *firebaseUserAgent = diagnosticObjects[kFIRCDFirebaseUserAgentKey];if (firebaseUserAgent.length) {config->platform_info = FIREncodeString(firebaseUserAgent);}NSNumber *usingOptionsFromDefaultPlist = diagnosticObjects[kFIRCDUsingOptionsFromDefaultPlistKey];if (usingOptionsFromDefaultPlist != nil) {config->use_default_app = [usingOptionsFromDefaultPlist boolValue];config->has_use_default_app = 1;}NSString *libraryVersionID = diagnosticObjects[kFIRCDLibraryVersionIDKey];if (libraryVersionID) {config->icore_version = FIREncodeString(libraryVersionID);}NSString *deviceModel = [GULAppEnvironmentUtil deviceModel];if (deviceModel.length) {config->device_model = FIREncodeString(deviceModel);}NSString *osVersion = [GULAppEnvironmentUtil systemVersion];if (osVersion.length) {config->os_version = FIREncodeString(osVersion);}config->using_zip_file = kUsingZipFile;config->has_using_zip_file = 1;config->deployment_type = kDeploymentType;config->has_deployment_type = 1;config->deployed_in_app_store = [GULAppEnvironmentUtil isFromAppStore];config->has_deployed_in_app_store = 1;}/** Populates the given proto with installed services data.** @param config The proto to populate*/void FIRPopulateProtoWithInstalledServices(logs_proto_mobilesdk_ios_ICoreConfiguration *config) {NSMutableArray<NSNumber *> *sdkServiceInstalledArray = [NSMutableArray array];// AdMobif (NSClassFromString(@"GADBannerView") != nil) {[sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceAdMob))];}// CloudMessagingif (NSClassFromString(@"FIRMessaging") != nil) {[sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMessaging))];}// RemoteConfigif (NSClassFromString(@"FIRRemoteConfig") != nil) {[sdkServiceInstalledArrayaddObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceRemoteConfig))];}// Measurement/Analtyicsif (NSClassFromString(@"FIRAnalytics") != nil) {[sdkServiceInstalledArrayaddObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMeasurement))];}// ML Model Interpreterif (NSClassFromString(@"FIRCustomModelInterpreter") != nil) {[sdkServiceInstalledArrayaddObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceMLModelInterpreter))];}// Databaseif (NSClassFromString(@"FIRDatabase") != nil) {[sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceDatabase))];}// DynamicDeepLinkif (NSClassFromString(@"FIRDynamicLinks") != nil) {[sdkServiceInstalledArrayaddObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceDynamicLinks))];}// Authif (NSClassFromString(@"FIRAuth") != nil) {[sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceAuth))];}// AuthUIif (NSClassFromString(@"FUIAuth") != nil) {[sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceAuthUI))];}// Firestoreif (NSClassFromString(@"FIRFirestore") != nil) {[sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceFirestore))];}// Functionsif (NSClassFromString(@"FIRFunctions") != nil) {[sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceFunctions))];}// Performanceif (NSClassFromString(@"FIRPerformance") != nil) {[sdkServiceInstalledArrayaddObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServicePerformance))];}// Storageif (NSClassFromString(@"FIRStorage") != nil) {[sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceStorage))];}// SignIn via Google podif (NSClassFromString(@"GIDSignIn") != nil && NSClassFromString(@"GGLContext") != nil) {[sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kGGLServiceSignIn))];}// Analytics via Google podif (NSClassFromString(@"GAI") != nil && NSClassFromString(@"GGLContext") != nil) {[sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kGGLServiceAnalytics))];}// In-App Messagingif (NSClassFromString(@"FIRInAppMessaging") != nil) {[sdkServiceInstalledArray addObject:@(FIRMapFromServiceStringToTypeEnum(kFIRServiceIAM))];}logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType *servicesInstalled =calloc(sdkServiceInstalledArray.count,sizeof(logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType));if (servicesInstalled == NULL) {return;}for (NSUInteger i = 0; i < sdkServiceInstalledArray.count; i++) {NSNumber *typeEnum = sdkServiceInstalledArray[i];logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType serviceType =(int32_t)typeEnum.integerValue;servicesInstalled[i] = serviceType;}config->sdk_service_installed = servicesInstalled;config->sdk_service_installed_count = (int32_t)sdkServiceInstalledArray.count;}/** Populates the proto with Info.plist values.** @param config The proto to populate.*/void FIRPopulateProtoWithInfoPlistValues(logs_proto_mobilesdk_ios_ICoreConfiguration *config) {NSDictionary<NSString *, id> *info = [[NSBundle mainBundle] infoDictionary];NSString *xcodeVersion = info[@"DTXcodeBuild"] ?: @"";NSString *sdkVersion = info[@"DTSDKBuild"] ?: @"";NSString *combinedVersions = [NSString stringWithFormat:@"%@-%@", xcodeVersion, sdkVersion];config->apple_framework_version = FIREncodeString(combinedVersions);NSString *minVersion = info[@"MinimumOSVersion"];if (minVersion) {config->min_supported_ios_version = FIREncodeString(minVersion);}// Apps can turn off swizzling in the Info.plist, check if they've explicitly set the value and// report it. It's enabled by default.NSNumber *appDelegateSwizzledNum = info[@"FirebaseAppDelegateProxyEnabled"];BOOL appDelegateSwizzled = YES;if ([appDelegateSwizzledNum isKindOfClass:[NSNumber class]]) {appDelegateSwizzled = [appDelegateSwizzledNum boolValue];}config->swizzling_enabled = appDelegateSwizzled;config->has_swizzling_enabled = 1;}#pragma mark - FIRCoreDiagnosticsInterop+ (void)sendDiagnosticsData:(nonnull id<FIRCoreDiagnosticsData>)diagnosticsData {FIRCoreDiagnostics *diagnostics = [FIRCoreDiagnostics sharedInstance];[diagnostics sendDiagnosticsData:diagnosticsData];}- (void)sendDiagnosticsData:(nonnull id<FIRCoreDiagnosticsData>)diagnosticsData {dispatch_async(self.diagnosticsQueue, ^{NSDictionary<NSString *, id> *diagnosticObjects = diagnosticsData.diagnosticObjects;NSNumber *isDataCollectionDefaultEnabled =diagnosticObjects[kFIRCDIsDataCollectionDefaultEnabledKey];if (isDataCollectionDefaultEnabled && ![isDataCollectionDefaultEnabled boolValue]) {return;}// Create the proto.logs_proto_mobilesdk_ios_ICoreConfiguration icore_config =logs_proto_mobilesdk_ios_ICoreConfiguration_init_default;icore_config.using_gdt = 1;icore_config.has_using_gdt = 1;// Populate the proto with information.FIRPopulateProtoWithInfoFromUserInfoParams(&icore_config, diagnosticObjects);FIRPopulateProtoWithCommonInfoFromApp(&icore_config, diagnosticObjects);FIRPopulateProtoWithInstalledServices(&icore_config);FIRPopulateProtoWithInfoPlistValues(&icore_config);[self setHeartbeatFlagIfNeededToConfig:&icore_config];// This log object is capable of converting the proto to bytes.FIRCoreDiagnosticsLog *log = [[FIRCoreDiagnosticsLog alloc] initWithConfig:icore_config];// Send the log as a telemetry event.GDTCOREvent *event = [self.transport eventForTransport];event.dataObject = (id<GDTCOREventDataObject>)log;[self.transport sendTelemetryEvent:event];});}#pragma mark - Heartbeat- (void)setHeartbeatFlagIfNeededToConfig:(logs_proto_mobilesdk_ios_ICoreConfiguration *)config {// Check if need to send a heartbeat.NSDate *currentDate = [NSDate date];NSDate *lastCheckin =[self.heartbeatDateStorage heartbeatDateForTag:kFIRCoreDiagnosticsHeartbeatTag];if (lastCheckin) {// Ensure the previous checkin was on a different date in the past.if ([self isDate:currentDate inSameDayOrBeforeThan:lastCheckin]) {return;}}// Update heartbeat sent date.[self.heartbeatDateStorage setHearbeatDate:currentDate forTag:kFIRCoreDiagnosticsHeartbeatTag];// Set the flag.config->sdk_name = logs_proto_mobilesdk_ios_ICoreConfiguration_ServiceType_ICORE;config->has_sdk_name = 1;}- (BOOL)isDate:(NSDate *)date1 inSameDayOrBeforeThan:(NSDate *)date2 {return [[NSCalendar currentCalendar] isDate:date1 inSameDayAsDate:date2] ||[date1 compare:date2] == NSOrderedAscending;}@end