firebase log level

This commit is contained in:
oscarz
2024-08-29 18:25:13 +08:00
parent 8500300d18
commit 27c160beaf
1165 changed files with 122916 additions and 1 deletions

View File

@ -0,0 +1,95 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTCompressionHelper.h"
#import <zlib.h>
@implementation GDTCCTCompressionHelper
+ (nullable NSData *)gzippedData:(NSData *)data {
#if defined(__LP64__) && __LP64__
// Don't support > 32bit length for 64 bit, see note in header.
if (data.length > UINT_MAX) {
return nil;
}
#endif
enum { kChunkSize = 1024 };
const void *bytes = [data bytes];
NSUInteger length = [data length];
int level = Z_DEFAULT_COMPRESSION;
if (!bytes || !length) {
return nil;
}
z_stream strm;
bzero(&strm, sizeof(z_stream));
int memLevel = 8; // Default.
int windowBits = 15 + 16; // Enable gzip header instead of zlib header.
int retCode;
if (deflateInit2(&strm, level, Z_DEFLATED, windowBits, memLevel, Z_DEFAULT_STRATEGY) != Z_OK) {
return nil;
}
// Hint the size at 1/4 the input size.
NSMutableData *result = [NSMutableData dataWithCapacity:(length / 4)];
unsigned char output[kChunkSize];
// Setup the input.
strm.avail_in = (unsigned int)length;
strm.next_in = (unsigned char *)bytes;
// Collect the data.
do {
// update what we're passing in
strm.avail_out = kChunkSize;
strm.next_out = output;
retCode = deflate(&strm, Z_FINISH);
if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) {
deflateEnd(&strm);
return nil;
}
// Collect what we got.
unsigned gotBack = kChunkSize - strm.avail_out;
if (gotBack > 0) {
[result appendBytes:output length:gotBack];
}
} while (retCode == Z_OK);
// If the loop exits, it used all input and the stream ended.
NSAssert(strm.avail_in == 0,
@"Should have finished deflating without using all input, %u bytes left", strm.avail_in);
NSAssert(retCode == Z_STREAM_END,
@"thought we finished deflate w/o getting a result of stream end, code %d", retCode);
// Clean up.
deflateEnd(&strm);
return result;
}
+ (BOOL)isGzipped:(NSData *)data {
const UInt8 *bytes = (const UInt8 *)data.bytes;
return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b);
}
@end

View File

@ -0,0 +1,322 @@
/*
* 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h"
#if TARGET_OS_IOS || TARGET_OS_TV
#import <UIKit/UIKit.h>
#elif TARGET_OS_OSX
#import <AppKit/AppKit.h>
#endif // TARGET_OS_IOS || TARGET_OS_TV
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
#import <nanopb/pb.h>
#import <nanopb/pb_decode.h>
#import <nanopb/pb_encode.h>
#import "GoogleDataTransport/GDTCCTLibrary/Public/GDTCOREvent+GDTCCTSupport.h"
#pragma mark - General purpose encoders
pb_bytes_array_t *GDTCCTEncodeString(NSString *string) {
NSData *stringBytes = [string dataUsingEncoding:NSUTF8StringEncoding];
return GDTCCTEncodeData(stringBytes);
}
pb_bytes_array_t *GDTCCTEncodeData(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;
}
#pragma mark - CCT object constructors
NSData *_Nullable GDTCCTEncodeBatchedLogRequest(gdt_cct_BatchedLogRequest *batchedLogRequest) {
pb_ostream_t sizestream = PB_OSTREAM_SIZING;
// Encode 1 time to determine the size.
if (!pb_encode(&sizestream, gdt_cct_BatchedLogRequest_fields, batchedLogRequest)) {
GDTCORLogError(GDTCORMCEGeneralError, @"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, gdt_cct_BatchedLogRequest_fields, batchedLogRequest)) {
GDTCORLogError(GDTCORMCEGeneralError, @"Error in nanopb encoding for bytes: %s",
PB_GET_ERROR(&ostream));
}
return CFBridgingRelease(dataRef);
}
gdt_cct_BatchedLogRequest GDTCCTConstructBatchedLogRequest(
NSDictionary<NSString *, NSSet<GDTCOREvent *> *> *logMappingIDToLogSet) {
gdt_cct_BatchedLogRequest batchedLogRequest = gdt_cct_BatchedLogRequest_init_default;
NSUInteger numberOfLogRequests = logMappingIDToLogSet.count;
gdt_cct_LogRequest *logRequests = calloc(numberOfLogRequests, sizeof(gdt_cct_LogRequest));
if (logRequests == NULL) {
return batchedLogRequest;
}
__block int i = 0;
[logMappingIDToLogSet enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull logMappingID,
NSSet<GDTCOREvent *> *_Nonnull logSet,
BOOL *_Nonnull stop) {
int32_t logSource = [logMappingID intValue];
gdt_cct_LogRequest logRequest = GDTCCTConstructLogRequest(logSource, logSet);
logRequests[i] = logRequest;
i++;
}];
batchedLogRequest.log_request = logRequests;
batchedLogRequest.log_request_count = (pb_size_t)numberOfLogRequests;
return batchedLogRequest;
}
gdt_cct_LogRequest GDTCCTConstructLogRequest(int32_t logSource,
NSSet<GDTCOREvent *> *_Nonnull logSet) {
if (logSet.count == 0) {
GDTCORLogError(GDTCORMCEGeneralError, @"%@",
@"An empty event set can't be serialized to proto.");
gdt_cct_LogRequest logRequest = gdt_cct_LogRequest_init_default;
return logRequest;
}
gdt_cct_LogRequest logRequest = gdt_cct_LogRequest_init_default;
logRequest.log_source = logSource;
logRequest.has_log_source = 1;
logRequest.client_info = GDTCCTConstructClientInfo();
logRequest.has_client_info = 1;
logRequest.log_event = calloc(logSet.count, sizeof(gdt_cct_LogEvent));
if (logRequest.log_event == NULL) {
return logRequest;
}
int i = 0;
for (GDTCOREvent *log in logSet) {
gdt_cct_LogEvent logEvent = GDTCCTConstructLogEvent(log);
logRequest.log_event[i] = logEvent;
i++;
}
logRequest.log_event_count = (pb_size_t)logSet.count;
GDTCORClock *currentTime = [GDTCORClock snapshot];
logRequest.request_time_ms = currentTime.timeMillis;
logRequest.has_request_time_ms = 1;
logRequest.request_uptime_ms = [currentTime uptimeMilliseconds];
logRequest.has_request_uptime_ms = 1;
return logRequest;
}
gdt_cct_LogEvent GDTCCTConstructLogEvent(GDTCOREvent *event) {
gdt_cct_LogEvent logEvent = gdt_cct_LogEvent_init_default;
logEvent.event_time_ms = event.clockSnapshot.timeMillis;
logEvent.has_event_time_ms = 1;
logEvent.event_uptime_ms = [event.clockSnapshot uptimeMilliseconds];
logEvent.has_event_uptime_ms = 1;
logEvent.timezone_offset_seconds = event.clockSnapshot.timezoneOffsetSeconds;
logEvent.has_timezone_offset_seconds = 1;
if (event.customBytes) {
NSData *networkConnectionInfoData = event.networkConnectionInfoData;
if (networkConnectionInfoData) {
[networkConnectionInfoData getBytes:&logEvent.network_connection_info
length:networkConnectionInfoData.length];
logEvent.has_network_connection_info = 1;
}
NSNumber *eventCode = event.eventCode;
if (eventCode != nil) {
logEvent.has_event_code = 1;
logEvent.event_code = [eventCode intValue];
}
}
NSError *error;
NSData *extensionBytes;
extensionBytes = event.serializedDataObjectBytes;
if (error) {
GDTCORLogWarning(GDTCORMCWFileReadError,
@"There was an error reading extension bytes from disk: %@", error);
return logEvent;
}
logEvent.source_extension = GDTCCTEncodeData(extensionBytes); // read bytes from the file.
if (event.productData) {
logEvent.compliance_data = GDTCCTConstructComplianceData(event.productData);
logEvent.has_compliance_data = 1;
}
return logEvent;
}
gdt_cct_ComplianceData GDTCCTConstructComplianceData(GDTCORProductData *productData) {
privacy_context_external_ExternalPRequestContext prequest =
privacy_context_external_ExternalPRequestContext_init_default;
prequest.origin_associated_product_id = productData.productID;
prequest.has_origin_associated_product_id = 1;
privacy_context_external_ExternalPrivacyContext privacy_context =
privacy_context_external_ExternalPrivacyContext_init_default;
privacy_context.prequest = prequest;
privacy_context.has_prequest = 1;
gdt_cct_ComplianceData complianceData = gdt_cct_ComplianceData_init_default;
complianceData.privacy_context = privacy_context;
complianceData.has_privacy_context = 1;
complianceData.product_id_origin = gdt_cct_ComplianceData_ProductIdOrigin_EVENT_OVERRIDE;
complianceData.has_product_id_origin = 1;
return complianceData;
}
gdt_cct_ClientInfo GDTCCTConstructClientInfo(void) {
gdt_cct_ClientInfo clientInfo = gdt_cct_ClientInfo_init_default;
clientInfo.client_type = gdt_cct_ClientInfo_ClientType_IOS_FIREBASE;
clientInfo.has_client_type = 1;
#if TARGET_OS_IOS || TARGET_OS_TV
clientInfo.ios_client_info = GDTCCTConstructiOSClientInfo();
clientInfo.has_ios_client_info = 1;
#elif TARGET_OS_OSX
clientInfo.mac_client_info = GDTCCTConstructMacClientInfo();
clientInfo.has_mac_client_info = 1;
#endif
return clientInfo;
}
gdt_cct_IosClientInfo GDTCCTConstructiOSClientInfo(void) {
gdt_cct_IosClientInfo iOSClientInfo = gdt_cct_IosClientInfo_init_default;
#if TARGET_OS_IOS || TARGET_OS_TV
UIDevice *device = [UIDevice currentDevice];
NSBundle *bundle = [NSBundle mainBundle];
NSLocale *locale = [NSLocale currentLocale];
iOSClientInfo.os_full_version = GDTCCTEncodeString(device.systemVersion);
NSArray *versionComponents = [device.systemVersion componentsSeparatedByString:@"."];
iOSClientInfo.os_major_version = GDTCCTEncodeString(versionComponents[0]);
NSString *version = [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
if (version) {
iOSClientInfo.application_build = GDTCCTEncodeString(version);
}
NSString *countryCode = [locale objectForKey:NSLocaleCountryCode];
if (countryCode) {
iOSClientInfo.country = GDTCCTEncodeString([locale objectForKey:NSLocaleCountryCode]);
}
iOSClientInfo.model = GDTCCTEncodeString(GDTCORDeviceModel());
NSString *languageCode = bundle.preferredLocalizations.firstObject;
iOSClientInfo.language_code =
languageCode ? GDTCCTEncodeString(languageCode) : GDTCCTEncodeString(@"en");
iOSClientInfo.application_bundle_id = GDTCCTEncodeString(bundle.bundleIdentifier);
#endif
return iOSClientInfo;
}
gdt_cct_MacClientInfo GDTCCTConstructMacClientInfo(void) {
gdt_cct_MacClientInfo macOSClientInfo = gdt_cct_MacClientInfo_init_default;
NSOperatingSystemVersion osVersion = [NSProcessInfo processInfo].operatingSystemVersion;
NSString *majorVersion = [@(osVersion.majorVersion) stringValue];
NSString *minorVersion = [@(osVersion.minorVersion) stringValue];
NSString *majorAndMinorString = [NSString stringWithFormat:@"%@.%@", majorVersion, minorVersion];
macOSClientInfo.os_major_version = GDTCCTEncodeString(majorAndMinorString);
NSString *patchVersion = [@(osVersion.patchVersion) stringValue];
NSString *majorMinorPatchString =
[NSString stringWithFormat:@"%@.%@", majorAndMinorString, patchVersion];
macOSClientInfo.os_full_version = GDTCCTEncodeString(majorMinorPatchString);
NSBundle *bundle = [NSBundle mainBundle];
NSString *version = [bundle objectForInfoDictionaryKey:(NSString *)kCFBundleVersionKey];
if (version) {
macOSClientInfo.application_build = GDTCCTEncodeString(version);
}
NSString *bundleID = bundle.bundleIdentifier;
if (bundleID) {
macOSClientInfo.application_bundle_id = GDTCCTEncodeString(bundleID);
}
return macOSClientInfo;
}
NSData *GDTCCTConstructNetworkConnectionInfoData(void) {
gdt_cct_NetworkConnectionInfo networkConnectionInfo = gdt_cct_NetworkConnectionInfo_init_default;
NSInteger currentNetworkType = GDTCORNetworkTypeMessage();
if (currentNetworkType) {
networkConnectionInfo.has_network_type = 1;
if (currentNetworkType == GDTCORNetworkTypeMobile) {
networkConnectionInfo.network_type = gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE;
networkConnectionInfo.mobile_subtype = GDTCCTNetworkConnectionInfoNetworkMobileSubtype();
if (networkConnectionInfo.mobile_subtype !=
gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE) {
networkConnectionInfo.has_mobile_subtype = 1;
}
} else {
networkConnectionInfo.network_type = gdt_cct_NetworkConnectionInfo_NetworkType_WIFI;
}
}
NSData *networkConnectionInfoData = [NSData dataWithBytes:&networkConnectionInfo
length:sizeof(networkConnectionInfo)];
return networkConnectionInfoData;
}
gdt_cct_NetworkConnectionInfo_MobileSubtype GDTCCTNetworkConnectionInfoNetworkMobileSubtype(void) {
NSNumber *networkMobileSubtypeMessage = @(GDTCORNetworkMobileSubTypeMessage());
if (!networkMobileSubtypeMessage.intValue) {
return gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE;
}
static NSDictionary<NSNumber *, NSNumber *> *MessageToNetworkSubTypeMessage;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
MessageToNetworkSubTypeMessage = @{
@(GDTCORNetworkMobileSubtypeGPRS) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_GPRS),
@(GDTCORNetworkMobileSubtypeEdge) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_EDGE),
@(GDTCORNetworkMobileSubtypeWCDMA) :
@(gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE),
@(GDTCORNetworkMobileSubtypeHSDPA) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_HSDPA),
@(GDTCORNetworkMobileSubtypeHSUPA) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_HSUPA),
@(GDTCORNetworkMobileSubtypeCDMA1x) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_CDMA),
@(GDTCORNetworkMobileSubtypeCDMAEVDORev0) :
@(gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_0),
@(GDTCORNetworkMobileSubtypeCDMAEVDORevA) :
@(gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_A),
@(GDTCORNetworkMobileSubtypeCDMAEVDORevB) :
@(gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_B),
@(GDTCORNetworkMobileSubtypeHRPD) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_EHRPD),
@(GDTCORNetworkMobileSubtypeLTE) : @(gdt_cct_NetworkConnectionInfo_MobileSubtype_LTE),
};
});
NSNumber *networkMobileSubtype = MessageToNetworkSubTypeMessage[networkMobileSubtypeMessage];
return networkMobileSubtype.intValue;
}
#pragma mark - CCT Object decoders
gdt_cct_LogResponse GDTCCTDecodeLogResponse(NSData *data, NSError **error) {
gdt_cct_LogResponse response = gdt_cct_LogResponse_init_default;
pb_istream_t istream = pb_istream_from_buffer([data bytes], [data length]);
if (!pb_decode(&istream, gdt_cct_LogResponse_fields, &response)) {
NSString *nanopb_error = [NSString stringWithFormat:@"%s", PB_GET_ERROR(&istream)];
NSDictionary *userInfo = @{@"nanopb error:" : nanopb_error};
if (error != NULL) {
*error = [NSError errorWithDomain:NSURLErrorDomain code:-1 userInfo:userInfo];
}
response = (gdt_cct_LogResponse)gdt_cct_LogResponse_init_default;
}
return response;
}

View File

@ -0,0 +1,28 @@
// Copyright 2024 Google LLC
//
// 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTURLSessionDataResponse.h"
@implementation GDTCCTURLSessionDataResponse
- (instancetype)initWithResponse:(NSHTTPURLResponse *)response HTTPBody:(NSData *)body {
self = [super init];
if (self) {
_HTTPResponse = response;
_HTTPBody = body;
}
return self;
}
@end

View File

@ -0,0 +1,668 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h"
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadBatch.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
#import <nanopb/pb.h>
#import <nanopb/pb_decode.h>
#import <nanopb/pb_encode.h>
#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTCompressionHelper.h"
#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h"
#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTURLSessionDataResponse.h"
#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCOREvent+GDTMetricsSupport.h"
#import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h"
NS_ASSUME_NONNULL_BEGIN
#ifdef GDTCOR_VERSION
#define STR(x) STR_EXPAND(x)
#define STR_EXPAND(x) #x
static NSString *const kGDTCCTSupportSDKVersion = @STR(GDTCOR_VERSION);
#else
static NSString *const kGDTCCTSupportSDKVersion = @"UNKNOWN";
#endif // GDTCOR_VERSION
typedef void (^GDTCCTUploaderURLTaskCompletion)(NSNumber *batchID,
NSSet<GDTCOREvent *> *_Nullable events,
NSData *_Nullable data,
NSURLResponse *_Nullable response,
NSError *_Nullable error);
typedef void (^GDTCCTUploaderEventBatchBlock)(NSNumber *_Nullable batchID,
NSSet<GDTCOREvent *> *_Nullable events);
@interface GDTCCTUploadOperation () <NSURLSessionDelegate>
/// The properties to store parameters passed in the initializer. See the initialized docs for
/// details.
@property(nonatomic, readonly) GDTCORTarget target;
@property(nonatomic, readonly) GDTCORUploadConditions conditions;
@property(nonatomic, readonly) NSURL *uploadURL;
@property(nonatomic, readonly) id<GDTCORStoragePromiseProtocol> storage;
@property(nonatomic, readonly) id<GDTCCTUploadMetadataProvider> metadataProvider;
@property(nonatomic, readonly, nullable) id<GDTCORMetricsControllerProtocol> metricsController;
/** The URL session that will attempt upload. */
@property(nonatomic, nullable) NSURLSession *uploaderSession;
/// The metrics being uploaded by the operation. These metrics are fetched and included as an event
/// in the upload batch as part of the upload process.
///
/// Metrics being uploaded are retained so they can be re-stored if upload is not successful.
@property(nonatomic, nullable) GDTCORMetrics *currentMetrics;
/// NSOperation state properties implementation.
@property(nonatomic, readwrite, getter=isExecuting) BOOL executing;
@property(nonatomic, readwrite, getter=isFinished) BOOL finished;
@property(nonatomic, readwrite) BOOL uploadAttempted;
@end
@implementation GDTCCTUploadOperation
- (instancetype)initWithTarget:(GDTCORTarget)target
conditions:(GDTCORUploadConditions)conditions
uploadURL:(NSURL *)uploadURL
queue:(dispatch_queue_t)queue
storage:(id<GDTCORStoragePromiseProtocol>)storage
metadataProvider:(id<GDTCCTUploadMetadataProvider>)metadataProvider
metricsController:(nullable id<GDTCORMetricsControllerProtocol>)metricsController {
self = [super init];
if (self) {
_uploaderQueue = queue;
_target = target;
_conditions = conditions;
_uploadURL = uploadURL;
_storage = storage;
_metadataProvider = metadataProvider;
_metricsController = metricsController;
}
return self;
}
- (NSURLSession *)uploaderSessionCreateIfNeeded {
if (_uploaderSession == nil) {
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_uploaderSession = [NSURLSession sessionWithConfiguration:config
delegate:self
delegateQueue:nil];
}
return _uploaderSession;
}
- (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions)conditions {
__block GDTCORBackgroundIdentifier backgroundTaskID = GDTCORBackgroundIdentifierInvalid;
dispatch_block_t backgroundTaskCompletion = ^{
// End the background task if there was one.
if (backgroundTaskID != GDTCORBackgroundIdentifierInvalid) {
[[GDTCORApplication sharedApplication] endBackgroundTask:backgroundTaskID];
backgroundTaskID = GDTCORBackgroundIdentifierInvalid;
}
};
backgroundTaskID = [[GDTCORApplication sharedApplication]
beginBackgroundTaskWithName:@"GDTCCTUploader-upload"
expirationHandler:^{
if (backgroundTaskID != GDTCORBackgroundIdentifierInvalid) {
// Cancel the upload and complete delivery.
[self.currentTask cancel];
// End the background task.
backgroundTaskCompletion();
} else {
GDTCORLog(GDTCORMCDDebugLog, GDTCORLoggingLevelWarnings,
@"Attempted to cancel invalid background task in "
"GDTCCTUploadOperation.");
}
}];
id<GDTCORStoragePromiseProtocol> storage = self.storage;
// 1. Check if the conditions for the target are suitable.
[self isReadyToUploadTarget:target conditions:conditions]
.validateOn(self.uploaderQueue,
^BOOL(NSNull *__unused _) {
// 2. Stop the operation if it has been cancelled.
return !self.isCancelled;
})
.thenOn(self.uploaderQueue,
^FBLPromise *(NSNull *result) {
// 3. Remove previously attempted batches.
return [storage removeAllBatchesForTarget:target deleteEvents:NO];
})
.thenOn(self.uploaderQueue,
^FBLPromise<NSNumber *> *(NSNull *__unused _) {
// There may be a big amount of events stored, so creating a batch may be an
// expensive operation.
// 4. Do a lightweight check if there are any events for the target first to
// finish early if there are none.
return [storage hasEventsForTarget:target];
})
.validateOn(self.uploaderQueue,
^BOOL(NSNumber *hasEvents) {
// 5. Stop operation if there are no events to upload.
return hasEvents.boolValue;
})
.thenOn(self.uploaderQueue,
^FBLPromise<GDTCORUploadBatch *> *(NSNumber *__unused _) {
// 6. Fetch events to upload.
GDTCORStorageEventSelector *eventSelector = [self eventSelectorTarget:target
withConditions:conditions];
return [storage batchWithEventSelector:eventSelector
batchExpiration:[NSDate dateWithTimeIntervalSinceNow:600]];
})
.thenOn(self.uploaderQueue,
^FBLPromise<GDTCORUploadBatch *> *(GDTCORUploadBatch *batch) {
// 7. Add metrics to the batch if the target has a
// corresponding metrics controller.
if (!self.metricsController) {
return [FBLPromise resolvedWith:batch];
}
return [self batchByAddingMetricsEventToBatch:batch forTarget:target];
})
.validateOn(self.uploaderQueue,
^BOOL(GDTCORUploadBatch *__unused _) {
// 8. Stop the operation if it has been cancelled.
return !self.isCancelled;
})
.thenOn(self.uploaderQueue,
^FBLPromise *(GDTCORUploadBatch *batch) {
// A non-empty batch has been created, consider it as an upload attempt.
self.uploadAttempted = YES;
// 9. Perform upload.
return [self uploadBatch:batch toTarget:target storage:storage];
})
.catchOn(self.uploaderQueue,
^(NSError *error){
// TODO: Consider reporting the error to the client.
})
.alwaysOn(self.uploaderQueue, ^{
// 10. Finish operation.
[self finishOperation];
backgroundTaskCompletion();
});
}
#pragma mark - Upload implementation details
/** Uploads a given batch from storage to a target. */
- (FBLPromise<NSNull *> *)uploadBatch:(GDTCORUploadBatch *)batch
toTarget:(GDTCORTarget)target
storage:(id<GDTCORStoragePromiseProtocol>)storage {
// 1. Send URL request.
return [self sendURLRequestWithBatch:batch target:target]
.thenOn(self.uploaderQueue,
^FBLPromise *(GDTCCTURLSessionDataResponse *response) {
// 2. Update the next upload time and process response.
[self updateNextUploadTimeWithResponse:response forTarget:target];
return [self processResponse:response forBatch:batch storage:storage];
})
.recoverOn(self.uploaderQueue, ^id(NSError *error) {
// If a network error occurred, move the events back to the main
// storage so they can attempt to be uploaded in the next attempt.
// Additionally, if metrics were added to the batch, place them back
// in storage.
if (self.currentMetrics) {
[self.metricsController offerMetrics:self.currentMetrics];
}
return [storage removeBatchWithID:batch.batchID deleteEvents:NO];
});
}
/** Processes a URL session response for a given batch from storage. */
- (FBLPromise<NSNull *> *)processResponse:(GDTCCTURLSessionDataResponse *)response
forBatch:(GDTCORUploadBatch *)batch
storage:(id<GDTCORStoragePromiseProtocol>)storage {
// Cleanup batch based on the response's status code.
NSInteger statusCode = response.HTTPResponse.statusCode;
BOOL isSuccess = statusCode >= 200 && statusCode < 300;
// Transient errors include "too many requests" (429) and server errors (5xx).
BOOL isTransientError =
statusCode == 429 || statusCode == 404 || (statusCode >= 500 && statusCode < 600);
BOOL shouldDeleteEvents = isSuccess || !isTransientError;
// If the batch included metrics and the upload failed, place metrics back
// in storage.
GDTCORMetrics *uploadedMetrics = [self currentMetrics];
if (uploadedMetrics && !isSuccess) {
[self.metricsController offerMetrics:uploadedMetrics];
}
if (isSuccess) {
GDTCORLogDebug(@"CCT: batch %@ uploaded. Batch will be deleted.", batch.batchID);
} else if (isTransientError) {
GDTCORLogDebug(@"CCT: batch %@ upload failed. Batch will attempt to be uploaded later.",
batch.batchID);
} else {
GDTCORLogDebug(@"CCT: batch %@ upload failed. Batch will be deleted.", batch.batchID);
if (/* isInvalidPayloadError */ statusCode == 400) {
// Log events that will be dropped due to the upload error.
[self.metricsController logEventsDroppedForReason:GDTCOREventDropReasonInvalidPayload
events:batch.events];
}
}
return [storage removeBatchWithID:batch.batchID deleteEvents:shouldDeleteEvents];
}
/** Composes and sends URL request. */
- (FBLPromise<GDTCCTURLSessionDataResponse *> *)sendURLRequestWithBatch:(GDTCORUploadBatch *)batch
target:(GDTCORTarget)target {
return [FBLPromise
onQueue:self.uploaderQueue
do:^NSURLRequest * {
// 1. Prepare URL request.
NSData *requestProtoData = [self constructRequestProtoWithEvents:batch.events];
NSData *gzippedData = [GDTCCTCompressionHelper gzippedData:requestProtoData];
BOOL usingGzipData =
gzippedData != nil && gzippedData.length < requestProtoData.length;
NSData *dataToSend = usingGzipData ? gzippedData : requestProtoData;
NSURLRequest *request = [self constructRequestWithURL:self.uploadURL
forTarget:target
data:dataToSend];
GDTCORLogDebug(@"CTT: request containing %lu events for batch: %@ for target: "
@"%ld created: %@",
(unsigned long)batch.events.count, batch.batchID, (long)target,
request);
return request;
}]
.thenOn(self.uploaderQueue,
^FBLPromise<GDTCCTURLSessionDataResponse *> *(NSURLRequest *request) {
// 2. Send URL request.
NSURLSession *session = [self uploaderSessionCreateIfNeeded];
return [FBLPromise wrapObjectOrErrorCompletion:^(
FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
[[session dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
if (error) {
handler(nil, error);
} else {
handler([[GDTCCTURLSessionDataResponse alloc]
initWithResponse:(NSHTTPURLResponse *)response
HTTPBody:data],
nil);
}
}] resume];
}];
})
.thenOn(self.uploaderQueue,
^GDTCCTURLSessionDataResponse *(GDTCCTURLSessionDataResponse *response) {
// Invalidate session to release the delegate (which is `self`) to break the retain
// cycle.
[self.uploaderSession finishTasksAndInvalidate];
return response;
})
.recoverOn(self.uploaderQueue, ^id(NSError *error) {
// Invalidate session to release the delegate (which is `self`) to break the retain cycle.
[self.uploaderSession finishTasksAndInvalidate];
// Re-throw the error.
return error;
});
}
/** Parses server response and update next upload time for the specified target based on it. */
- (void)updateNextUploadTimeWithResponse:(GDTCCTURLSessionDataResponse *)response
forTarget:(GDTCORTarget)target {
GDTCORClock *futureUploadTime;
if (response.HTTPBody) {
NSError *decodingError;
gdt_cct_LogResponse logResponse = GDTCCTDecodeLogResponse(response.HTTPBody, &decodingError);
if (!decodingError && logResponse.has_next_request_wait_millis) {
GDTCORLogDebug(@"CCT: The backend responded asking to not upload for %lld millis from now.",
logResponse.next_request_wait_millis);
futureUploadTime =
[GDTCORClock clockSnapshotInTheFuture:logResponse.next_request_wait_millis];
} else if (decodingError) {
GDTCORLogDebug(@"There was a response decoding error: %@", decodingError);
}
pb_release(gdt_cct_LogResponse_fields, &logResponse);
}
// If no futureUploadTime was parsed from the response body, then check
// [Retry-After](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header.
if (!futureUploadTime) {
NSString *retryAfterHeader = response.HTTPResponse.allHeaderFields[@"Retry-After"];
if (retryAfterHeader.length > 0) {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterDecimalStyle;
NSNumber *retryAfterSeconds = [formatter numberFromString:retryAfterHeader];
if (retryAfterSeconds != nil) {
uint64_t retryAfterMillis = retryAfterSeconds.unsignedIntegerValue * 1000u;
futureUploadTime = [GDTCORClock clockSnapshotInTheFuture:retryAfterMillis];
}
}
}
if (!futureUploadTime) {
GDTCORLogDebug(@"%@", @"CCT: The backend response failed to parse, so the next request "
@"won't occur until 15 minutes from now");
// 15 minutes from now.
futureUploadTime = [GDTCORClock clockSnapshotInTheFuture:15 * 60 * 1000];
}
[self.metadataProvider setNextUploadTime:futureUploadTime forTarget:target];
}
#pragma mark - Private helper methods
/** @return A resolved promise if is ready and a rejected promise if not. */
- (FBLPromise<NSNull *> *)isReadyToUploadTarget:(GDTCORTarget)target
conditions:(GDTCORUploadConditions)conditions {
FBLPromise<NSNull *> *promise = [FBLPromise pendingPromise];
if ([self readyToUploadTarget:target conditions:conditions]) {
[promise fulfill:[NSNull null]];
} else {
NSString *reason =
[NSString stringWithFormat:@"Target %ld is not ready to upload with condition: %ld",
(long)target, (long)conditions];
[promise reject:[self genericRejectedPromiseErrorWithReason:reason]];
}
return promise;
}
// TODO: Move to a separate class/extension/file when needed in other files.
/** Returns an error object with the specified failure reason. */
- (NSError *)genericRejectedPromiseErrorWithReason:(NSString *)reason {
return [NSError errorWithDomain:@"GDTCCTUploader"
code:-1
userInfo:@{NSLocalizedFailureReasonErrorKey : reason}];
}
/** Returns if the specified target is ready to be uploaded based on the specified conditions. */
- (BOOL)readyToUploadTarget:(GDTCORTarget)target conditions:(GDTCORUploadConditions)conditions {
// Not ready to upload with no network connection.
// TODO: Reconsider using reachability to prevent an upload attempt.
// See https://developer.apple.com/videos/play/wwdc2019/712/ (49:40) for more details.
if (conditions & GDTCORUploadConditionNoNetwork) {
GDTCORLogDebug(@"%@", @"CCT: Not ready to upload without a network connection.");
return NO;
}
// Upload events with no additional conditions if high priority.
if ((conditions & GDTCORUploadConditionHighPriority) == GDTCORUploadConditionHighPriority) {
GDTCORLogDebug(@"%@", @"CCT: a high priority event is allowing an upload");
return YES;
}
// Check next upload time for the target.
BOOL isAfterNextUploadTime = YES;
GDTCORClock *nextUploadTime = [self.metadataProvider nextUploadTimeForTarget:target];
if (nextUploadTime) {
isAfterNextUploadTime = [[GDTCORClock snapshot] isAfter:nextUploadTime];
}
if (isAfterNextUploadTime) {
GDTCORLogDebug(@"CCT: can upload to target %ld because the request wait time has transpired",
(long)target);
} else {
GDTCORLogDebug(@"CCT: can't upload to target %ld because the backend asked to wait",
(long)target);
}
return isAfterNextUploadTime;
}
/** Constructs data given an upload package.
*
* @param events The events used to construct the request proto bytes.
* @return Proto bytes representing a gdt_cct_LogRequest object.
*/
- (nonnull NSData *)constructRequestProtoWithEvents:(NSSet<GDTCOREvent *> *)events {
// Segment the log events by log type.
NSMutableDictionary<NSString *, NSMutableSet<GDTCOREvent *> *> *logMappingIDToLogSet =
[[NSMutableDictionary alloc] init];
[events enumerateObjectsUsingBlock:^(GDTCOREvent *_Nonnull event, BOOL *_Nonnull stop) {
NSMutableSet *logSet = logMappingIDToLogSet[event.mappingID];
logSet = logSet ? logSet : [[NSMutableSet alloc] init];
[logSet addObject:event];
logMappingIDToLogSet[event.mappingID] = logSet;
}];
gdt_cct_BatchedLogRequest batchedLogRequest =
GDTCCTConstructBatchedLogRequest(logMappingIDToLogSet);
NSData *data = GDTCCTEncodeBatchedLogRequest(&batchedLogRequest);
pb_release(gdt_cct_BatchedLogRequest_fields, &batchedLogRequest);
return data ? data : [[NSData alloc] init];
}
/** Constructs a request to the given URL and target with the specified request body data.
*
* @param target The target backend to send the request to.
* @param data The request body data.
* @return A new NSURLRequest ready to be sent to FLL.
*/
- (nullable NSURLRequest *)constructRequestWithURL:(NSURL *)URL
forTarget:(GDTCORTarget)target
data:(NSData *)data {
if (data == nil || data.length == 0) {
GDTCORLogDebug(@"There was no data to construct a request for target %ld.", (long)target);
return nil;
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
NSString *targetString;
switch (target) {
case kGDTCORTargetCCT:
targetString = @"cct";
break;
case kGDTCORTargetFLL:
targetString = @"fll";
break;
case kGDTCORTargetCSH:
targetString = @"csh";
break;
case kGDTCORTargetINT:
targetString = @"int";
break;
default:
targetString = @"unknown";
break;
}
NSString *userAgent =
[NSString stringWithFormat:@"datatransport/%@ %@support/%@ apple/", kGDTCORVersion,
targetString, kGDTCCTSupportSDKVersion];
[request setValue:[self.metadataProvider APIKeyForTarget:target]
forHTTPHeaderField:@"X-Goog-Api-Key"];
if ([GDTCCTCompressionHelper isGzipped:data]) {
[request setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
}
[request setValue:@"application/x-protobuf" forHTTPHeaderField:@"Content-Type"];
[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
[request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
request.HTTPMethod = @"POST";
[request setHTTPBody:data];
return request;
}
/** Creates and returns a storage event selector for the specified target and conditions. */
- (GDTCORStorageEventSelector *)eventSelectorTarget:(GDTCORTarget)target
withConditions:(GDTCORUploadConditions)conditions {
if ((conditions & GDTCORUploadConditionHighPriority) == GDTCORUploadConditionHighPriority) {
return [GDTCORStorageEventSelector eventSelectorForTarget:target];
}
NSMutableSet<NSNumber *> *qosTiers = [[NSMutableSet alloc] init];
if (conditions & GDTCORUploadConditionWifiData) {
[qosTiers addObjectsFromArray:@[
@(GDTCOREventQoSFast), @(GDTCOREventQoSWifiOnly), @(GDTCOREventQosDefault),
@(GDTCOREventQoSTelemetry), @(GDTCOREventQoSUnknown)
]];
}
if (conditions & GDTCORUploadConditionMobileData) {
[qosTiers addObjectsFromArray:@[ @(GDTCOREventQoSFast), @(GDTCOREventQosDefault) ]];
}
return [[GDTCORStorageEventSelector alloc] initWithTarget:target
eventIDs:nil
mappingIDs:nil
qosTiers:qosTiers];
}
- (FBLPromise<GDTCORUploadBatch *> *)batchByAddingMetricsEventToBatch:(GDTCORUploadBatch *)batch
forTarget:(GDTCORTarget)target {
return [self.metricsController getAndResetMetrics]
.thenOn(self.uploaderQueue,
^GDTCORUploadBatch *(GDTCORMetrics *metrics) {
// Save the metrics so they can be re-stored if upload fails.
[self setCurrentMetrics:metrics];
GDTCOREvent *metricsEvent = [GDTCOREvent eventWithMetrics:metrics forTarget:target];
GDTCORUploadBatch *batchWithMetricEvent = [[GDTCORUploadBatch alloc]
initWithBatchID:batch.batchID
events:[batch.events setByAddingObject:metricsEvent]];
return batchWithMetricEvent;
})
.recoverOn(self.uploaderQueue, ^GDTCORUploadBatch *(NSError *error) {
// Return given batch if an error occurs (i.e. no metrics were fetched).
return batch;
});
}
#pragma mark - NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *_Nullable))completionHandler {
if (!completionHandler) {
return;
}
if (response.statusCode == 302 || response.statusCode == 301) {
NSURLRequest *newRequest = [self constructRequestWithURL:request.URL
forTarget:kGDTCORTargetCCT
data:task.originalRequest.HTTPBody];
completionHandler(newRequest);
} else {
completionHandler(request);
}
}
#pragma mark - NSOperation methods
@synthesize executing = _executing;
@synthesize finished = _finished;
- (BOOL)isFinished {
@synchronized(self) {
return _finished;
}
}
- (BOOL)isExecuting {
@synchronized(self) {
return _executing;
}
}
- (BOOL)isAsynchronous {
return YES;
}
- (void)startOperation {
@synchronized(self) {
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
self->_executing = YES;
self->_finished = NO;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
}
- (void)finishOperation {
@synchronized(self) {
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
self->_executing = NO;
self->_finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
}
}
- (void)start {
[self startOperation];
GDTCORLogDebug(@"Upload operation started: %@", self);
[self uploadTarget:self.target withConditions:self.conditions];
}
- (void)cancel {
@synchronized(self) {
[super cancel];
// If the operation hasn't been started we can set `isFinished = YES` straight away.
if (!_executing) {
_executing = NO;
_finished = YES;
}
}
}
#pragma mark - Force Category Linking
extern void GDTCCTInclude_GDTCOREvent_GDTCCTSupport_Category(void);
extern void GDTCCTInclude_GDTCOREvent_GDTMetricsSupport_Category(void);
extern void GDTCCTInclude_GDTCORLogSourceMetrics_Internal_Category(void);
/// Does nothing when called, and not meant to be called.
///
/// This method forces the linker to include categories even if
/// users do not include the '-ObjC' linker flag in their project.
+ (void)noop {
GDTCCTInclude_GDTCOREvent_GDTCCTSupport_Category();
GDTCCTInclude_GDTCOREvent_GDTMetricsSupport_Category();
GDTCCTInclude_GDTCORLogSourceMetrics_Internal_Category();
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,215 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploader.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREndpoints.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTUploadOperation.h"
NS_ASSUME_NONNULL_BEGIN
@interface GDTCCTUploader () <NSURLSessionDelegate, GDTCCTUploadMetadataProvider>
@property(nonatomic, readonly) NSOperationQueue *uploadOperationQueue;
@property(nonatomic, readonly) dispatch_queue_t uploadQueue;
@property(nonatomic, readonly)
NSMutableDictionary<NSNumber * /*GDTCORTarget*/, GDTCORClock *> *nextUploadTimeByTarget;
@end
@implementation GDTCCTUploader
static NSURL *_testServerURL = nil;
+ (void)load {
GDTCCTUploader *uploader = [GDTCCTUploader sharedInstance];
#if GDT_TEST
[[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetTest];
#endif // GDT_TEST
[[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetCCT];
[[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetFLL];
[[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetCSH];
[[GDTCORRegistrar sharedInstance] registerUploader:uploader target:kGDTCORTargetINT];
}
+ (instancetype)sharedInstance {
static GDTCCTUploader *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[GDTCCTUploader alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_uploadQueue = dispatch_queue_create("com.google.GDTCCTUploader", DISPATCH_QUEUE_SERIAL);
_uploadOperationQueue = [[NSOperationQueue alloc] init];
_uploadOperationQueue.maxConcurrentOperationCount = 1;
_nextUploadTimeByTarget = [[NSMutableDictionary alloc] init];
}
return self;
}
- (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions)conditions {
// Current GDTCCTUploader expected behaviour:
// 1. Accept multiple upload request
// 2. Verify if there are events eligible for upload and start upload for the first suitable
// target
// 3. Ignore other requests while an upload is in-progress.
// TODO: Revisit expected behaviour.
// Potentially better option:
// 1. Accept and enqueue all upload requests
// 2. Notify the client of upload stages
// 3. Allow the client cancelling upload requests as needed.
id<GDTCORStoragePromiseProtocol> storage = GDTCORStoragePromiseInstanceForTarget(target);
if (storage == nil) {
GDTCORLogError(GDTCORMCEGeneralError,
@"Failed to upload target: %ld - could not find corresponding storage instance.",
(long)target);
return;
}
id<GDTCORMetricsControllerProtocol> metricsController =
GDTCORMetricsControllerInstanceForTarget(target);
GDTCCTUploadOperation *uploadOperation =
[[GDTCCTUploadOperation alloc] initWithTarget:target
conditions:conditions
uploadURL:[[self class] serverURLForTarget:target]
queue:self.uploadQueue
storage:storage
metadataProvider:self
metricsController:metricsController];
GDTCORLogDebug(@"Upload operation created: %@, target: %@", uploadOperation, @(target));
__weak __auto_type weakSelf = self;
__weak GDTCCTUploadOperation *weakOperation = uploadOperation;
uploadOperation.completionBlock = ^{
__auto_type strongSelf = weakSelf;
GDTCCTUploadOperation *strongOperation = weakOperation;
if (strongSelf == nil || strongOperation == nil) {
GDTCORLogDebug(@"Internal inconsistency: GDTCCTUploader was deallocated during upload.", nil);
return;
}
GDTCORLogDebug(@"Upload operation finished: %@, uploadAttempted: %@", strongOperation,
@(strongOperation.uploadAttempted));
if (strongOperation.uploadAttempted) {
// Ignore all upload requests received when the upload was in progress.
[strongSelf.uploadOperationQueue cancelAllOperations];
}
};
[self.uploadOperationQueue addOperation:uploadOperation];
GDTCORLogDebug(@"Upload operation scheduled: %@, operation count: %@", uploadOperation,
@(self.uploadOperationQueue.operationCount));
}
#pragma mark - URLs
+ (void)setTestServerURL:(NSURL *_Nullable)serverURL {
_testServerURL = serverURL;
}
+ (NSURL *_Nullable)testServerURL {
return _testServerURL;
}
+ (nullable NSURL *)serverURLForTarget:(GDTCORTarget)target {
#if GDT_TEST
if (_testServerURL) {
return _testServerURL;
}
#endif // GDT_TEST
return [GDTCOREndpoints uploadURLForTarget:target];
}
- (NSString *)FLLAndCSHAndINTAPIKey {
static NSString *defaultServerKey;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// These strings should be interleaved to construct the real key.
const char *p1 = "AzSBG0honD6A-PxV5nBc";
const char *p2 = "Iay44Iwtu2vV0AOrz1C";
const char defaultKey[40] = {p1[0], p2[0], p1[1], p2[1], p1[2], p2[2], p1[3], p2[3],
p1[4], p2[4], p1[5], p2[5], p1[6], p2[6], p1[7], p2[7],
p1[8], p2[8], p1[9], p2[9], p1[10], p2[10], p1[11], p2[11],
p1[12], p2[12], p1[13], p2[13], p1[14], p2[14], p1[15], p2[15],
p1[16], p2[16], p1[17], p2[17], p1[18], p2[18], p1[19], '\0'};
defaultServerKey = [NSString stringWithUTF8String:defaultKey];
});
return defaultServerKey;
}
#pragma mark - GDTCCTUploadMetadataProvider
- (nullable GDTCORClock *)nextUploadTimeForTarget:(GDTCORTarget)target {
@synchronized(self.nextUploadTimeByTarget) {
return self.nextUploadTimeByTarget[@(target)];
}
}
- (void)setNextUploadTime:(nullable GDTCORClock *)time forTarget:(GDTCORTarget)target {
@synchronized(self.nextUploadTimeByTarget) {
self.nextUploadTimeByTarget[@(target)] = time;
}
}
- (nullable NSString *)APIKeyForTarget:(GDTCORTarget)target {
if (target == kGDTCORTargetFLL || target == kGDTCORTargetCSH) {
return [self FLLAndCSHAndINTAPIKey];
}
if (target == kGDTCORTargetINT) {
return [self FLLAndCSHAndINTAPIKey];
}
return nil;
}
#if GDT_TEST
- (BOOL)waitForUploadFinishedWithTimeout:(NSTimeInterval)timeout {
NSDate *expirationDate = [NSDate dateWithTimeIntervalSinceNow:timeout];
while ([expirationDate compare:[NSDate date]] == NSOrderedDescending) {
if (self.uploadOperationQueue.operationCount == 0) {
return YES;
} else {
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}
}
GDTCORLogDebug(@"Uploader wait for finish timeout exceeded. Operations still in queue: %@",
self.uploadOperationQueue.operations);
return NO;
}
#endif // GDT_TEST
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,244 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleDataTransport/GDTCCTLibrary/Public/GDTCOREvent+GDTCCTSupport.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
NSString *const GDTCCTNeedsNetworkConnectionInfo = @"needs_network_connection_info";
NSString *const GDTCCTNetworkConnectionInfo = @"network_connection_info";
NSString *const GDTCCTEventCodeInfo = @"event_code_info";
@implementation GDTCOREvent (GDTCCTSupport)
- (void)setNeedsNetworkConnectionInfoPopulated:(BOOL)needsNetworkConnectionInfoPopulated {
if (!needsNetworkConnectionInfoPopulated) {
if (!self.customBytes) {
return;
}
// Make sure we don't destroy the eventCode data, if any is present.
@try {
NSError *error;
NSMutableDictionary *bytesDict =
[[NSJSONSerialization JSONObjectWithData:self.customBytes options:0
error:&error] mutableCopy];
if (error) {
GDTCORLogDebug(@"Error when setting an event's event_code: %@", error);
return;
}
NSNumber *eventCode = bytesDict[GDTCCTEventCodeInfo];
if (eventCode != nil) {
self.customBytes =
[NSJSONSerialization dataWithJSONObject:@{GDTCCTEventCodeInfo : eventCode}
options:0
error:&error];
}
} @catch (NSException *exception) {
GDTCORLogDebug(@"Error when setting the event for needs_network_connection_info: %@",
exception);
}
} else {
@try {
NSError *error;
NSMutableDictionary *bytesDict;
if (self.customBytes) {
bytesDict = [[NSJSONSerialization JSONObjectWithData:self.customBytes
options:0
error:&error] mutableCopy];
if (error) {
GDTCORLogDebug(@"Error when setting an even'ts event_code: %@", error);
return;
}
} else {
bytesDict = [[NSMutableDictionary alloc] init];
}
[bytesDict setObject:@YES forKey:GDTCCTNeedsNetworkConnectionInfo];
self.customBytes = [NSJSONSerialization dataWithJSONObject:bytesDict options:0 error:&error];
} @catch (NSException *exception) {
GDTCORLogDebug(@"Error when setting the event for needs_network_connection_info: %@",
exception);
}
}
}
- (BOOL)needsNetworkConnectionInfoPopulated {
if (self.customBytes) {
@try {
NSError *error;
NSDictionary *bytesDict = [NSJSONSerialization JSONObjectWithData:self.customBytes
options:0
error:&error];
return bytesDict && !error && [bytesDict[GDTCCTNeedsNetworkConnectionInfo] boolValue];
} @catch (NSException *exception) {
GDTCORLogDebug(@"Error when checking the event for needs_network_connection_info: %@",
exception);
}
}
return NO;
}
- (void)setNetworkConnectionInfoData:(NSData *)networkConnectionInfoData {
@try {
NSError *error;
NSString *dataString = [networkConnectionInfoData base64EncodedStringWithOptions:0];
if (dataString != nil) {
NSMutableDictionary *bytesDict;
if (self.customBytes) {
bytesDict = [[NSJSONSerialization JSONObjectWithData:self.customBytes
options:0
error:&error] mutableCopy];
if (error) {
GDTCORLogDebug(@"Error when setting an even'ts event_code: %@", error);
return;
}
} else {
bytesDict = [[NSMutableDictionary alloc] init];
}
[bytesDict setObject:dataString forKey:GDTCCTNetworkConnectionInfo];
self.customBytes = [NSJSONSerialization dataWithJSONObject:bytesDict options:0 error:&error];
if (error) {
self.customBytes = nil;
GDTCORLogDebug(@"Error when setting an event's network_connection_info: %@", error);
}
}
} @catch (NSException *exception) {
GDTCORLogDebug(@"Error when setting an event's network_connection_info: %@", exception);
}
}
- (nullable NSData *)networkConnectionInfoData {
if (self.customBytes) {
@try {
NSError *error;
NSDictionary *bytesDict = [NSJSONSerialization JSONObjectWithData:self.customBytes
options:0
error:&error];
NSString *base64Data = bytesDict[GDTCCTNetworkConnectionInfo];
if (base64Data == nil) {
return nil;
}
NSData *networkConnectionInfoData = [[NSData alloc] initWithBase64EncodedString:base64Data
options:0];
if (error) {
GDTCORLogDebug(@"Error when getting an event's network_connection_info: %@", error);
return nil;
} else {
return networkConnectionInfoData;
}
} @catch (NSException *exception) {
GDTCORLogDebug(@"Error when getting an event's network_connection_info: %@", exception);
}
}
return nil;
}
- (NSNumber *)eventCode {
if (self.customBytes) {
@try {
NSError *error;
NSDictionary *bytesDict = [NSJSONSerialization JSONObjectWithData:self.customBytes
options:0
error:&error];
NSString *eventCodeString = bytesDict[GDTCCTEventCodeInfo];
if (!eventCodeString) {
return nil;
}
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterDecimalStyle;
NSNumber *eventCode = [formatter numberFromString:eventCodeString];
if (error) {
GDTCORLogDebug(@"Error when getting an event's network_connection_info: %@", error);
return nil;
} else {
return eventCode;
}
} @catch (NSException *exception) {
GDTCORLogDebug(@"Error when getting an event's event_code: %@", exception);
}
}
return nil;
}
- (void)setEventCode:(NSNumber *)eventCode {
if (eventCode == nil) {
if (!self.customBytes) {
return;
}
NSError *error;
NSMutableDictionary *bytesDict = [[NSJSONSerialization JSONObjectWithData:self.customBytes
options:0
error:&error] mutableCopy];
if (error) {
GDTCORLogDebug(@"Error when setting an event's event_code: %@", error);
return;
}
[bytesDict removeObjectForKey:GDTCCTEventCodeInfo];
self.customBytes = [NSJSONSerialization dataWithJSONObject:bytesDict options:0 error:&error];
if (error) {
self.customBytes = nil;
GDTCORLogDebug(@"Error when setting an event's event_code: %@", error);
return;
}
return;
}
@try {
NSMutableDictionary *bytesDict;
NSError *error;
if (self.customBytes) {
bytesDict = [[NSJSONSerialization JSONObjectWithData:self.customBytes options:0
error:&error] mutableCopy];
if (error) {
GDTCORLogDebug(@"Error when setting an event's event_code: %@", error);
return;
}
} else {
bytesDict = [[NSMutableDictionary alloc] init];
}
NSString *eventCodeString = [eventCode stringValue];
if (eventCodeString == nil) {
return;
}
[bytesDict setObject:eventCodeString forKey:GDTCCTEventCodeInfo];
self.customBytes = [NSJSONSerialization dataWithJSONObject:bytesDict options:0 error:&error];
if (error) {
self.customBytes = nil;
GDTCORLogDebug(@"Error when setting an event's network_connection_info: %@", error);
return;
}
} @catch (NSException *exception) {
GDTCORLogDebug(@"Error when getting an event's network_connection_info: %@", exception);
}
}
@end
/// Stub used to force the linker to include the categories in this file.
void GDTCCTInclude_GDTCOREvent_GDTCCTSupport_Category(void) {
}

View File

@ -0,0 +1,37 @@
// Copyright 2022 Google LLC
//
// 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCOREvent+GDTMetricsSupport.h"
#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCORMetrics+GDTCCTSupport.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h"
/// The mapping ID that represents the `LogSource` for GDT metrics.
static NSString *const kMetricEventMappingID = @"1710";
@implementation GDTCOREvent (GDTMetricsSupport)
+ (GDTCOREvent *)eventWithMetrics:(GDTCORMetrics *)metrics forTarget:(GDTCORTarget)target {
GDTCOREvent *metricsEvent = [[GDTCOREvent alloc] initWithMappingID:kMetricEventMappingID
target:target];
metricsEvent.dataObject = metrics;
return metricsEvent;
}
@end
/// Stub used to force the linker to include the categories in this file.
void GDTCCTInclude_GDTCOREvent_GDTMetricsSupport_Category(void) {
}

View File

@ -0,0 +1,211 @@
// Copyright 2022 Google LLC
//
// 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 "GoogleDataTransport/GDTCCTLibrary/Private/GDTCORMetrics+GDTCCTSupport.h"
#import <nanopb/pb.h>
#import <nanopb/pb_decode.h>
#import <nanopb/pb_encode.h>
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h"
#import "GoogleDataTransport/GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h"
#import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.h"
typedef NSDictionary<NSNumber *, NSNumber *> GDTCORDroppedEventCounter;
@interface GDTCORLogSourceMetrics (Internal)
/// A dictionary of log sources that map to counters that reflect the number of events dropped for a
/// given set of reasons (``GDTCOREventDropReason``).
@property(nonatomic, readonly)
NSDictionary<NSString *, GDTCORDroppedEventCounter *> *droppedEventCounterByLogSource;
@end
@implementation GDTCORMetrics (GDTCCTSupport)
- (NSData *)transportBytes {
// Create and populate proto.
gdt_client_metrics_ClientMetrics clientMetricsProto =
gdt_client_metrics_ClientMetrics_init_default;
clientMetricsProto.window =
GDTCCTConstructTimeWindow(self.collectionStartDate, self.collectionEndDate);
clientMetricsProto.log_source_metrics = GDTCCTConstructLogSourceMetrics(self.logSourceMetrics);
clientMetricsProto.log_source_metrics_count =
GDTCCTGetLogSourceMetricsCount(self.logSourceMetrics);
clientMetricsProto.global_metrics =
GDTCCTConstructGlobalMetrics(self.currentCacheSize, self.maxCacheSize);
clientMetricsProto.app_namespace = GDTCCTEncodeString(self.bundleID);
// Encode proto into a data buffer.
pb_ostream_t sizeStream = PB_OSTREAM_SIZING;
// - Encode 1 time to determine the expected size of the buffer.
if (!pb_encode(&sizeStream, gdt_client_metrics_ClientMetrics_fields, &clientMetricsProto)) {
GDTCORLogError(GDTCORMCETransportBytesError, @"Error in nanopb encoding for size: %s",
PB_GET_ERROR(&sizeStream));
}
// - Encode a 2nd time to actually copy the proto's bytes into the buffer.
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, gdt_client_metrics_ClientMetrics_fields, &clientMetricsProto)) {
GDTCORLogError(GDTCORMCETransportBytesError, @"Error in nanopb encoding for size: %s",
PB_GET_ERROR(&ostream));
}
CFDataSetLength(dataRef, ostream.bytes_written);
// Release the allocated proto.
pb_release(gdt_client_metrics_ClientMetrics_fields, &clientMetricsProto);
return CFBridgingRelease(dataRef);
}
/// Constructs and returns a ``gdt_client_metrics_LogSourceMetrics`` from the given log source
/// metrics.
/// @param logSourceMetrics The given log source metrics.
gdt_client_metrics_LogSourceMetrics *GDTCCTConstructLogSourceMetrics(
GDTCORLogSourceMetrics *logSourceMetrics) {
// The metrics proto is a repeating field where each element represents the
// dropped event data for a log source (mapping ID).
NSUInteger logMetricsCount = logSourceMetrics.droppedEventCounterByLogSource.count;
gdt_client_metrics_LogSourceMetrics *repeatedLogSourceMetrics =
calloc(logMetricsCount, sizeof(gdt_client_metrics_LogSourceMetrics));
// Each log source (mapping ID) has a corresponding dropped event counter.
// Enumerate over the dictionary of log source and, for each log source,
// (mapping ID) create a proto representation of the number of events dropped
// for each given reason.
__block NSUInteger logSourceIndex = 0;
[logSourceMetrics.droppedEventCounterByLogSource
enumerateKeysAndObjectsUsingBlock:^(NSString *logSource,
GDTCORDroppedEventCounter *eventCounterForLogSource,
BOOL *__unused _) {
// Create the log source proto for the given mapping ID. It contains a
// repeating field to encapsulate the number of events dropped for each
// given drop reason.
__block gdt_client_metrics_LogSourceMetrics logSourceMetrics =
gdt_client_metrics_LogSourceMetrics_init_zero;
logSourceMetrics.log_source = GDTCCTEncodeString(logSource);
logSourceMetrics.log_event_dropped_count = (pb_size_t)eventCounterForLogSource.count;
logSourceMetrics.log_event_dropped =
calloc(eventCounterForLogSource.count, sizeof(gdt_client_metrics_LogEventDropped));
// Each dropped event counter counts the number of events dropped for
// each drop reason. Enumerate over all of these counters to populate
// the log source proto's repeating field of event drop data.
__block NSUInteger eventCounterIndex = 0;
[eventCounterForLogSource
enumerateKeysAndObjectsUsingBlock:^(NSNumber *eventDropReason,
NSNumber *droppedEventCount, BOOL *__unused _) {
gdt_client_metrics_LogEventDropped droppedEvents =
gdt_client_metrics_LogEventDropped_init_zero;
droppedEvents.events_dropped_count = droppedEventCount.integerValue;
droppedEvents.reason =
GDTCCTConvertEventDropReasonToProtoReason(eventDropReason.integerValue);
// Append the dropped events proto to the repeated field and
// increment the index used for appending.
logSourceMetrics.log_event_dropped[eventCounterIndex] = droppedEvents;
eventCounterIndex += 1;
}];
// Append the metrics for the given log source (mappingID) to the
// repeated field and increment the index used for appending.
repeatedLogSourceMetrics[logSourceIndex] = logSourceMetrics;
logSourceIndex += 1;
}];
return repeatedLogSourceMetrics;
}
/// Returns the count of log sources that have event drop metrics.
/// @param logSourceMetrics The given log source metrics.
pb_size_t GDTCCTGetLogSourceMetricsCount(GDTCORLogSourceMetrics *logSourceMetrics) {
return (pb_size_t)logSourceMetrics.droppedEventCounterByLogSource.count;
}
/// Constructs and returns a ``gdt_client_metrics_TimeWindow`` proto from the given parameters.
/// @param collectionStartDate The start of the time window.
/// @param collectionEndDate The end of the time window.
gdt_client_metrics_TimeWindow GDTCCTConstructTimeWindow(NSDate *collectionStartDate,
NSDate *collectionEndDate) {
gdt_client_metrics_TimeWindow timeWindow = gdt_client_metrics_TimeWindow_init_zero;
// `- [NSDate timeIntervalSince1970]` returns a time interval in seconds so
// multiply by 1000 to convert to milliseconds.
timeWindow.start_ms = (int64_t)collectionStartDate.timeIntervalSince1970 * 1000;
timeWindow.end_ms = (int64_t)collectionEndDate.timeIntervalSince1970 * 1000;
return timeWindow;
}
/// Constructs and returns a ``gdt_client_metrics_GlobalMetrics`` proto from the given parameters.
/// @param currentCacheSize The current cache size.
/// @param maxCacheSize The max cache size.
gdt_client_metrics_GlobalMetrics GDTCCTConstructGlobalMetrics(uint64_t currentCacheSize,
uint64_t maxCacheSize) {
gdt_client_metrics_StorageMetrics storageMetrics = gdt_client_metrics_StorageMetrics_init_zero;
storageMetrics.current_cache_size_bytes = currentCacheSize;
storageMetrics.max_cache_size_bytes = maxCacheSize;
gdt_client_metrics_GlobalMetrics globalMetrics = gdt_client_metrics_GlobalMetrics_init_zero;
globalMetrics.storage_metrics = storageMetrics;
return globalMetrics;
}
/// Returns the corresponding ``gdt_client_metrics_LogEventDropped_Reason`` for the given
/// ``GDTCOREventDropReason``.
///
/// To represent ``GDTCOREventDropReason`` in a proto, the reason must be mapped to a
/// ``gdt_client_metrics_LogEventDropped_Reason``.
///
/// @param reason The ``GDTCOREventDropReason`` to represent in a proto.
gdt_client_metrics_LogEventDropped_Reason GDTCCTConvertEventDropReasonToProtoReason(
GDTCOREventDropReason reason) {
switch (reason) {
case GDTCOREventDropReasonUnknown:
return gdt_client_metrics_LogEventDropped_Reason_REASON_UNKNOWN;
case GDTCOREventDropReasonMessageTooOld:
return gdt_client_metrics_LogEventDropped_Reason_MESSAGE_TOO_OLD;
case GDTCOREventDropReasonStorageFull:
return gdt_client_metrics_LogEventDropped_Reason_CACHE_FULL;
case GDTCOREventDropReasonPayloadTooBig:
return gdt_client_metrics_LogEventDropped_Reason_PAYLOAD_TOO_BIG;
case GDTCOREventDropReasonMaxRetriesReached:
return gdt_client_metrics_LogEventDropped_Reason_MAX_RETRIES_REACHED;
case GDTCOREventDropReasonInvalidPayload:
// The below typo (`PAYLOD`) is currently checked in to g3.
return gdt_client_metrics_LogEventDropped_Reason_INVALID_PAYLOD;
case GDTCOREventDropReasonServerError:
return gdt_client_metrics_LogEventDropped_Reason_SERVER_ERROR;
}
}
@end
/// Stub used to force the linker to include the categories in this file.
void GDTCCTInclude_GDTCORLogSourceMetrics_Internal_Category(void) {
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2020 Google LLC
*
* 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** A class with methods to help with gzipped data. */
@interface GDTCCTCompressionHelper : NSObject
/** Compresses the given data and returns a new data object.
*
* @note Reduced version from GULNSData+zlib.m of GoogleUtilities.
* @return Compressed data, or nil if there was an error.
*/
+ (nullable NSData *)gzippedData:(NSData *)data;
/** Returns YES if the data looks like it was gzip compressed by checking for the gzip magic number.
*
* @note: From https://en.wikipedia.org/wiki/Gzip, gzip's magic number is 1f 8b.
* @return YES if the data appears gzipped, NO otherwise.
*/
+ (BOOL)isGzipped:(NSData *)data;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,144 @@
/*
* 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h"
#import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h"
#import "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.h"
NS_ASSUME_NONNULL_BEGIN
#pragma mark - General purpose encoders
/** Converts an NSString* to a pb_bytes_array_t*.
*
* @note calloc is called in this method. Ensure that pb_release is called on this or the parent.
*
* @param string The string to convert.
* @return A newly allocated array of bytes representing the UTF8 encoding of the string.
*/
pb_bytes_array_t *GDTCCTEncodeString(NSString *string);
/** Converts an NSData to a pb_bytes_array_t*.
*
* @note calloc is called in this method. Ensure that pb_release is called on this or the parent.
*
* @param data The data to convert.
* @return A newly allocated array of bytes with [data bytes] copied into it.
*/
pb_bytes_array_t *GDTCCTEncodeData(NSData *data);
#pragma mark - CCT object constructors
/** Encodes a batched log request.
*
* @note Ensure that pb_release is called on the batchedLogRequest param.
*
* @param batchedLogRequest A pointer to the log batch to encode to bytes.
* @return An NSData object representing the bytes of the log request batch.
*/
FOUNDATION_EXPORT
NSData *GDTCCTEncodeBatchedLogRequest(gdt_cct_BatchedLogRequest *batchedLogRequest);
/** Constructs a gdt_cct_BatchedLogRequest given sets of events segemented by mapping ID.
*
* @note calloc is called in this method. Ensure that pb_release is called on this or the parent.
*
* @param logMappingIDToLogSet A map of mapping IDs to sets of events to convert into a batch.
* @return A newly created gdt_cct_BatchedLogRequest.
*/
FOUNDATION_EXPORT
gdt_cct_BatchedLogRequest GDTCCTConstructBatchedLogRequest(
NSDictionary<NSString *, NSSet<GDTCOREvent *> *> *logMappingIDToLogSet);
/** Constructs a log request given a log source and a set of events.
*
* @note calloc is called in this method. Ensure that pb_release is called on this or the parent.
* @param logSource The CCT log source to put into the log request.
* @param logSet The set of events to send in this log request.
*/
FOUNDATION_EXPORT
gdt_cct_LogRequest GDTCCTConstructLogRequest(int32_t logSource, NSSet<GDTCOREvent *> *logSet);
/** Constructs a gdt_cct_LogEvent given a GDTCOREvent*.
*
* @param event The GDTCOREvent to convert.
* @return The new gdt_cct_LogEvent object.
*/
FOUNDATION_EXPORT
gdt_cct_LogEvent GDTCCTConstructLogEvent(GDTCOREvent *event);
/** Constructs a `gdt_cct_ComplianceData` given a `GDTCORProductData` instance.
*
* @param productData The product data to convert to compliance data.
*/
FOUNDATION_EXPORT
gdt_cct_ComplianceData GDTCCTConstructComplianceData(GDTCORProductData *productData);
/** Constructs a gdt_cct_ClientInfo representing the client device.
*
* @return The new gdt_cct_ClientInfo object.
*/
FOUNDATION_EXPORT
gdt_cct_ClientInfo GDTCCTConstructClientInfo(void);
/** Constructs a gdt_cct_IosClientInfo representing the client device.
*
* @return The new gdt_cct_IosClientInfo object.
*/
FOUNDATION_EXPORT
gdt_cct_IosClientInfo GDTCCTConstructiOSClientInfo(void);
/** Constructs a gdt_cct_MacClientInfo representing the client device.
*
* @return The new gdt_cct_MacClientInfo object.
*/
FOUNDATION_EXPORT
gdt_cct_MacClientInfo GDTCCTConstructMacClientInfo(void);
/** Constructs the data of a gdt_cct_NetworkConnectionInfo representing the client nework connection
* information.
*
* @return The data of a gdt_cct_NetworkConnectionInfo object.
*/
FOUNDATION_EXPORT
NSData *GDTCCTConstructNetworkConnectionInfoData(void);
/** Return a gdt_cct_NetworkConnectionInfo_MobileSubtype representing the client
*
* @return The gdt_cct_NetworkConnectionInfo_MobileSubtype.
*/
FOUNDATION_EXPORT
gdt_cct_NetworkConnectionInfo_MobileSubtype GDTCCTNetworkConnectionInfoNetworkMobileSubtype(void);
#pragma mark - CCT object decoders
/** Decodes a gdt_cct_LogResponse given proto bytes.
*
* @note calloc is called in this method. Ensure that pb_release is called on the return value.
*
* @param data The proto bytes of the gdt_cct_LogResponse.
* @param error An error that will be populated if something went wrong during decoding.
* @return A newly allocated gdt_cct_LogResponse from the data, if the bytes decoded properly.
*/
FOUNDATION_EXPORT
gdt_cct_LogResponse GDTCCTDecodeLogResponse(NSData *data, NSError **error);
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,29 @@
// Copyright 2024 Google LLC
//
// 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** The class represents HTTP response received from `NSURLSession`. */
@interface GDTCCTURLSessionDataResponse : NSObject
@property(nonatomic, readonly) NSHTTPURLResponse *HTTPResponse;
@property(nonatomic, nullable, readonly) NSData *HTTPBody;
- (instancetype)initWithResponse:(NSHTTPURLResponse *)response HTTPBody:(nullable NSData *)body;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,78 @@
/*
* Copyright 2020 Google LLC
*
* 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORUploader.h"
@protocol GDTCORStoragePromiseProtocol;
@protocol GDTCORMetricsControllerProtocol;
NS_ASSUME_NONNULL_BEGIN
/// The protocol defines methods to retrieve/update data shared between different upload operations.
@protocol GDTCCTUploadMetadataProvider <NSObject>
/** Returns a GDTCORClock object representing time after which a next upload attempt is allowed for
* the specified target. Upload is allowed now if `nil`. */
- (nullable GDTCORClock *)nextUploadTimeForTarget:(GDTCORTarget)target;
/** Stores or resets time after which a next upload attempt is allowed for the specified target. */
- (void)setNextUploadTime:(nullable GDTCORClock *)time forTarget:(GDTCORTarget)target;
/** Returns an API key for the specified target. */
- (nullable NSString *)APIKeyForTarget:(GDTCORTarget)target;
@end
/** Class capable of uploading events to the CCT backend. */
@interface GDTCCTUploadOperation : NSOperation
- (instancetype)init NS_UNAVAILABLE;
/// Designated initializer.
/// @param target The events target to upload.
/// @param conditions A set of upload conditions. The conditions affect the set of events to be
/// uploaded, e.g. events with some QoS are not uploaded on a cellular network, etc.
/// @param uploadURL The backend URL to upload the events.
/// @param queue A queue to dispatch async upload steps.
/// @param storage A storage object to fetch events for upload.
/// @param metadataProvider An object to retrieve/update data shared between upload operations.
/// @param metricsController The metrics controller corresponding to the given target. If the given
/// target does not support metrics controller, `nil` should be passed.
/// @return An individual operation that can be added to an operation queue.
- (instancetype)initWithTarget:(GDTCORTarget)target
conditions:(GDTCORUploadConditions)conditions
uploadURL:(NSURL *)uploadURL
queue:(dispatch_queue_t)queue
storage:(id<GDTCORStoragePromiseProtocol>)storage
metadataProvider:(id<GDTCCTUploadMetadataProvider>)metadataProvider
metricsController:(nullable id<GDTCORMetricsControllerProtocol>)metricsController
NS_DESIGNATED_INITIALIZER;
/** YES if a batch upload attempt was performed. NO otherwise. If NO for the finished operation,
* then there were no events suitable for upload. */
@property(nonatomic, readonly) BOOL uploadAttempted;
/** The queue on which all CCT uploading will occur. */
@property(nonatomic, readonly) dispatch_queue_t uploaderQueue;
/** The current upload task. */
@property(nullable, nonatomic, readonly) NSURLSessionUploadTask *currentTask;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,45 @@
/*
* 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORUploader.h"
NS_ASSUME_NONNULL_BEGIN
/** Class capable of uploading events to the CCT backend. */
@interface GDTCCTUploader : NSObject <GDTCORUploader>
/** Creates and/or returns the singleton instance of this class.
*
* @return The singleton instance of this class.
*/
+ (instancetype)sharedInstance;
#if GDT_TEST
/** An upload URL used across all targets. For testing only. */
@property(class, nullable, nonatomic) NSURL *testServerURL;
/** Spins runloop until upload finishes or timeout.
* @return YES if upload finishes, NO in the case of timeout.
*/
- (BOOL)waitForUploadFinishedWithTimeout:(NSTimeInterval)timeout;
#endif // GDT_TEST
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,34 @@
// Copyright 2022 Google LLC
//
// 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h"
@class GDTCORMetrics;
NS_ASSUME_NONNULL_BEGIN
@interface GDTCOREvent (GDTMetricsSupport)
/// Creates and returns an event for the given target with the given metrics.
/// @param metrics The metrics to set at the event's data.
/// @param target The backend target that the event corresponds to.
+ (GDTCOREvent *)eventWithMetrics:(GDTCORMetrics *)metrics forTarget:(GDTCORTarget)target;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,25 @@
// Copyright 2022 Google LLC
//
// 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventDataObject.h"
NS_ASSUME_NONNULL_BEGIN
@interface GDTCORMetrics (GDTCCTSupport) <GDTCOREventDataObject>
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,138 @@
/*
* 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.
*/
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.3.9.9 */
#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/cct.nanopb.h"
/* @@protoc_insertion_point(includes) */
#if PB_PROTO_HEADER_VERSION != 30
#error Regenerate this file with the current version of nanopb generator.
#endif
const gdt_cct_NetworkConnectionInfo_NetworkType gdt_cct_NetworkConnectionInfo_network_type_default = gdt_cct_NetworkConnectionInfo_NetworkType_NONE;
const gdt_cct_NetworkConnectionInfo_MobileSubtype gdt_cct_NetworkConnectionInfo_mobile_subtype_default = gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE;
const gdt_cct_QosTierConfiguration_QosTier gdt_cct_LogRequest_qos_tier_default = gdt_cct_QosTierConfiguration_QosTier_DEFAULT;
const int32_t gdt_cct_QosTierConfiguration_log_source_default = 0;
const pb_field_t gdt_cct_LogEvent_fields[8] = {
PB_FIELD( 1, INT64 , OPTIONAL, STATIC , FIRST, gdt_cct_LogEvent, event_time_ms, event_time_ms, 0),
PB_FIELD( 6, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_LogEvent, source_extension, event_time_ms, 0),
PB_FIELD( 11, INT32 , OPTIONAL, STATIC , OTHER, gdt_cct_LogEvent, event_code, source_extension, 0),
PB_FIELD( 15, SINT64 , OPTIONAL, STATIC , OTHER, gdt_cct_LogEvent, timezone_offset_seconds, event_code, 0),
PB_FIELD( 17, INT64 , OPTIONAL, STATIC , OTHER, gdt_cct_LogEvent, event_uptime_ms, timezone_offset_seconds, 0),
PB_FIELD( 23, MESSAGE , OPTIONAL, STATIC , OTHER, gdt_cct_LogEvent, network_connection_info, event_uptime_ms, &gdt_cct_NetworkConnectionInfo_fields),
PB_FIELD( 33, MESSAGE , OPTIONAL, STATIC , OTHER, gdt_cct_LogEvent, compliance_data, network_connection_info, &gdt_cct_ComplianceData_fields),
PB_LAST_FIELD
};
const pb_field_t gdt_cct_NetworkConnectionInfo_fields[3] = {
PB_FIELD( 1, ENUM , OPTIONAL, STATIC , FIRST, gdt_cct_NetworkConnectionInfo, network_type, network_type, &gdt_cct_NetworkConnectionInfo_network_type_default),
PB_FIELD( 2, UENUM , OPTIONAL, STATIC , OTHER, gdt_cct_NetworkConnectionInfo, mobile_subtype, network_type, &gdt_cct_NetworkConnectionInfo_mobile_subtype_default),
PB_LAST_FIELD
};
const pb_field_t gdt_cct_MacClientInfo_fields[5] = {
PB_FIELD( 1, BYTES , OPTIONAL, POINTER , FIRST, gdt_cct_MacClientInfo, os_major_version, os_major_version, 0),
PB_FIELD( 2, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_MacClientInfo, os_full_version, os_major_version, 0),
PB_FIELD( 3, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_MacClientInfo, application_build, os_full_version, 0),
PB_FIELD( 7, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_MacClientInfo, application_bundle_id, application_build, 0),
PB_LAST_FIELD
};
const pb_field_t gdt_cct_IosClientInfo_fields[8] = {
PB_FIELD( 3, BYTES , OPTIONAL, POINTER , FIRST, gdt_cct_IosClientInfo, os_major_version, os_major_version, 0),
PB_FIELD( 4, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, os_full_version, os_major_version, 0),
PB_FIELD( 5, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, application_build, os_full_version, 0),
PB_FIELD( 6, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, country, application_build, 0),
PB_FIELD( 7, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, model, country, 0),
PB_FIELD( 8, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, language_code, model, 0),
PB_FIELD( 11, BYTES , OPTIONAL, POINTER , OTHER, gdt_cct_IosClientInfo, application_bundle_id, language_code, 0),
PB_LAST_FIELD
};
const pb_field_t gdt_cct_ClientInfo_fields[4] = {
PB_FIELD( 1, UENUM , OPTIONAL, STATIC , FIRST, gdt_cct_ClientInfo, client_type, client_type, 0),
PB_FIELD( 4, MESSAGE , OPTIONAL, STATIC , OTHER, gdt_cct_ClientInfo, ios_client_info, client_type, &gdt_cct_IosClientInfo_fields),
PB_FIELD( 13, MESSAGE , OPTIONAL, STATIC , OTHER, gdt_cct_ClientInfo, mac_client_info, ios_client_info, &gdt_cct_MacClientInfo_fields),
PB_LAST_FIELD
};
const pb_field_t gdt_cct_BatchedLogRequest_fields[2] = {
PB_FIELD( 1, MESSAGE , REPEATED, POINTER , FIRST, gdt_cct_BatchedLogRequest, log_request, log_request, &gdt_cct_LogRequest_fields),
PB_LAST_FIELD
};
const pb_field_t gdt_cct_LogRequest_fields[7] = {
PB_FIELD( 1, MESSAGE , OPTIONAL, STATIC , FIRST, gdt_cct_LogRequest, client_info, client_info, &gdt_cct_ClientInfo_fields),
PB_FIELD( 2, INT32 , OPTIONAL, STATIC , OTHER, gdt_cct_LogRequest, log_source, client_info, 0),
PB_FIELD( 3, MESSAGE , REPEATED, POINTER , OTHER, gdt_cct_LogRequest, log_event, log_source, &gdt_cct_LogEvent_fields),
PB_FIELD( 4, INT64 , OPTIONAL, STATIC , OTHER, gdt_cct_LogRequest, request_time_ms, log_event, 0),
PB_FIELD( 8, INT64 , OPTIONAL, STATIC , OTHER, gdt_cct_LogRequest, request_uptime_ms, request_time_ms, 0),
PB_FIELD( 9, UENUM , OPTIONAL, STATIC , OTHER, gdt_cct_LogRequest, qos_tier, request_uptime_ms, &gdt_cct_LogRequest_qos_tier_default),
PB_LAST_FIELD
};
const pb_field_t gdt_cct_QosTierConfiguration_fields[3] = {
PB_FIELD( 2, UENUM , OPTIONAL, STATIC , FIRST, gdt_cct_QosTierConfiguration, qos_tier, qos_tier, 0),
PB_FIELD( 3, INT32 , OPTIONAL, STATIC , OTHER, gdt_cct_QosTierConfiguration, log_source, qos_tier, &gdt_cct_QosTierConfiguration_log_source_default),
PB_LAST_FIELD
};
const pb_field_t gdt_cct_QosTiersOverride_fields[3] = {
PB_FIELD( 1, MESSAGE , REPEATED, POINTER , FIRST, gdt_cct_QosTiersOverride, qos_tier_configuration, qos_tier_configuration, &gdt_cct_QosTierConfiguration_fields),
PB_FIELD( 2, INT64 , OPTIONAL, STATIC , OTHER, gdt_cct_QosTiersOverride, qos_tier_fingerprint, qos_tier_configuration, 0),
PB_LAST_FIELD
};
const pb_field_t gdt_cct_LogResponse_fields[3] = {
PB_FIELD( 1, INT64 , OPTIONAL, STATIC , FIRST, gdt_cct_LogResponse, next_request_wait_millis, next_request_wait_millis, 0),
PB_FIELD( 3, MESSAGE , OPTIONAL, STATIC , OTHER, gdt_cct_LogResponse, qos_tier, next_request_wait_millis, &gdt_cct_QosTiersOverride_fields),
PB_LAST_FIELD
};
/* Check that field information fits in pb_field_t */
#if !defined(PB_FIELD_32BIT)
/* If you get an error here, it means that you need to define PB_FIELD_32BIT
* compile-time option. You can do that in pb.h or on compiler command line.
*
* The reason you need to do this is that some of your messages contain tag
* numbers or field sizes that are larger than what can fit in 8 or 16 bit
* field descriptors.
*/
PB_STATIC_ASSERT((pb_membersize(gdt_cct_LogEvent, network_connection_info) < 65536 && pb_membersize(gdt_cct_LogEvent, compliance_data) < 65536 && pb_membersize(gdt_cct_ClientInfo, ios_client_info) < 65536 && pb_membersize(gdt_cct_ClientInfo, mac_client_info) < 65536 && pb_membersize(gdt_cct_LogRequest, client_info) < 65536 && pb_membersize(gdt_cct_LogResponse, qos_tier) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_gdt_cct_LogEvent_gdt_cct_NetworkConnectionInfo_gdt_cct_MacClientInfo_gdt_cct_IosClientInfo_gdt_cct_ClientInfo_gdt_cct_BatchedLogRequest_gdt_cct_LogRequest_gdt_cct_QosTierConfiguration_gdt_cct_QosTiersOverride_gdt_cct_LogResponse)
#endif
#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
/* If you get an error here, it means that you need to define PB_FIELD_16BIT
* compile-time option. You can do that in pb.h or on compiler command line.
*
* The reason you need to do this is that some of your messages contain tag
* numbers or field sizes that are larger than what can fit in the default
* 8 bit descriptors.
*/
PB_STATIC_ASSERT((pb_membersize(gdt_cct_LogEvent, network_connection_info) < 256 && pb_membersize(gdt_cct_LogEvent, compliance_data) < 256 && pb_membersize(gdt_cct_ClientInfo, ios_client_info) < 256 && pb_membersize(gdt_cct_ClientInfo, mac_client_info) < 256 && pb_membersize(gdt_cct_LogRequest, client_info) < 256 && pb_membersize(gdt_cct_LogResponse, qos_tier) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_gdt_cct_LogEvent_gdt_cct_NetworkConnectionInfo_gdt_cct_MacClientInfo_gdt_cct_IosClientInfo_gdt_cct_ClientInfo_gdt_cct_BatchedLogRequest_gdt_cct_LogRequest_gdt_cct_QosTierConfiguration_gdt_cct_QosTiersOverride_gdt_cct_LogResponse)
#endif
/* @@protoc_insertion_point(eof) */

View File

@ -0,0 +1,305 @@
/*
* 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.
*/
/* Automatically generated nanopb header */
/* Generated by nanopb-0.3.9.9 */
#ifndef PB_GDT_CCT_CCT_NANOPB_H_INCLUDED
#define PB_GDT_CCT_CCT_NANOPB_H_INCLUDED
#include <nanopb/pb.h>
#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.h"
/* @@protoc_insertion_point(includes) */
#if PB_PROTO_HEADER_VERSION != 30
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Enum definitions */
typedef enum _gdt_cct_NetworkConnectionInfo_NetworkType {
gdt_cct_NetworkConnectionInfo_NetworkType_NONE = -1,
gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE = 0,
gdt_cct_NetworkConnectionInfo_NetworkType_WIFI = 1,
gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_MMS = 2,
gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_SUPL = 3,
gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_DUN = 4,
gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_HIPRI = 5,
gdt_cct_NetworkConnectionInfo_NetworkType_WIMAX = 6,
gdt_cct_NetworkConnectionInfo_NetworkType_BLUETOOTH = 7,
gdt_cct_NetworkConnectionInfo_NetworkType_DUMMY = 8,
gdt_cct_NetworkConnectionInfo_NetworkType_ETHERNET = 9,
gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_FOTA = 10,
gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_IMS = 11,
gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_CBS = 12,
gdt_cct_NetworkConnectionInfo_NetworkType_WIFI_P2P = 13,
gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_IA = 14,
gdt_cct_NetworkConnectionInfo_NetworkType_MOBILE_EMERGENCY = 15,
gdt_cct_NetworkConnectionInfo_NetworkType_PROXY = 16,
gdt_cct_NetworkConnectionInfo_NetworkType_VPN = 17
} gdt_cct_NetworkConnectionInfo_NetworkType;
#define _gdt_cct_NetworkConnectionInfo_NetworkType_MIN gdt_cct_NetworkConnectionInfo_NetworkType_NONE
#define _gdt_cct_NetworkConnectionInfo_NetworkType_MAX gdt_cct_NetworkConnectionInfo_NetworkType_VPN
#define _gdt_cct_NetworkConnectionInfo_NetworkType_ARRAYSIZE ((gdt_cct_NetworkConnectionInfo_NetworkType)(gdt_cct_NetworkConnectionInfo_NetworkType_VPN+1))
typedef enum _gdt_cct_NetworkConnectionInfo_MobileSubtype {
gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE = 0,
gdt_cct_NetworkConnectionInfo_MobileSubtype_GPRS = 1,
gdt_cct_NetworkConnectionInfo_MobileSubtype_EDGE = 2,
gdt_cct_NetworkConnectionInfo_MobileSubtype_UMTS = 3,
gdt_cct_NetworkConnectionInfo_MobileSubtype_CDMA = 4,
gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_0 = 5,
gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_A = 6,
gdt_cct_NetworkConnectionInfo_MobileSubtype_RTT = 7,
gdt_cct_NetworkConnectionInfo_MobileSubtype_HSDPA = 8,
gdt_cct_NetworkConnectionInfo_MobileSubtype_HSUPA = 9,
gdt_cct_NetworkConnectionInfo_MobileSubtype_HSPA = 10,
gdt_cct_NetworkConnectionInfo_MobileSubtype_IDEN = 11,
gdt_cct_NetworkConnectionInfo_MobileSubtype_EVDO_B = 12,
gdt_cct_NetworkConnectionInfo_MobileSubtype_LTE = 13,
gdt_cct_NetworkConnectionInfo_MobileSubtype_EHRPD = 14,
gdt_cct_NetworkConnectionInfo_MobileSubtype_HSPAP = 15,
gdt_cct_NetworkConnectionInfo_MobileSubtype_GSM = 16,
gdt_cct_NetworkConnectionInfo_MobileSubtype_TD_SCDMA = 17,
gdt_cct_NetworkConnectionInfo_MobileSubtype_IWLAN = 18,
gdt_cct_NetworkConnectionInfo_MobileSubtype_LTE_CA = 19,
gdt_cct_NetworkConnectionInfo_MobileSubtype_COMBINED = 100
} gdt_cct_NetworkConnectionInfo_MobileSubtype;
#define _gdt_cct_NetworkConnectionInfo_MobileSubtype_MIN gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE
#define _gdt_cct_NetworkConnectionInfo_MobileSubtype_MAX gdt_cct_NetworkConnectionInfo_MobileSubtype_COMBINED
#define _gdt_cct_NetworkConnectionInfo_MobileSubtype_ARRAYSIZE ((gdt_cct_NetworkConnectionInfo_MobileSubtype)(gdt_cct_NetworkConnectionInfo_MobileSubtype_COMBINED+1))
typedef enum _gdt_cct_ClientInfo_ClientType {
gdt_cct_ClientInfo_ClientType_CLIENT_UNKNOWN = 0,
gdt_cct_ClientInfo_ClientType_IOS_FIREBASE = 15
} gdt_cct_ClientInfo_ClientType;
#define _gdt_cct_ClientInfo_ClientType_MIN gdt_cct_ClientInfo_ClientType_CLIENT_UNKNOWN
#define _gdt_cct_ClientInfo_ClientType_MAX gdt_cct_ClientInfo_ClientType_IOS_FIREBASE
#define _gdt_cct_ClientInfo_ClientType_ARRAYSIZE ((gdt_cct_ClientInfo_ClientType)(gdt_cct_ClientInfo_ClientType_IOS_FIREBASE+1))
typedef enum _gdt_cct_QosTierConfiguration_QosTier {
gdt_cct_QosTierConfiguration_QosTier_DEFAULT = 0,
gdt_cct_QosTierConfiguration_QosTier_UNMETERED_ONLY = 1,
gdt_cct_QosTierConfiguration_QosTier_UNMETERED_OR_DAILY = 2,
gdt_cct_QosTierConfiguration_QosTier_FAST_IF_RADIO_AWAKE = 3,
gdt_cct_QosTierConfiguration_QosTier_NEVER = 4
} gdt_cct_QosTierConfiguration_QosTier;
#define _gdt_cct_QosTierConfiguration_QosTier_MIN gdt_cct_QosTierConfiguration_QosTier_DEFAULT
#define _gdt_cct_QosTierConfiguration_QosTier_MAX gdt_cct_QosTierConfiguration_QosTier_NEVER
#define _gdt_cct_QosTierConfiguration_QosTier_ARRAYSIZE ((gdt_cct_QosTierConfiguration_QosTier)(gdt_cct_QosTierConfiguration_QosTier_NEVER+1))
/* Struct definitions */
typedef struct _gdt_cct_BatchedLogRequest {
pb_size_t log_request_count;
struct _gdt_cct_LogRequest *log_request;
/* @@protoc_insertion_point(struct:gdt_cct_BatchedLogRequest) */
} gdt_cct_BatchedLogRequest;
typedef struct _gdt_cct_IosClientInfo {
pb_bytes_array_t *os_major_version;
pb_bytes_array_t *os_full_version;
pb_bytes_array_t *application_build;
pb_bytes_array_t *country;
pb_bytes_array_t *model;
pb_bytes_array_t *language_code;
pb_bytes_array_t *application_bundle_id;
/* @@protoc_insertion_point(struct:gdt_cct_IosClientInfo) */
} gdt_cct_IosClientInfo;
typedef struct _gdt_cct_MacClientInfo {
pb_bytes_array_t *os_major_version;
pb_bytes_array_t *os_full_version;
pb_bytes_array_t *application_build;
pb_bytes_array_t *application_bundle_id;
/* @@protoc_insertion_point(struct:gdt_cct_MacClientInfo) */
} gdt_cct_MacClientInfo;
typedef struct _gdt_cct_ClientInfo {
bool has_client_type;
gdt_cct_ClientInfo_ClientType client_type;
bool has_ios_client_info;
gdt_cct_IosClientInfo ios_client_info;
bool has_mac_client_info;
gdt_cct_MacClientInfo mac_client_info;
/* @@protoc_insertion_point(struct:gdt_cct_ClientInfo) */
} gdt_cct_ClientInfo;
typedef struct _gdt_cct_NetworkConnectionInfo {
bool has_network_type;
gdt_cct_NetworkConnectionInfo_NetworkType network_type;
bool has_mobile_subtype;
gdt_cct_NetworkConnectionInfo_MobileSubtype mobile_subtype;
/* @@protoc_insertion_point(struct:gdt_cct_NetworkConnectionInfo) */
} gdt_cct_NetworkConnectionInfo;
typedef struct _gdt_cct_QosTierConfiguration {
bool has_qos_tier;
gdt_cct_QosTierConfiguration_QosTier qos_tier;
bool has_log_source;
int32_t log_source;
/* @@protoc_insertion_point(struct:gdt_cct_QosTierConfiguration) */
} gdt_cct_QosTierConfiguration;
typedef struct _gdt_cct_QosTiersOverride {
pb_size_t qos_tier_configuration_count;
struct _gdt_cct_QosTierConfiguration *qos_tier_configuration;
bool has_qos_tier_fingerprint;
int64_t qos_tier_fingerprint;
/* @@protoc_insertion_point(struct:gdt_cct_QosTiersOverride) */
} gdt_cct_QosTiersOverride;
typedef struct _gdt_cct_LogEvent {
bool has_event_time_ms;
int64_t event_time_ms;
pb_bytes_array_t *source_extension;
bool has_event_code;
int32_t event_code;
bool has_timezone_offset_seconds;
int64_t timezone_offset_seconds;
bool has_event_uptime_ms;
int64_t event_uptime_ms;
bool has_network_connection_info;
gdt_cct_NetworkConnectionInfo network_connection_info;
bool has_compliance_data;
gdt_cct_ComplianceData compliance_data;
/* @@protoc_insertion_point(struct:gdt_cct_LogEvent) */
} gdt_cct_LogEvent;
typedef struct _gdt_cct_LogRequest {
bool has_client_info;
gdt_cct_ClientInfo client_info;
bool has_log_source;
int32_t log_source;
pb_size_t log_event_count;
struct _gdt_cct_LogEvent *log_event;
bool has_request_time_ms;
int64_t request_time_ms;
bool has_request_uptime_ms;
int64_t request_uptime_ms;
bool has_qos_tier;
gdt_cct_QosTierConfiguration_QosTier qos_tier;
/* @@protoc_insertion_point(struct:gdt_cct_LogRequest) */
} gdt_cct_LogRequest;
typedef struct _gdt_cct_LogResponse {
bool has_next_request_wait_millis;
int64_t next_request_wait_millis;
bool has_qos_tier;
gdt_cct_QosTiersOverride qos_tier;
/* @@protoc_insertion_point(struct:gdt_cct_LogResponse) */
} gdt_cct_LogResponse;
/* Default values for struct fields */
extern const gdt_cct_NetworkConnectionInfo_NetworkType gdt_cct_NetworkConnectionInfo_network_type_default;
extern const gdt_cct_NetworkConnectionInfo_MobileSubtype gdt_cct_NetworkConnectionInfo_mobile_subtype_default;
extern const gdt_cct_QosTierConfiguration_QosTier gdt_cct_LogRequest_qos_tier_default;
extern const int32_t gdt_cct_QosTierConfiguration_log_source_default;
/* Initializer values for message structs */
#define gdt_cct_LogEvent_init_default {false, 0, NULL, false, 0, false, 0, false, 0, false, gdt_cct_NetworkConnectionInfo_init_default, false, gdt_cct_ComplianceData_init_default}
#define gdt_cct_NetworkConnectionInfo_init_default {false, gdt_cct_NetworkConnectionInfo_NetworkType_NONE, false, gdt_cct_NetworkConnectionInfo_MobileSubtype_UNKNOWN_MOBILE_SUBTYPE}
#define gdt_cct_MacClientInfo_init_default {NULL, NULL, NULL, NULL}
#define gdt_cct_IosClientInfo_init_default {NULL, NULL, NULL, NULL, NULL, NULL, NULL}
#define gdt_cct_ClientInfo_init_default {false, _gdt_cct_ClientInfo_ClientType_MIN, false, gdt_cct_IosClientInfo_init_default, false, gdt_cct_MacClientInfo_init_default}
#define gdt_cct_BatchedLogRequest_init_default {0, NULL}
#define gdt_cct_LogRequest_init_default {false, gdt_cct_ClientInfo_init_default, false, 0, 0, NULL, false, 0, false, 0, false, gdt_cct_QosTierConfiguration_QosTier_DEFAULT}
#define gdt_cct_QosTierConfiguration_init_default {false, _gdt_cct_QosTierConfiguration_QosTier_MIN, false, 0}
#define gdt_cct_QosTiersOverride_init_default {0, NULL, false, 0}
#define gdt_cct_LogResponse_init_default {false, 0, false, gdt_cct_QosTiersOverride_init_default}
#define gdt_cct_LogEvent_init_zero {false, 0, NULL, false, 0, false, 0, false, 0, false, gdt_cct_NetworkConnectionInfo_init_zero, false, gdt_cct_ComplianceData_init_zero}
#define gdt_cct_NetworkConnectionInfo_init_zero {false, _gdt_cct_NetworkConnectionInfo_NetworkType_MIN, false, _gdt_cct_NetworkConnectionInfo_MobileSubtype_MIN}
#define gdt_cct_MacClientInfo_init_zero {NULL, NULL, NULL, NULL}
#define gdt_cct_IosClientInfo_init_zero {NULL, NULL, NULL, NULL, NULL, NULL, NULL}
#define gdt_cct_ClientInfo_init_zero {false, _gdt_cct_ClientInfo_ClientType_MIN, false, gdt_cct_IosClientInfo_init_zero, false, gdt_cct_MacClientInfo_init_zero}
#define gdt_cct_BatchedLogRequest_init_zero {0, NULL}
#define gdt_cct_LogRequest_init_zero {false, gdt_cct_ClientInfo_init_zero, false, 0, 0, NULL, false, 0, false, 0, false, _gdt_cct_QosTierConfiguration_QosTier_MIN}
#define gdt_cct_QosTierConfiguration_init_zero {false, _gdt_cct_QosTierConfiguration_QosTier_MIN, false, 0}
#define gdt_cct_QosTiersOverride_init_zero {0, NULL, false, 0}
#define gdt_cct_LogResponse_init_zero {false, 0, false, gdt_cct_QosTiersOverride_init_zero}
/* Field tags (for use in manual encoding/decoding) */
#define gdt_cct_BatchedLogRequest_log_request_tag 1
#define gdt_cct_IosClientInfo_os_major_version_tag 3
#define gdt_cct_IosClientInfo_os_full_version_tag 4
#define gdt_cct_IosClientInfo_application_build_tag 5
#define gdt_cct_IosClientInfo_country_tag 6
#define gdt_cct_IosClientInfo_model_tag 7
#define gdt_cct_IosClientInfo_language_code_tag 8
#define gdt_cct_IosClientInfo_application_bundle_id_tag 11
#define gdt_cct_MacClientInfo_os_major_version_tag 1
#define gdt_cct_MacClientInfo_os_full_version_tag 2
#define gdt_cct_MacClientInfo_application_build_tag 3
#define gdt_cct_MacClientInfo_application_bundle_id_tag 7
#define gdt_cct_ClientInfo_client_type_tag 1
#define gdt_cct_ClientInfo_ios_client_info_tag 4
#define gdt_cct_ClientInfo_mac_client_info_tag 13
#define gdt_cct_NetworkConnectionInfo_network_type_tag 1
#define gdt_cct_NetworkConnectionInfo_mobile_subtype_tag 2
#define gdt_cct_QosTierConfiguration_qos_tier_tag 2
#define gdt_cct_QosTierConfiguration_log_source_tag 3
#define gdt_cct_QosTiersOverride_qos_tier_configuration_tag 1
#define gdt_cct_QosTiersOverride_qos_tier_fingerprint_tag 2
#define gdt_cct_LogEvent_event_time_ms_tag 1
#define gdt_cct_LogEvent_event_code_tag 11
#define gdt_cct_LogEvent_event_uptime_ms_tag 17
#define gdt_cct_LogEvent_source_extension_tag 6
#define gdt_cct_LogEvent_timezone_offset_seconds_tag 15
#define gdt_cct_LogEvent_network_connection_info_tag 23
#define gdt_cct_LogEvent_compliance_data_tag 33
#define gdt_cct_LogRequest_request_time_ms_tag 4
#define gdt_cct_LogRequest_request_uptime_ms_tag 8
#define gdt_cct_LogRequest_client_info_tag 1
#define gdt_cct_LogRequest_log_source_tag 2
#define gdt_cct_LogRequest_log_event_tag 3
#define gdt_cct_LogRequest_qos_tier_tag 9
#define gdt_cct_LogResponse_next_request_wait_millis_tag 1
#define gdt_cct_LogResponse_qos_tier_tag 3
/* Struct field encoding specification for nanopb */
extern const pb_field_t gdt_cct_LogEvent_fields[8];
extern const pb_field_t gdt_cct_NetworkConnectionInfo_fields[3];
extern const pb_field_t gdt_cct_MacClientInfo_fields[5];
extern const pb_field_t gdt_cct_IosClientInfo_fields[8];
extern const pb_field_t gdt_cct_ClientInfo_fields[4];
extern const pb_field_t gdt_cct_BatchedLogRequest_fields[2];
extern const pb_field_t gdt_cct_LogRequest_fields[7];
extern const pb_field_t gdt_cct_QosTierConfiguration_fields[3];
extern const pb_field_t gdt_cct_QosTiersOverride_fields[3];
extern const pb_field_t gdt_cct_LogResponse_fields[3];
/* Maximum encoded size of messages (where known) */
/* gdt_cct_LogEvent_size depends on runtime parameters */
#define gdt_cct_NetworkConnectionInfo_size 13
/* gdt_cct_MacClientInfo_size depends on runtime parameters */
/* gdt_cct_IosClientInfo_size depends on runtime parameters */
/* gdt_cct_ClientInfo_size depends on runtime parameters */
/* gdt_cct_BatchedLogRequest_size depends on runtime parameters */
/* gdt_cct_LogRequest_size depends on runtime parameters */
#define gdt_cct_QosTierConfiguration_size 13
/* gdt_cct_QosTiersOverride_size depends on runtime parameters */
/* gdt_cct_LogResponse_size depends on runtime parameters */
/* Message IDs (where set with "msgid" option) */
#ifdef PB_MSGID
#define CCT_MESSAGES \
#endif
/* @@protoc_insertion_point(eof) */
#endif

View File

@ -0,0 +1,92 @@
/*
* Copyright 2022 Google LLC
*
* 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.
*/
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.3.9.9 */
#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/client_metrics.nanopb.h"
/* @@protoc_insertion_point(includes) */
#if PB_PROTO_HEADER_VERSION != 30
#error Regenerate this file with the current version of nanopb generator.
#endif
const pb_field_t gdt_client_metrics_ClientMetrics_fields[5] = {
PB_FIELD( 1, MESSAGE , SINGULAR, STATIC , FIRST, gdt_client_metrics_ClientMetrics, window, window, &gdt_client_metrics_TimeWindow_fields),
PB_FIELD( 2, MESSAGE , REPEATED, POINTER , OTHER, gdt_client_metrics_ClientMetrics, log_source_metrics, window, &gdt_client_metrics_LogSourceMetrics_fields),
PB_FIELD( 3, MESSAGE , SINGULAR, STATIC , OTHER, gdt_client_metrics_ClientMetrics, global_metrics, log_source_metrics, &gdt_client_metrics_GlobalMetrics_fields),
PB_FIELD( 4, BYTES , SINGULAR, POINTER , OTHER, gdt_client_metrics_ClientMetrics, app_namespace, global_metrics, 0),
PB_LAST_FIELD
};
const pb_field_t gdt_client_metrics_TimeWindow_fields[3] = {
PB_FIELD( 1, INT64 , SINGULAR, STATIC , FIRST, gdt_client_metrics_TimeWindow, start_ms, start_ms, 0),
PB_FIELD( 2, INT64 , SINGULAR, STATIC , OTHER, gdt_client_metrics_TimeWindow, end_ms, start_ms, 0),
PB_LAST_FIELD
};
const pb_field_t gdt_client_metrics_GlobalMetrics_fields[2] = {
PB_FIELD( 1, MESSAGE , SINGULAR, STATIC , FIRST, gdt_client_metrics_GlobalMetrics, storage_metrics, storage_metrics, &gdt_client_metrics_StorageMetrics_fields),
PB_LAST_FIELD
};
const pb_field_t gdt_client_metrics_StorageMetrics_fields[3] = {
PB_FIELD( 1, INT64 , SINGULAR, STATIC , FIRST, gdt_client_metrics_StorageMetrics, current_cache_size_bytes, current_cache_size_bytes, 0),
PB_FIELD( 2, INT64 , SINGULAR, STATIC , OTHER, gdt_client_metrics_StorageMetrics, max_cache_size_bytes, current_cache_size_bytes, 0),
PB_LAST_FIELD
};
const pb_field_t gdt_client_metrics_LogSourceMetrics_fields[3] = {
PB_FIELD( 1, BYTES , SINGULAR, POINTER , FIRST, gdt_client_metrics_LogSourceMetrics, log_source, log_source, 0),
PB_FIELD( 2, MESSAGE , REPEATED, POINTER , OTHER, gdt_client_metrics_LogSourceMetrics, log_event_dropped, log_source, &gdt_client_metrics_LogEventDropped_fields),
PB_LAST_FIELD
};
const pb_field_t gdt_client_metrics_LogEventDropped_fields[3] = {
PB_FIELD( 1, INT64 , SINGULAR, STATIC , FIRST, gdt_client_metrics_LogEventDropped, events_dropped_count, events_dropped_count, 0),
PB_FIELD( 3, UENUM , SINGULAR, STATIC , OTHER, gdt_client_metrics_LogEventDropped, reason, events_dropped_count, 0),
PB_LAST_FIELD
};
/* Check that field information fits in pb_field_t */
#if !defined(PB_FIELD_32BIT)
/* If you get an error here, it means that you need to define PB_FIELD_32BIT
* compile-time option. You can do that in pb.h or on compiler command line.
*
* The reason you need to do this is that some of your messages contain tag
* numbers or field sizes that are larger than what can fit in 8 or 16 bit
* field descriptors.
*/
PB_STATIC_ASSERT((pb_membersize(gdt_client_metrics_ClientMetrics, window) < 65536 && pb_membersize(gdt_client_metrics_ClientMetrics, global_metrics) < 65536 && pb_membersize(gdt_client_metrics_GlobalMetrics, storage_metrics) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_gdt_client_metrics_ClientMetrics_gdt_client_metrics_TimeWindow_gdt_client_metrics_GlobalMetrics_gdt_client_metrics_StorageMetrics_gdt_client_metrics_LogSourceMetrics_gdt_client_metrics_LogEventDropped)
#endif
#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
/* If you get an error here, it means that you need to define PB_FIELD_16BIT
* compile-time option. You can do that in pb.h or on compiler command line.
*
* The reason you need to do this is that some of your messages contain tag
* numbers or field sizes that are larger than what can fit in the default
* 8 bit descriptors.
*/
PB_STATIC_ASSERT((pb_membersize(gdt_client_metrics_ClientMetrics, window) < 256 && pb_membersize(gdt_client_metrics_ClientMetrics, global_metrics) < 256 && pb_membersize(gdt_client_metrics_GlobalMetrics, storage_metrics) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_gdt_client_metrics_ClientMetrics_gdt_client_metrics_TimeWindow_gdt_client_metrics_GlobalMetrics_gdt_client_metrics_StorageMetrics_gdt_client_metrics_LogSourceMetrics_gdt_client_metrics_LogEventDropped)
#endif
/* @@protoc_insertion_point(eof) */

View File

@ -0,0 +1,141 @@
/*
* Copyright 2022 Google LLC
*
* 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.
*/
/* Automatically generated nanopb header */
/* Generated by nanopb-0.3.9.9 */
#ifndef PB_GDT_CLIENT_METRICS_CLIENT_METRICS_NANOPB_H_INCLUDED
#define PB_GDT_CLIENT_METRICS_CLIENT_METRICS_NANOPB_H_INCLUDED
#include <nanopb/pb.h>
/* @@protoc_insertion_point(includes) */
#if PB_PROTO_HEADER_VERSION != 30
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Enum definitions */
typedef enum _gdt_client_metrics_LogEventDropped_Reason {
gdt_client_metrics_LogEventDropped_Reason_REASON_UNKNOWN = 0,
gdt_client_metrics_LogEventDropped_Reason_MESSAGE_TOO_OLD = 1,
gdt_client_metrics_LogEventDropped_Reason_CACHE_FULL = 2,
gdt_client_metrics_LogEventDropped_Reason_PAYLOAD_TOO_BIG = 3,
gdt_client_metrics_LogEventDropped_Reason_MAX_RETRIES_REACHED = 4,
gdt_client_metrics_LogEventDropped_Reason_INVALID_PAYLOD = 5,
gdt_client_metrics_LogEventDropped_Reason_SERVER_ERROR = 6
} gdt_client_metrics_LogEventDropped_Reason;
#define _gdt_client_metrics_LogEventDropped_Reason_MIN gdt_client_metrics_LogEventDropped_Reason_REASON_UNKNOWN
#define _gdt_client_metrics_LogEventDropped_Reason_MAX gdt_client_metrics_LogEventDropped_Reason_SERVER_ERROR
#define _gdt_client_metrics_LogEventDropped_Reason_ARRAYSIZE ((gdt_client_metrics_LogEventDropped_Reason)(gdt_client_metrics_LogEventDropped_Reason_SERVER_ERROR+1))
/* Struct definitions */
typedef struct _gdt_client_metrics_LogSourceMetrics {
pb_bytes_array_t *log_source;
pb_size_t log_event_dropped_count;
struct _gdt_client_metrics_LogEventDropped *log_event_dropped;
/* @@protoc_insertion_point(struct:gdt_client_metrics_LogSourceMetrics) */
} gdt_client_metrics_LogSourceMetrics;
typedef struct _gdt_client_metrics_LogEventDropped {
int64_t events_dropped_count;
gdt_client_metrics_LogEventDropped_Reason reason;
/* @@protoc_insertion_point(struct:gdt_client_metrics_LogEventDropped) */
} gdt_client_metrics_LogEventDropped;
typedef struct _gdt_client_metrics_StorageMetrics {
int64_t current_cache_size_bytes;
int64_t max_cache_size_bytes;
/* @@protoc_insertion_point(struct:gdt_client_metrics_StorageMetrics) */
} gdt_client_metrics_StorageMetrics;
typedef struct _gdt_client_metrics_TimeWindow {
int64_t start_ms;
int64_t end_ms;
/* @@protoc_insertion_point(struct:gdt_client_metrics_TimeWindow) */
} gdt_client_metrics_TimeWindow;
typedef struct _gdt_client_metrics_GlobalMetrics {
gdt_client_metrics_StorageMetrics storage_metrics;
/* @@protoc_insertion_point(struct:gdt_client_metrics_GlobalMetrics) */
} gdt_client_metrics_GlobalMetrics;
typedef struct _gdt_client_metrics_ClientMetrics {
gdt_client_metrics_TimeWindow window;
pb_size_t log_source_metrics_count;
struct _gdt_client_metrics_LogSourceMetrics *log_source_metrics;
gdt_client_metrics_GlobalMetrics global_metrics;
pb_bytes_array_t *app_namespace;
/* @@protoc_insertion_point(struct:gdt_client_metrics_ClientMetrics) */
} gdt_client_metrics_ClientMetrics;
/* Default values for struct fields */
/* Initializer values for message structs */
#define gdt_client_metrics_ClientMetrics_init_default {gdt_client_metrics_TimeWindow_init_default, 0, NULL, gdt_client_metrics_GlobalMetrics_init_default, NULL}
#define gdt_client_metrics_TimeWindow_init_default {0, 0}
#define gdt_client_metrics_GlobalMetrics_init_default {gdt_client_metrics_StorageMetrics_init_default}
#define gdt_client_metrics_StorageMetrics_init_default {0, 0}
#define gdt_client_metrics_LogSourceMetrics_init_default {NULL, 0, NULL}
#define gdt_client_metrics_LogEventDropped_init_default {0, _gdt_client_metrics_LogEventDropped_Reason_MIN}
#define gdt_client_metrics_ClientMetrics_init_zero {gdt_client_metrics_TimeWindow_init_zero, 0, NULL, gdt_client_metrics_GlobalMetrics_init_zero, NULL}
#define gdt_client_metrics_TimeWindow_init_zero {0, 0}
#define gdt_client_metrics_GlobalMetrics_init_zero {gdt_client_metrics_StorageMetrics_init_zero}
#define gdt_client_metrics_StorageMetrics_init_zero {0, 0}
#define gdt_client_metrics_LogSourceMetrics_init_zero {NULL, 0, NULL}
#define gdt_client_metrics_LogEventDropped_init_zero {0, _gdt_client_metrics_LogEventDropped_Reason_MIN}
/* Field tags (for use in manual encoding/decoding) */
#define gdt_client_metrics_LogSourceMetrics_log_source_tag 1
#define gdt_client_metrics_LogSourceMetrics_log_event_dropped_tag 2
#define gdt_client_metrics_LogEventDropped_events_dropped_count_tag 1
#define gdt_client_metrics_LogEventDropped_reason_tag 3
#define gdt_client_metrics_StorageMetrics_current_cache_size_bytes_tag 1
#define gdt_client_metrics_StorageMetrics_max_cache_size_bytes_tag 2
#define gdt_client_metrics_TimeWindow_start_ms_tag 1
#define gdt_client_metrics_TimeWindow_end_ms_tag 2
#define gdt_client_metrics_GlobalMetrics_storage_metrics_tag 1
#define gdt_client_metrics_ClientMetrics_window_tag 1
#define gdt_client_metrics_ClientMetrics_log_source_metrics_tag 2
#define gdt_client_metrics_ClientMetrics_global_metrics_tag 3
#define gdt_client_metrics_ClientMetrics_app_namespace_tag 4
/* Struct field encoding specification for nanopb */
extern const pb_field_t gdt_client_metrics_ClientMetrics_fields[5];
extern const pb_field_t gdt_client_metrics_TimeWindow_fields[3];
extern const pb_field_t gdt_client_metrics_GlobalMetrics_fields[2];
extern const pb_field_t gdt_client_metrics_StorageMetrics_fields[3];
extern const pb_field_t gdt_client_metrics_LogSourceMetrics_fields[3];
extern const pb_field_t gdt_client_metrics_LogEventDropped_fields[3];
/* Maximum encoded size of messages (where known) */
/* gdt_client_metrics_ClientMetrics_size depends on runtime parameters */
#define gdt_client_metrics_TimeWindow_size 22
#define gdt_client_metrics_GlobalMetrics_size 24
#define gdt_client_metrics_StorageMetrics_size 22
/* gdt_client_metrics_LogSourceMetrics_size depends on runtime parameters */
#define gdt_client_metrics_LogEventDropped_size 13
/* Message IDs (where set with "msgid" option) */
#ifdef PB_MSGID
#define CLIENT_METRICS_MESSAGES \
#endif
/* @@protoc_insertion_point(eof) */
#endif

View File

@ -0,0 +1,62 @@
/*
* Copyright 2023 Google LLC
*
* 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.
*/
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.3.9.9 */
#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/compliance.nanopb.h"
/* @@protoc_insertion_point(includes) */
#if PB_PROTO_HEADER_VERSION != 30
#error Regenerate this file with the current version of nanopb generator.
#endif
const gdt_cct_ComplianceData_ProductIdOrigin gdt_cct_ComplianceData_product_id_origin_default = gdt_cct_ComplianceData_ProductIdOrigin_NOT_SET;
const pb_field_t gdt_cct_ComplianceData_fields[3] = {
PB_FIELD( 1, MESSAGE , OPTIONAL, STATIC , FIRST, gdt_cct_ComplianceData, privacy_context, privacy_context, &privacy_context_external_ExternalPrivacyContext_fields),
PB_FIELD( 2, UENUM , OPTIONAL, STATIC , OTHER, gdt_cct_ComplianceData, product_id_origin, privacy_context, &gdt_cct_ComplianceData_product_id_origin_default),
PB_LAST_FIELD
};
/* Check that field information fits in pb_field_t */
#if !defined(PB_FIELD_32BIT)
/* If you get an error here, it means that you need to define PB_FIELD_32BIT
* compile-time option. You can do that in pb.h or on compiler command line.
*
* The reason you need to do this is that some of your messages contain tag
* numbers or field sizes that are larger than what can fit in 8 or 16 bit
* field descriptors.
*/
PB_STATIC_ASSERT((pb_membersize(gdt_cct_ComplianceData, privacy_context) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_gdt_cct_ComplianceData)
#endif
#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
/* If you get an error here, it means that you need to define PB_FIELD_16BIT
* compile-time option. You can do that in pb.h or on compiler command line.
*
* The reason you need to do this is that some of your messages contain tag
* numbers or field sizes that are larger than what can fit in the default
* 8 bit descriptors.
*/
PB_STATIC_ASSERT((pb_membersize(gdt_cct_ComplianceData, privacy_context) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_gdt_cct_ComplianceData)
#endif
/* @@protoc_insertion_point(eof) */

View File

@ -0,0 +1,77 @@
/*
* Copyright 2023 Google LLC
*
* 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.
*/
/* Automatically generated nanopb header */
/* Generated by nanopb-0.3.9.9 */
#ifndef PB_GDT_CCT_COMPLIANCE_NANOPB_H_INCLUDED
#define PB_GDT_CCT_COMPLIANCE_NANOPB_H_INCLUDED
#include <nanopb/pb.h>
#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_privacy_context.nanopb.h"
/* @@protoc_insertion_point(includes) */
#if PB_PROTO_HEADER_VERSION != 30
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Enum definitions */
typedef enum _gdt_cct_ComplianceData_ProductIdOrigin {
gdt_cct_ComplianceData_ProductIdOrigin_NOT_SET = 0,
gdt_cct_ComplianceData_ProductIdOrigin_EVENT_OVERRIDE = 5
} gdt_cct_ComplianceData_ProductIdOrigin;
#define _gdt_cct_ComplianceData_ProductIdOrigin_MIN gdt_cct_ComplianceData_ProductIdOrigin_NOT_SET
#define _gdt_cct_ComplianceData_ProductIdOrigin_MAX gdt_cct_ComplianceData_ProductIdOrigin_EVENT_OVERRIDE
#define _gdt_cct_ComplianceData_ProductIdOrigin_ARRAYSIZE ((gdt_cct_ComplianceData_ProductIdOrigin)(gdt_cct_ComplianceData_ProductIdOrigin_EVENT_OVERRIDE+1))
/* Struct definitions */
typedef struct _gdt_cct_ComplianceData {
bool has_privacy_context;
privacy_context_external_ExternalPrivacyContext privacy_context;
bool has_product_id_origin;
gdt_cct_ComplianceData_ProductIdOrigin product_id_origin;
/* @@protoc_insertion_point(struct:gdt_cct_ComplianceData) */
} gdt_cct_ComplianceData;
/* Default values for struct fields */
extern const gdt_cct_ComplianceData_ProductIdOrigin gdt_cct_ComplianceData_product_id_origin_default;
/* Initializer values for message structs */
#define gdt_cct_ComplianceData_init_default {false, privacy_context_external_ExternalPrivacyContext_init_default, false, gdt_cct_ComplianceData_ProductIdOrigin_NOT_SET}
#define gdt_cct_ComplianceData_init_zero {false, privacy_context_external_ExternalPrivacyContext_init_zero, false, _gdt_cct_ComplianceData_ProductIdOrigin_MIN}
/* Field tags (for use in manual encoding/decoding) */
#define gdt_cct_ComplianceData_privacy_context_tag 1
#define gdt_cct_ComplianceData_product_id_origin_tag 2
/* Struct field encoding specification for nanopb */
extern const pb_field_t gdt_cct_ComplianceData_fields[3];
/* Maximum encoded size of messages (where known) */
#define gdt_cct_ComplianceData_size (14 + privacy_context_external_ExternalPRequestContext_size)
/* Message IDs (where set with "msgid" option) */
#ifdef PB_MSGID
#define COMPLIANCE_MESSAGES \
#endif
/* @@protoc_insertion_point(eof) */
#endif

View File

@ -0,0 +1,35 @@
/*
* Copyright 2023 Google LLC
*
* 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.
*/
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.3.9.9 */
#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_prequest_context.nanopb.h"
/* @@protoc_insertion_point(includes) */
#if PB_PROTO_HEADER_VERSION != 30
#error Regenerate this file with the current version of nanopb generator.
#endif
const pb_field_t privacy_context_external_ExternalPRequestContext_fields[2] = {
PB_FIELD( 13, INT32 , OPTIONAL, STATIC , FIRST, privacy_context_external_ExternalPRequestContext, origin_associated_product_id, origin_associated_product_id, 0),
PB_LAST_FIELD
};
/* @@protoc_insertion_point(eof) */

View File

@ -0,0 +1,62 @@
/*
* Copyright 2023 Google LLC
*
* 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.
*/
/* Automatically generated nanopb header */
/* Generated by nanopb-0.3.9.9 */
#ifndef PB_PRIVACY_CONTEXT_EXTERNAL_EXTERNAL_PREQUEST_CONTEXT_NANOPB_H_INCLUDED
#define PB_PRIVACY_CONTEXT_EXTERNAL_EXTERNAL_PREQUEST_CONTEXT_NANOPB_H_INCLUDED
#include <nanopb/pb.h>
/* @@protoc_insertion_point(includes) */
#if PB_PROTO_HEADER_VERSION != 30
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Struct definitions */
typedef struct _privacy_context_external_ExternalPRequestContext {
bool has_origin_associated_product_id;
int32_t origin_associated_product_id;
/* @@protoc_insertion_point(struct:privacy_context_external_ExternalPRequestContext) */
} privacy_context_external_ExternalPRequestContext;
/* Default values for struct fields */
/* Initializer values for message structs */
#define privacy_context_external_ExternalPRequestContext_init_default {false, 0}
#define privacy_context_external_ExternalPRequestContext_init_zero {false, 0}
/* Field tags (for use in manual encoding/decoding) */
#define privacy_context_external_ExternalPRequestContext_origin_associated_product_id_tag 13
/* Struct field encoding specification for nanopb */
extern const pb_field_t privacy_context_external_ExternalPRequestContext_fields[2];
/* Maximum encoded size of messages (where known) */
#define privacy_context_external_ExternalPRequestContext_size 11
/* Message IDs (where set with "msgid" option) */
#ifdef PB_MSGID
#define EXTERNAL_PREQUEST_CONTEXT_MESSAGES \
#endif
/* @@protoc_insertion_point(eof) */
#endif

View File

@ -0,0 +1,59 @@
/*
* Copyright 2023 Google LLC
*
* 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.
*/
/* Automatically generated nanopb constant definitions */
/* Generated by nanopb-0.3.9.9 */
#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_privacy_context.nanopb.h"
/* @@protoc_insertion_point(includes) */
#if PB_PROTO_HEADER_VERSION != 30
#error Regenerate this file with the current version of nanopb generator.
#endif
const pb_field_t privacy_context_external_ExternalPrivacyContext_fields[2] = {
PB_FIELD( 2, MESSAGE , OPTIONAL, STATIC , FIRST, privacy_context_external_ExternalPrivacyContext, prequest, prequest, &privacy_context_external_ExternalPRequestContext_fields),
PB_LAST_FIELD
};
/* Check that field information fits in pb_field_t */
#if !defined(PB_FIELD_32BIT)
/* If you get an error here, it means that you need to define PB_FIELD_32BIT
* compile-time option. You can do that in pb.h or on compiler command line.
*
* The reason you need to do this is that some of your messages contain tag
* numbers or field sizes that are larger than what can fit in 8 or 16 bit
* field descriptors.
*/
PB_STATIC_ASSERT((pb_membersize(privacy_context_external_ExternalPrivacyContext, prequest) < 65536), YOU_MUST_DEFINE_PB_FIELD_32BIT_FOR_MESSAGES_privacy_context_external_ExternalPrivacyContext)
#endif
#if !defined(PB_FIELD_16BIT) && !defined(PB_FIELD_32BIT)
/* If you get an error here, it means that you need to define PB_FIELD_16BIT
* compile-time option. You can do that in pb.h or on compiler command line.
*
* The reason you need to do this is that some of your messages contain tag
* numbers or field sizes that are larger than what can fit in the default
* 8 bit descriptors.
*/
PB_STATIC_ASSERT((pb_membersize(privacy_context_external_ExternalPrivacyContext, prequest) < 256), YOU_MUST_DEFINE_PB_FIELD_16BIT_FOR_MESSAGES_privacy_context_external_ExternalPrivacyContext)
#endif
/* @@protoc_insertion_point(eof) */

View File

@ -0,0 +1,64 @@
/*
* Copyright 2023 Google LLC
*
* 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.
*/
/* Automatically generated nanopb header */
/* Generated by nanopb-0.3.9.9 */
#ifndef PB_PRIVACY_CONTEXT_EXTERNAL_EXTERNAL_PRIVACY_CONTEXT_NANOPB_H_INCLUDED
#define PB_PRIVACY_CONTEXT_EXTERNAL_EXTERNAL_PRIVACY_CONTEXT_NANOPB_H_INCLUDED
#include <nanopb/pb.h>
#include "GoogleDataTransport/GDTCCTLibrary/Protogen/nanopb/external_prequest_context.nanopb.h"
/* @@protoc_insertion_point(includes) */
#if PB_PROTO_HEADER_VERSION != 30
#error Regenerate this file with the current version of nanopb generator.
#endif
/* Struct definitions */
typedef struct _privacy_context_external_ExternalPrivacyContext {
bool has_prequest;
privacy_context_external_ExternalPRequestContext prequest;
/* @@protoc_insertion_point(struct:privacy_context_external_ExternalPrivacyContext) */
} privacy_context_external_ExternalPrivacyContext;
/* Default values for struct fields */
/* Initializer values for message structs */
#define privacy_context_external_ExternalPrivacyContext_init_default {false, privacy_context_external_ExternalPRequestContext_init_default}
#define privacy_context_external_ExternalPrivacyContext_init_zero {false, privacy_context_external_ExternalPRequestContext_init_zero}
/* Field tags (for use in manual encoding/decoding) */
#define privacy_context_external_ExternalPrivacyContext_prequest_tag 2
/* Struct field encoding specification for nanopb */
extern const pb_field_t privacy_context_external_ExternalPrivacyContext_fields[2];
/* Maximum encoded size of messages (where known) */
#define privacy_context_external_ExternalPrivacyContext_size 13
/* Message IDs (where set with "msgid" option) */
#ifdef PB_MSGID
#define EXTERNAL_PRIVACY_CONTEXT_MESSAGES \
#endif
/* @@protoc_insertion_point(eof) */
#endif

View File

@ -0,0 +1,51 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
NS_ASSUME_NONNULL_BEGIN
/** A string sets in customBytes as a key paired to @YES if current event needs to
* populate network connection info data, @NO otherwise.
*/
FOUNDATION_EXPORT NSString *const GDTCCTNeedsNetworkConnectionInfo;
/** A string sets in customBytes as a key paired to the network connection info data
* of current event.
*/
FOUNDATION_EXPORT NSString *const GDTCCTNetworkConnectionInfo;
/** A category that uses the customBytes property of a GDTCOREvent to store network connection info.
*/
@interface GDTCOREvent (GDTCCTSupport)
/** If YES, needs the network connection info field set during prioritization.
* @note Uses the GDTCOREvent customBytes property.
*/
@property(nonatomic) BOOL needsNetworkConnectionInfoPopulated;
/** The network connection info as collected at the time of the event.
* @note Uses the GDTCOREvent customBytes property.
*/
@property(nullable, nonatomic) NSData *networkConnectionInfoData;
/** Code that identifies the event to be sent to the CCT backend.
*/
@property(nullable, nonatomic) NSNumber *eventCode;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,36 @@
/*
* 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h"
GDTCORAssertionBlock GDTCORAssertionBlockToRunInstead(void) {
// This class is only compiled in by unit tests, and this should fail quickly in optimized builds.
Class GDTCORAssertClass = NSClassFromString(@"GDTCORAssertHelper");
if (__builtin_expect(!!GDTCORAssertClass, 0)) {
SEL assertionBlockSEL = NSSelectorFromString(@"assertionBlock");
if (assertionBlockSEL) {
IMP assertionBlockIMP = [GDTCORAssertClass methodForSelector:assertionBlockSEL];
if (assertionBlockIMP) {
GDTCORAssertionBlock assertionBlock = ((GDTCORAssertionBlock(*)(id, SEL))assertionBlockIMP)(
GDTCORAssertClass, assertionBlockSEL);
if (assertionBlock) {
return assertionBlock;
}
}
}
}
return NULL;
}

View File

@ -0,0 +1,174 @@
/*
* Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h"
#import <sys/sysctl.h>
// Using a monotonic clock is necessary because CFAbsoluteTimeGetCurrent(), NSDate, and related all
// are subject to drift. That it to say, multiple consecutive calls do not always result in a
// time that is in the future. Clocks may be adjusted by the user, NTP, or any number of external
// factors. This class attempts to determine the wall-clock time at the time of the event by
// capturing the kernel start and time since boot to determine a wallclock time in UTC.
//
// Timezone offsets at the time of a snapshot are also captured in order to provide local-time
// details. Other classes in this library depend on comparing times at some time in the future to
// a time captured in the past, and this class needs to provide a mechanism to do that.
//
// TL;DR: This class attempts to accomplish two things: 1. Provide accurate event times. 2. Provide
// a monotonic clock mechanism to accurately check if some clock snapshot was before or after
// by using a shared reference point (kernel boot time).
//
// Note: Much of the mach time stuff doesn't work properly in the simulator. So this class can be
// difficult to unit test.
/** Returns the kernel boottime property from sysctl.
*
* @return The KERN_BOOTTIME property from sysctl, in nanoseconds.
*/
static int64_t KernelBootTimeInNanoseconds(void) {
// Caching the result is not possible because clock drift would not be accounted for.
struct timeval boottime;
int mib[2] = {CTL_KERN, KERN_BOOTTIME};
size_t size = sizeof(boottime);
int rc = sysctl(mib, 2, &boottime, &size, NULL, 0);
if (rc != 0) {
return 0;
}
return (int64_t)boottime.tv_sec * NSEC_PER_SEC + (int64_t)boottime.tv_usec * NSEC_PER_USEC;
}
/** Returns value of gettimeofday, in nanoseconds.
*
* @return The value of gettimeofday, in nanoseconds.
*/
static int64_t UptimeInNanoseconds(void) {
int64_t before_now_nsec;
int64_t after_now_nsec;
struct timeval now;
before_now_nsec = KernelBootTimeInNanoseconds();
// Addresses a race condition in which the system time has updated, but the boottime has not.
do {
gettimeofday(&now, NULL);
after_now_nsec = KernelBootTimeInNanoseconds();
} while (after_now_nsec != before_now_nsec);
return (int64_t)now.tv_sec * NSEC_PER_SEC + (int64_t)now.tv_usec * NSEC_PER_USEC -
before_now_nsec;
}
// TODO: Consider adding a 'trustedTime' property that can be populated by the response from a BE.
@implementation GDTCORClock
- (instancetype)init {
self = [super init];
if (self) {
_kernelBootTimeNanoseconds = KernelBootTimeInNanoseconds();
_uptimeNanoseconds = UptimeInNanoseconds();
_timeMillis =
(int64_t)((CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) * NSEC_PER_USEC);
_timezoneOffsetSeconds = [[NSTimeZone systemTimeZone] secondsFromGMT];
}
return self;
}
+ (GDTCORClock *)snapshot {
return [[GDTCORClock alloc] init];
}
+ (instancetype)clockSnapshotInTheFuture:(uint64_t)millisInTheFuture {
GDTCORClock *snapshot = [self snapshot];
snapshot->_timeMillis += millisInTheFuture;
return snapshot;
}
- (BOOL)isAfter:(GDTCORClock *)otherClock {
// These clocks are trivially comparable when they share a kernel boot time.
if (_kernelBootTimeNanoseconds == otherClock->_kernelBootTimeNanoseconds) {
int64_t timeDiff = (_timeMillis + _timezoneOffsetSeconds) -
(otherClock->_timeMillis + otherClock->_timezoneOffsetSeconds);
return timeDiff > 0;
} else {
int64_t kernelBootTimeDiff =
otherClock->_kernelBootTimeNanoseconds - _kernelBootTimeNanoseconds;
// This isn't a great solution, but essentially, if the other clock's boot time is 'later', NO
// is returned. This can be altered by changing the system time and rebooting.
return kernelBootTimeDiff < 0 ? YES : NO;
}
}
- (int64_t)uptimeMilliseconds {
return self.uptimeNanoseconds / NSEC_PER_MSEC;
}
- (NSUInteger)hash {
return [@(_kernelBootTimeNanoseconds) hash] ^ [@(_uptimeNanoseconds) hash] ^
[@(_timeMillis) hash] ^ [@(_timezoneOffsetSeconds) hash];
}
- (BOOL)isEqual:(id)object {
return [self hash] == [object hash];
}
#pragma mark - NSSecureCoding
/** NSKeyedCoder key for timeMillis property. */
static NSString *const kGDTCORClockTimeMillisKey = @"GDTCORClockTimeMillis";
/** NSKeyedCoder key for timezoneOffsetMillis property. */
static NSString *const kGDTCORClockTimezoneOffsetSeconds = @"GDTCORClockTimezoneOffsetSeconds";
/** NSKeyedCoder key for _kernelBootTime ivar. */
static NSString *const kGDTCORClockKernelBootTime = @"GDTCORClockKernelBootTime";
/** NSKeyedCoder key for _uptimeNanoseconds ivar. */
static NSString *const kGDTCORClockUptime = @"GDTCORClockUptime";
+ (BOOL)supportsSecureCoding {
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
// TODO: If the kernelBootTimeNanoseconds is more recent, we need to change the kernel boot time
// and uptimeMillis ivars
_timeMillis = [aDecoder decodeInt64ForKey:kGDTCORClockTimeMillisKey];
_timezoneOffsetSeconds = [aDecoder decodeInt64ForKey:kGDTCORClockTimezoneOffsetSeconds];
_kernelBootTimeNanoseconds = [aDecoder decodeInt64ForKey:kGDTCORClockKernelBootTime];
_uptimeNanoseconds = [aDecoder decodeInt64ForKey:kGDTCORClockUptime];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeInt64:_timeMillis forKey:kGDTCORClockTimeMillisKey];
[aCoder encodeInt64:_timezoneOffsetSeconds forKey:kGDTCORClockTimezoneOffsetSeconds];
[aCoder encodeInt64:_kernelBootTimeNanoseconds forKey:kGDTCORClockKernelBootTime];
[aCoder encodeInt64:_uptimeNanoseconds forKey:kGDTCORClockUptime];
}
#pragma mark - Deprecated properties
- (int64_t)kernelBootTime {
return self.kernelBootTimeNanoseconds;
}
- (int64_t)uptime {
return self.uptimeNanoseconds;
}
@end

View File

@ -0,0 +1,55 @@
/*
* Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
volatile NSInteger GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevelErrors;
/** The console logger prefix. */
static NSString *kGDTCORConsoleLogger = @"[GoogleDataTransport]";
NSString *GDTCORMessageCodeEnumToString(GDTCORMessageCode code) {
return [[NSString alloc] initWithFormat:@"I-GDTCOR%06ld", (long)code];
}
void GDTCORLog(GDTCORMessageCode code, GDTCORLoggingLevel logLevel, NSString *format, ...) {
// Don't log anything in not debug builds.
#if !NDEBUG
if (logLevel >= GDTCORConsoleLoggerLoggingLevel) {
NSString *logFormat = [NSString stringWithFormat:@"%@[%@] %@", kGDTCORConsoleLogger,
GDTCORMessageCodeEnumToString(code), format];
va_list args;
va_start(args, format);
NSLogv(logFormat, args);
va_end(args);
}
#endif // !NDEBUG
}
void GDTCORLogAssert(
BOOL wasFatal, NSString *_Nonnull file, NSInteger line, NSString *_Nullable format, ...) {
// Don't log anything in not debug builds.
#if !NDEBUG
GDTCORMessageCode code = wasFatal ? GDTCORMCEFatalAssertion : GDTCORMCEGeneralError;
NSString *logFormat =
[NSString stringWithFormat:@"%@[%@] (%@:%ld) : %@", kGDTCORConsoleLogger,
GDTCORMessageCodeEnumToString(code), file, (long)line, format];
va_list args;
va_start(args, format);
NSLogv(logFormat, args);
va_end(args);
#endif // !NDEBUG
}

View File

@ -0,0 +1,101 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORDirectorySizeTracker.h"
@interface GDTCORDirectorySizeTracker ()
/** The observed directory path. */
@property(nonatomic, readonly) NSString *directoryPath;
/** The cached content size of the observed directory. */
@property(nonatomic, nullable) NSNumber *cachedSizeBytes;
@end
@implementation GDTCORDirectorySizeTracker
- (instancetype)initWithDirectoryPath:(NSString *)path {
self = [super init];
if (self) {
_directoryPath = path;
}
return self;
}
- (GDTCORStorageSizeBytes)directoryContentSize {
if (self.cachedSizeBytes == nil) {
self.cachedSizeBytes = @([self calculateDirectoryContentSize]);
}
return self.cachedSizeBytes.unsignedLongLongValue;
}
- (void)fileWasAddedAtPath:(NSString *)path withSize:(GDTCORStorageSizeBytes)fileSize {
if (![path hasPrefix:self.directoryPath]) {
// Ignore because the file is not inside the directory.
return;
}
self.cachedSizeBytes = @([self directoryContentSize] + fileSize);
}
- (void)fileWasRemovedAtPath:(NSString *)path withSize:(GDTCORStorageSizeBytes)fileSize {
if (![path hasPrefix:self.directoryPath]) {
// Ignore because the file is not inside the directory.
return;
}
self.cachedSizeBytes = @([self directoryContentSize] - fileSize);
}
- (void)resetCachedSize {
self.cachedSizeBytes = nil;
}
- (GDTCORStorageSizeBytes)calculateDirectoryContentSize {
NSArray *prefetchedProperties = @[ NSURLIsRegularFileKey, NSURLFileSizeKey ];
uint64_t totalBytes = 0;
NSURL *directoryURL = [NSURL fileURLWithPath:self.directoryPath];
NSDirectoryEnumerator *enumerator = [[NSFileManager defaultManager]
enumeratorAtURL:directoryURL
includingPropertiesForKeys:prefetchedProperties
options:NSDirectoryEnumerationSkipsHiddenFiles
errorHandler:^BOOL(NSURL *_Nonnull url, NSError *_Nonnull error) {
return YES;
}];
for (NSURL *fileURL in enumerator) {
@autoreleasepool {
NSNumber *isRegularFile;
[fileURL getResourceValue:&isRegularFile forKey:NSURLIsRegularFileKey error:nil];
if (isRegularFile.boolValue) {
totalBytes += [self fileSizeAtURL:fileURL];
}
}
}
return totalBytes;
}
- (GDTCORStorageSizeBytes)fileSizeAtURL:(NSURL *)fileURL {
NSNumber *fileSize;
[fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:nil];
return fileSize.unsignedLongLongValue;
}
@end

View File

@ -0,0 +1,92 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREndpoints.h"
static NSString *const kINTServerURL =
@"https://dummyapiverylong-dummy.google.com/dummy/api/very/long";
@implementation GDTCOREndpoints
+ (NSDictionary<NSNumber *, NSURL *> *)uploadURLs {
// These strings should be interleaved to construct the real URL. This is just to (hopefully)
// fool github URL scanning bots.
static NSURL *CCTServerURL;
static dispatch_once_t CCTOnceToken;
dispatch_once(&CCTOnceToken, ^{
const char *p1 = "hts/frbslgiggolai.o/0clgbth";
const char *p2 = "tp:/ieaeogn.ogepscmvc/o/ac";
const char URL[54] = {p1[0], p2[0], p1[1], p2[1], p1[2], p2[2], p1[3], p2[3], p1[4],
p2[4], p1[5], p2[5], p1[6], p2[6], p1[7], p2[7], p1[8], p2[8],
p1[9], p2[9], p1[10], p2[10], p1[11], p2[11], p1[12], p2[12], p1[13],
p2[13], p1[14], p2[14], p1[15], p2[15], p1[16], p2[16], p1[17], p2[17],
p1[18], p2[18], p1[19], p2[19], p1[20], p2[20], p1[21], p2[21], p1[22],
p2[22], p1[23], p2[23], p1[24], p2[24], p1[25], p2[25], p1[26], '\0'};
CCTServerURL = [NSURL URLWithString:[NSString stringWithUTF8String:URL]];
});
static NSURL *FLLServerURL;
static dispatch_once_t FLLOnceToken;
dispatch_once(&FLLOnceToken, ^{
const char *p1 = "hts/frbslgigp.ogepscmv/ieo/eaybtho";
const char *p2 = "tp:/ieaeogn-agolai.o/1frlglgc/aclg";
const char URL[69] = {p1[0], p2[0], p1[1], p2[1], p1[2], p2[2], p1[3], p2[3], p1[4],
p2[4], p1[5], p2[5], p1[6], p2[6], p1[7], p2[7], p1[8], p2[8],
p1[9], p2[9], p1[10], p2[10], p1[11], p2[11], p1[12], p2[12], p1[13],
p2[13], p1[14], p2[14], p1[15], p2[15], p1[16], p2[16], p1[17], p2[17],
p1[18], p2[18], p1[19], p2[19], p1[20], p2[20], p1[21], p2[21], p1[22],
p2[22], p1[23], p2[23], p1[24], p2[24], p1[25], p2[25], p1[26], p2[26],
p1[27], p2[27], p1[28], p2[28], p1[29], p2[29], p1[30], p2[30], p1[31],
p2[31], p1[32], p2[32], p1[33], p2[33], '\0'};
FLLServerURL = [NSURL URLWithString:[NSString stringWithUTF8String:URL]];
});
static NSURL *CSHServerURL;
static dispatch_once_t CSHOnceToken;
dispatch_once(&CSHOnceToken, ^{
// These strings should be interleaved to construct the real URL. This is just to (hopefully)
// fool github URL scanning bots.
const char *p1 = "hts/cahyiseot-agolai.o/1frlglgc/aclg";
const char *p2 = "tp:/rsltcrprsp.ogepscmv/ieo/eaybtho";
const char URL[72] = {p1[0], p2[0], p1[1], p2[1], p1[2], p2[2], p1[3], p2[3], p1[4],
p2[4], p1[5], p2[5], p1[6], p2[6], p1[7], p2[7], p1[8], p2[8],
p1[9], p2[9], p1[10], p2[10], p1[11], p2[11], p1[12], p2[12], p1[13],
p2[13], p1[14], p2[14], p1[15], p2[15], p1[16], p2[16], p1[17], p2[17],
p1[18], p2[18], p1[19], p2[19], p1[20], p2[20], p1[21], p2[21], p1[22],
p2[22], p1[23], p2[23], p1[24], p2[24], p1[25], p2[25], p1[26], p2[26],
p1[27], p2[27], p1[28], p2[28], p1[29], p2[29], p1[30], p2[30], p1[31],
p2[31], p1[32], p2[32], p1[33], p2[33], p1[34], p2[34], p1[35], '\0'};
CSHServerURL = [NSURL URLWithString:[NSString stringWithUTF8String:URL]];
});
static NSDictionary<NSNumber *, NSURL *> *uploadURLs;
static dispatch_once_t URLOnceToken;
dispatch_once(&URLOnceToken, ^{
uploadURLs = @{
@(kGDTCORTargetCCT) : CCTServerURL,
@(kGDTCORTargetFLL) : FLLServerURL,
@(kGDTCORTargetCSH) : CSHServerURL,
@(kGDTCORTargetINT) : [NSURL URLWithString:kINTServerURL]
};
});
return uploadURLs;
}
+ (nullable NSURL *)uploadURLForTarget:(GDTCORTarget)target {
NSDictionary<NSNumber *, NSURL *> *URLs = [self uploadURLs];
return [URLs objectForKey:@(target)];
}
@end

View File

@ -0,0 +1,170 @@
/*
* Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h"
@implementation GDTCOREvent
+ (NSString *)nextEventID {
// Replace special non-alphanumeric characters to avoid potential conflicts with storage logic.
return [[NSUUID UUID].UUIDString stringByReplacingOccurrencesOfString:@"-" withString:@""];
}
- (nullable instancetype)initWithMappingID:(NSString *)mappingID
productData:(nullable GDTCORProductData *)productData
target:(GDTCORTarget)target {
GDTCORAssert(mappingID.length > 0, @"Please give a valid mapping ID");
GDTCORAssert(target > 0, @"A target cannot be negative or 0");
if (mappingID.length == 0 || target <= 0) {
return nil;
}
self = [super init];
if (self) {
_eventID = [GDTCOREvent nextEventID];
_mappingID = mappingID;
_productData = productData;
_target = target;
_qosTier = GDTCOREventQosDefault;
_expirationDate = [NSDate dateWithTimeIntervalSinceNow:604800]; // 7 days.
GDTCORLogDebug(@"Event %@ created. ID:%@ mappingID: %@ target:%ld", self, _eventID, mappingID,
(long)target);
}
return self;
}
- (nullable instancetype)initWithMappingID:(NSString *)mappingID target:(GDTCORTarget)target {
return [self initWithMappingID:mappingID productData:nil target:target];
}
- (instancetype)copy {
GDTCOREvent *copy = [[GDTCOREvent alloc] initWithMappingID:_mappingID
productData:_productData
target:_target];
copy->_eventID = _eventID;
copy.dataObject = _dataObject;
copy.qosTier = _qosTier;
copy.clockSnapshot = _clockSnapshot;
copy.customBytes = _customBytes;
GDTCORLogDebug(@"Copying event %@ to event %@", self, copy);
return copy;
}
- (NSUInteger)hash {
// This loses some precision, but it's probably fine.
NSUInteger eventIDHash = [_eventID hash];
NSUInteger mappingIDHash = [_mappingID hash];
NSUInteger productDataHash = [_productData hash];
NSUInteger timeHash = [_clockSnapshot hash];
NSInteger serializedBytesHash = [_serializedDataObjectBytes hash];
return eventIDHash ^ mappingIDHash ^ productDataHash ^ _target ^ _qosTier ^ timeHash ^
serializedBytesHash;
}
- (BOOL)isEqual:(id)object {
return [self hash] == [object hash];
}
#pragma mark - Property overrides
- (void)setDataObject:(id<GDTCOREventDataObject>)dataObject {
// If you're looking here because of a performance issue in -transportBytes slowing the assignment
// of -dataObject, one way to address this is to add a queue to this class,
// dispatch_(barrier_ if concurrent)async here, and implement the getter with a dispatch_sync.
if (dataObject != _dataObject) {
_dataObject = dataObject;
}
self->_serializedDataObjectBytes = [dataObject transportBytes];
}
#pragma mark - NSSecureCoding and NSCoding Protocols
/** NSCoding key for eventID property. */
static NSString *kEventIDKey = @"GDTCOREventEventIDKey";
/** NSCoding key for mappingID property. */
static NSString *kMappingIDKey = @"GDTCOREventMappingIDKey";
/** NSCoding key for target property. */
static NSString *kTargetKey = @"GDTCOREventTargetKey";
/** NSCoding key for qosTier property. */
static NSString *kQoSTierKey = @"GDTCOREventQoSTierKey";
/** NSCoding key for clockSnapshot property. */
static NSString *kClockSnapshotKey = @"GDTCOREventClockSnapshotKey";
/** NSCoding key for expirationDate property. */
static NSString *kExpirationDateKey = @"GDTCOREventExpirationDateKey";
/** NSCoding key for serializedDataObjectBytes property. */
static NSString *kSerializedDataObjectBytes = @"GDTCOREventSerializedDataObjectBytesKey";
/** NSCoding key for customData property. */
static NSString *kCustomDataKey = @"GDTCOREventCustomDataKey";
/** NSCoding key for productData property. */
static NSString *kProductDataKey = @"GDTCOREventProductDataKey";
+ (BOOL)supportsSecureCoding {
return YES;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [self init];
if (self) {
_mappingID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kMappingIDKey];
_productData = [aDecoder decodeObjectOfClass:[GDTCORProductData class] forKey:kProductDataKey];
_target = [aDecoder decodeIntegerForKey:kTargetKey];
_eventID = [aDecoder decodeObjectOfClass:[NSString class] forKey:kEventIDKey]
?: [GDTCOREvent nextEventID];
_qosTier = [aDecoder decodeIntegerForKey:kQoSTierKey];
_clockSnapshot = [aDecoder decodeObjectOfClass:[GDTCORClock class] forKey:kClockSnapshotKey];
_customBytes = [aDecoder decodeObjectOfClass:[NSData class] forKey:kCustomDataKey];
_expirationDate = [aDecoder decodeObjectOfClass:[NSDate class] forKey:kExpirationDateKey];
_serializedDataObjectBytes = [aDecoder decodeObjectOfClass:[NSData class]
forKey:kSerializedDataObjectBytes];
if (!_serializedDataObjectBytes) {
return nil;
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_eventID forKey:kEventIDKey];
[aCoder encodeObject:_mappingID forKey:kMappingIDKey];
[aCoder encodeObject:_productData forKey:kProductDataKey];
[aCoder encodeInteger:_target forKey:kTargetKey];
[aCoder encodeInteger:_qosTier forKey:kQoSTierKey];
[aCoder encodeObject:_clockSnapshot forKey:kClockSnapshotKey];
[aCoder encodeObject:_customBytes forKey:kCustomDataKey];
[aCoder encodeObject:_expirationDate forKey:kExpirationDateKey];
[aCoder encodeObject:self.serializedDataObjectBytes forKey:kSerializedDataObjectBytes];
}
@end

View File

@ -0,0 +1,161 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage+Promises.h"
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadBatch.h"
@implementation GDTCORFlatFileStorage (Promises)
- (FBLPromise<NSSet<NSNumber *> *> *)batchIDsForTarget:(GDTCORTarget)target {
return [FBLPromise onQueue:self.storageQueue
wrapObjectCompletion:^(FBLPromiseObjectCompletion _Nonnull handler) {
[self batchIDsForTarget:target onComplete:handler];
}];
}
- (FBLPromise<NSNull *> *)removeBatchWithID:(NSNumber *)batchID deleteEvents:(BOOL)deleteEvents {
return [FBLPromise onQueue:self.storageQueue
wrapCompletion:^(FBLPromiseCompletion _Nonnull handler) {
[self removeBatchWithID:batchID deleteEvents:deleteEvents onComplete:handler];
}];
}
- (FBLPromise<NSNull *> *)removeBatchesWithIDs:(NSSet<NSNumber *> *)batchIDs
deleteEvents:(BOOL)deleteEvents {
NSMutableArray<FBLPromise *> *removeBatchPromises =
[NSMutableArray arrayWithCapacity:batchIDs.count];
for (NSNumber *batchID in batchIDs) {
[removeBatchPromises addObject:[self removeBatchWithID:batchID deleteEvents:deleteEvents]];
}
return [FBLPromise onQueue:self.storageQueue all:[removeBatchPromises copy]].thenOn(
self.storageQueue, ^id(id result) {
return [FBLPromise resolvedWith:[NSNull null]];
});
}
- (FBLPromise<NSNull *> *)removeAllBatchesForTarget:(GDTCORTarget)target
deleteEvents:(BOOL)deleteEvents {
return
[self batchIDsForTarget:target].thenOn(self.storageQueue, ^id(NSSet<NSNumber *> *batchIDs) {
if (batchIDs.count == 0) {
return [FBLPromise resolvedWith:[NSNull null]];
} else {
return [self removeBatchesWithIDs:batchIDs deleteEvents:NO];
}
});
}
- (FBLPromise<NSNumber *> *)hasEventsForTarget:(GDTCORTarget)target {
return [FBLPromise onQueue:self.storageQueue
wrapBoolCompletion:^(FBLPromiseBoolCompletion _Nonnull handler) {
[self hasEventsForTarget:target onComplete:handler];
}];
}
- (FBLPromise<GDTCORUploadBatch *> *)batchWithEventSelector:
(GDTCORStorageEventSelector *)eventSelector
batchExpiration:(NSDate *)expiration {
return [FBLPromise
onQueue:self.storageQueue
async:^(FBLPromiseFulfillBlock _Nonnull fulfill, FBLPromiseRejectBlock _Nonnull reject) {
[self batchWithEventSelector:eventSelector
batchExpiration:expiration
onComplete:^(NSNumber *_Nullable newBatchID,
NSSet<GDTCOREvent *> *_Nullable batchEvents) {
if (newBatchID == nil || batchEvents == nil) {
reject([self genericRejectedPromiseErrorWithReason:
@"There are no events for the selector."]);
} else {
fulfill([[GDTCORUploadBatch alloc] initWithBatchID:newBatchID
events:batchEvents]);
}
}];
}];
}
- (FBLPromise<NSNull *> *)fetchAndUpdateMetricsWithHandler:
(GDTCORMetricsMetadata * (^)(GDTCORMetricsMetadata *_Nullable fetchedMetadata,
NSError *_Nullable fetchError))handler {
return FBLPromise.doOn(self.storageQueue, ^id {
// Fetch the stored metrics metadata.
NSError *decodeError;
NSString *metricsMetadataPath =
[[[self class] libraryDataStoragePath] stringByAppendingPathComponent:@"metrics_metadata"];
GDTCORMetricsMetadata *decodedMetadata = (GDTCORMetricsMetadata *)GDTCORDecodeArchiveAtPath(
GDTCORMetricsMetadata.class, metricsMetadataPath, &decodeError);
// Update the metadata using the retrieved metadata.
GDTCORMetricsMetadata *updatedMetadata = handler(decodedMetadata, decodeError);
if (updatedMetadata == nil) {
// `nil` metadata is not expected and will be a no-op.
return nil;
}
if (![updatedMetadata isEqual:decodedMetadata]) {
// The metadata was updated so it needs to be saved.
// - Encode the updated metadata.
NSError *encodeError;
NSData *encodedMetadata = GDTCOREncodeArchive(updatedMetadata, nil, &encodeError);
if (encodeError) {
return encodeError;
}
// - Write the encoded metadata to disk.
NSError *writeError;
BOOL writeResult = GDTCORWriteDataToFile(encodedMetadata, metricsMetadataPath, &writeError);
if (writeResult == NO || writeError) {
return writeError;
}
}
return nil;
});
}
- (FBLPromise<GDTCORStorageMetadata *> *)fetchStorageMetadata {
return FBLPromise.asyncOn(self.storageQueue, ^(FBLPromiseFulfillBlock _Nonnull fulfill,
FBLPromiseRejectBlock _Nonnull reject) {
[self storageSizeWithCallback:^(GDTCORStorageSizeBytes storageSize) {
fulfill([GDTCORStorageMetadata metadataWithCurrentCacheSize:storageSize
maxCacheSize:kGDTCORFlatFileStorageSizeLimit]);
}];
});
}
// TODO: Move to a separate class/extension when needed in more places.
- (NSError *)genericRejectedPromiseErrorWithReason:(NSString *)reason {
return [NSError errorWithDomain:@"GDTCORFlatFileStorage"
code:-1
userInfo:@{NSLocalizedFailureReasonErrorKey : reason}];
}
@end
/// Stub used to force the linker to include the categories in this file.
void GDTCORInclude_GDTCORLogSourceMetrics_Internal_Category(void) {
}

View File

@ -0,0 +1,849 @@
/*
* Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageEventSelector.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORDirectorySizeTracker.h"
NS_ASSUME_NONNULL_BEGIN
/** A library data key this class uses to track batchIDs. */
static NSString *const gBatchIDCounterKey = @"GDTCORFlatFileStorageBatchIDCounter";
/** The separator used between metadata elements in filenames. */
static NSString *const kMetadataSeparator = @"-";
NSString *const kGDTCOREventComponentsEventIDKey = @"GDTCOREventComponentsEventIDKey";
NSString *const kGDTCOREventComponentsQoSTierKey = @"GDTCOREventComponentsQoSTierKey";
NSString *const kGDTCOREventComponentsMappingIDKey = @"GDTCOREventComponentsMappingIDKey";
NSString *const kGDTCOREventComponentsExpirationKey = @"GDTCOREventComponentsExpirationKey";
NSString *const kGDTCORBatchComponentsTargetKey = @"GDTCORBatchComponentsTargetKey";
NSString *const kGDTCORBatchComponentsBatchIDKey = @"GDTCORBatchComponentsBatchIDKey";
NSString *const kGDTCORBatchComponentsExpirationKey = @"GDTCORBatchComponentsExpirationKey";
NSString *const GDTCORFlatFileStorageErrorDomain = @"GDTCORFlatFileStorage";
const uint64_t kGDTCORFlatFileStorageSizeLimit = 20 * 1000 * 1000; // 20 MB.
@interface GDTCORFlatFileStorage ()
/** An instance of the size tracker to keep track of the disk space consumed by the storage. */
@property(nonatomic, readonly) GDTCORDirectorySizeTracker *sizeTracker;
@end
@implementation GDTCORFlatFileStorage
@synthesize sizeTracker = _sizeTracker;
@synthesize delegate = _delegate;
+ (void)load {
#if !NDEBUG
[[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetTest];
#endif // !NDEBUG
[[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetCCT];
[[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetFLL];
[[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetCSH];
[[GDTCORRegistrar sharedInstance] registerStorage:[self sharedInstance] target:kGDTCORTargetINT];
}
+ (instancetype)sharedInstance {
static GDTCORFlatFileStorage *sharedStorage;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedStorage = [[GDTCORFlatFileStorage alloc] init];
});
return sharedStorage;
}
- (instancetype)init {
self = [super init];
if (self) {
_storageQueue =
dispatch_queue_create("com.google.GDTCORFlatFileStorage", DISPATCH_QUEUE_SERIAL);
_uploadCoordinator = [GDTCORUploadCoordinator sharedInstance];
}
return self;
}
- (GDTCORDirectorySizeTracker *)sizeTracker {
if (_sizeTracker == nil) {
_sizeTracker =
[[GDTCORDirectorySizeTracker alloc] initWithDirectoryPath:GDTCORRootDirectory().path];
}
return _sizeTracker;
}
#pragma mark - GDTCORStorageProtocol
- (void)storeEvent:(GDTCOREvent *)event
onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion {
GDTCORLogDebug(@"Saving event: %@", event);
if (event == nil || event.serializedDataObjectBytes == nil) {
GDTCORLogDebug(@"%@", @"The event was nil, so it was not saved.");
if (completion) {
completion(NO, [NSError errorWithDomain:NSInternalInconsistencyException
code:-1
userInfo:nil]);
}
return;
}
if (!completion) {
completion = ^(BOOL wasWritten, NSError *_Nullable error) {
GDTCORLogDebug(@"event %@ stored. success:%@ error:%@", event, wasWritten ? @"YES" : @"NO",
error);
};
}
__block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid;
bgID = [[GDTCORApplication sharedApplication]
beginBackgroundTaskWithName:@"GDTStorage"
expirationHandler:^{
// End the background task if it's still valid.
[[GDTCORApplication sharedApplication] endBackgroundTask:bgID];
bgID = GDTCORBackgroundIdentifierInvalid;
}];
dispatch_async(_storageQueue, ^{
// Check that a backend implementation is available for this target.
GDTCORTarget target = event.target;
NSString *filePath = [GDTCORFlatFileStorage pathForTarget:target
eventID:event.eventID
qosTier:@(event.qosTier)
expirationDate:event.expirationDate
mappingID:event.mappingID];
NSError *error;
NSData *encodedEvent = GDTCOREncodeArchive(event, nil, &error);
if (error) {
completion(NO, error);
return;
}
// Check storage size limit before storing the event.
uint64_t resultingStorageSize = self.sizeTracker.directoryContentSize + encodedEvent.length;
if (resultingStorageSize > kGDTCORFlatFileStorageSizeLimit) {
NSError *error = [NSError
errorWithDomain:GDTCORFlatFileStorageErrorDomain
code:GDTCORFlatFileStorageErrorSizeLimitReached
userInfo:@{
NSLocalizedFailureReasonErrorKey : @"Storage size limit has been reached."
}];
if (self.delegate != nil) {
GDTCORLogDebug(@"Delegate notified that event with mapping ID %@ was dropped.",
event.mappingID);
[self.delegate storage:self didDropEvent:event];
}
completion(NO, error);
return;
}
// Write the encoded event to the file.
BOOL writeResult = GDTCORWriteDataToFile(encodedEvent, filePath, &error);
if (writeResult == NO || error) {
GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, error);
completion(NO, error);
return;
} else {
GDTCORLogDebug(@"Writing archive succeeded: %@", filePath);
completion(YES, nil);
}
// Notify size tracker.
[self.sizeTracker fileWasAddedAtPath:filePath withSize:encodedEvent.length];
// Check the QoS, if it's high priority, notify the target that it has a high priority event.
if (event.qosTier == GDTCOREventQoSFast) {
// TODO: Remove a direct dependency on the upload coordinator.
[self.uploadCoordinator forceUploadForTarget:target];
}
// Cancel or end the associated background task if it's still valid.
[[GDTCORApplication sharedApplication] endBackgroundTask:bgID];
bgID = GDTCORBackgroundIdentifierInvalid;
});
}
- (void)batchWithEventSelector:(nonnull GDTCORStorageEventSelector *)eventSelector
batchExpiration:(nonnull NSDate *)expiration
onComplete:
(nonnull void (^)(NSNumber *_Nullable batchID,
NSSet<GDTCOREvent *> *_Nullable events))onComplete {
dispatch_queue_t queue = _storageQueue;
void (^onPathsForTargetComplete)(NSNumber *, NSSet<NSString *> *_Nonnull) = ^(
NSNumber *batchID, NSSet<NSString *> *_Nonnull paths) {
dispatch_async(queue, ^{
NSMutableSet<GDTCOREvent *> *events = [[NSMutableSet alloc] init];
for (NSString *eventPath in paths) {
NSError *error;
GDTCOREvent *event =
(GDTCOREvent *)GDTCORDecodeArchiveAtPath([GDTCOREvent class], eventPath, &error);
if (event == nil || error) {
GDTCORLogDebug(@"Error deserializing event: %@", error);
[[NSFileManager defaultManager] removeItemAtPath:eventPath error:nil];
continue;
} else {
NSString *fileName = [eventPath lastPathComponent];
NSString *batchPath =
[GDTCORFlatFileStorage batchPathForTarget:eventSelector.selectedTarget
batchID:batchID
expirationDate:expiration];
[[NSFileManager defaultManager] createDirectoryAtPath:batchPath
withIntermediateDirectories:YES
attributes:nil
error:nil];
NSString *destinationPath = [batchPath stringByAppendingPathComponent:fileName];
error = nil;
[[NSFileManager defaultManager] moveItemAtPath:eventPath
toPath:destinationPath
error:&error];
if (error) {
GDTCORLogDebug(@"An event file wasn't moveable into the batch directory: %@", error);
}
[events addObject:event];
}
}
if (onComplete) {
if (events.count == 0) {
onComplete(nil, nil);
} else {
onComplete(batchID, events);
}
}
});
};
void (^onBatchIDFetchComplete)(NSNumber *) = ^(NSNumber *batchID) {
dispatch_async(queue, ^{
if (batchID == nil) {
if (onComplete) {
onComplete(nil, nil);
return;
}
}
[self pathsForTarget:eventSelector.selectedTarget
eventIDs:eventSelector.selectedEventIDs
qosTiers:eventSelector.selectedQosTiers
mappingIDs:eventSelector.selectedMappingIDs
onComplete:^(NSSet<NSString *> *_Nonnull paths) {
onPathsForTargetComplete(batchID, paths);
}];
});
};
[self nextBatchID:^(NSNumber *_Nullable batchID) {
if (batchID == nil) {
if (onComplete) {
onComplete(nil, nil);
}
} else {
onBatchIDFetchComplete(batchID);
}
}];
}
- (void)removeBatchWithID:(nonnull NSNumber *)batchID
deleteEvents:(BOOL)deleteEvents
onComplete:(void (^_Nullable)(void))onComplete {
dispatch_async(_storageQueue, ^{
[self syncThreadUnsafeRemoveBatchWithID:batchID deleteEvents:deleteEvents];
if (onComplete) {
onComplete();
}
});
}
- (void)batchIDsForTarget:(GDTCORTarget)target
onComplete:(nonnull void (^)(NSSet<NSNumber *> *_Nullable))onComplete {
dispatch_async(_storageQueue, ^{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray<NSString *> *batchPaths =
[fileManager contentsOfDirectoryAtPath:[GDTCORFlatFileStorage batchDataStoragePath]
error:&error];
if (error || batchPaths.count == 0) {
if (onComplete) {
onComplete(nil);
}
return;
}
NSMutableSet<NSNumber *> *batchIDs = [[NSMutableSet alloc] init];
for (NSString *path in batchPaths) {
NSDictionary<NSString *, id> *components = [self batchComponentsFromFilename:path];
NSNumber *targetNumber = components[kGDTCORBatchComponentsTargetKey];
NSNumber *batchID = components[kGDTCORBatchComponentsBatchIDKey];
if (batchID != nil && targetNumber.intValue == target) {
[batchIDs addObject:batchID];
}
}
if (onComplete) {
onComplete(batchIDs);
}
});
}
- (void)libraryDataForKey:(nonnull NSString *)key
onFetchComplete:(nonnull void (^)(NSData *_Nullable, NSError *_Nullable))onFetchComplete
setNewValue:(NSData *_Nullable (^_Nullable)(void))setValueBlock {
dispatch_async(_storageQueue, ^{
NSString *dataPath = [[[self class] libraryDataStoragePath] stringByAppendingPathComponent:key];
NSError *error;
NSData *data = [NSData dataWithContentsOfFile:dataPath options:0 error:&error];
if (onFetchComplete) {
onFetchComplete(data, error);
}
if (setValueBlock) {
NSData *newValue = setValueBlock();
// The -isKindOfClass check is necessary because without an explicit 'return nil' in the block
// the implicit return value will be the block itself. The compiler doesn't detect this.
if (newValue != nil && [newValue isKindOfClass:[NSData class]] && newValue.length) {
NSError *newValueError;
if ([newValue writeToFile:dataPath options:NSDataWritingAtomic error:&newValueError]) {
// Update storage size.
[self.sizeTracker fileWasRemovedAtPath:dataPath withSize:data.length];
[self.sizeTracker fileWasAddedAtPath:dataPath withSize:newValue.length];
} else {
GDTCORLogDebug(@"Error writing new value in libraryDataForKey: %@", newValueError);
}
}
}
});
}
- (void)storeLibraryData:(NSData *)data
forKey:(nonnull NSString *)key
onComplete:(nullable void (^)(NSError *_Nullable error))onComplete {
if (!data || data.length <= 0) {
if (onComplete) {
onComplete([NSError errorWithDomain:NSInternalInconsistencyException code:-1 userInfo:nil]);
}
return;
}
dispatch_async(_storageQueue, ^{
NSError *error;
NSString *dataPath = [[[self class] libraryDataStoragePath] stringByAppendingPathComponent:key];
if ([data writeToFile:dataPath options:NSDataWritingAtomic error:&error]) {
[self.sizeTracker fileWasAddedAtPath:dataPath withSize:data.length];
}
if (onComplete) {
onComplete(error);
}
});
}
- (void)removeLibraryDataForKey:(nonnull NSString *)key
onComplete:(nonnull void (^)(NSError *_Nullable error))onComplete {
dispatch_async(_storageQueue, ^{
NSError *error;
NSString *dataPath = [[[self class] libraryDataStoragePath] stringByAppendingPathComponent:key];
GDTCORStorageSizeBytes fileSize =
[self.sizeTracker fileSizeAtURL:[NSURL fileURLWithPath:dataPath]];
if ([[NSFileManager defaultManager] fileExistsAtPath:dataPath]) {
if ([[NSFileManager defaultManager] removeItemAtPath:dataPath error:&error]) {
[self.sizeTracker fileWasRemovedAtPath:dataPath withSize:fileSize];
}
if (onComplete) {
onComplete(error);
}
}
});
}
- (void)hasEventsForTarget:(GDTCORTarget)target onComplete:(void (^)(BOOL hasEvents))onComplete {
dispatch_async(_storageQueue, ^{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *targetPath = [NSString
stringWithFormat:@"%@/%ld", [GDTCORFlatFileStorage eventDataStoragePath], (long)target];
[fileManager createDirectoryAtPath:targetPath
withIntermediateDirectories:YES
attributes:nil
error:nil];
NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:targetPath];
BOOL hasEventAtLeastOneEvent = [enumerator nextObject] != nil;
if (onComplete) {
onComplete(hasEventAtLeastOneEvent);
}
});
}
- (void)checkForExpirations {
dispatch_async(_storageQueue, ^{
GDTCORLogDebug(@"%@", @"Checking for expired events and batches");
NSTimeInterval now = [NSDate date].timeIntervalSince1970;
NSFileManager *fileManager = [NSFileManager defaultManager];
// TODO: Storage may not have enough context to remove batches because a batch may be being
// uploaded but the storage has not context of it.
// Find expired batches and move their events back to the main storage.
// If a batch contains expired events they are expected to be removed further in the method
// together with other expired events in the main storage.
NSString *batchDataPath = [GDTCORFlatFileStorage batchDataStoragePath];
NSArray<NSString *> *batchDataPaths = [fileManager contentsOfDirectoryAtPath:batchDataPath
error:nil];
for (NSString *path in batchDataPaths) {
@autoreleasepool {
NSString *fileName = [path lastPathComponent];
NSDictionary<NSString *, id> *batchComponents = [self batchComponentsFromFilename:fileName];
NSDate *expirationDate = batchComponents[kGDTCORBatchComponentsExpirationKey];
NSNumber *batchID = batchComponents[kGDTCORBatchComponentsBatchIDKey];
if (expirationDate != nil && expirationDate.timeIntervalSince1970 < now && batchID != nil) {
NSNumber *batchID = batchComponents[kGDTCORBatchComponentsBatchIDKey];
// Move all events from the expired batch back to the main storage.
[self syncThreadUnsafeRemoveBatchWithID:batchID deleteEvents:NO];
}
}
}
// Find expired events and remove them from the storage.
NSMutableSet<GDTCOREvent *> *expiredEvents = [NSMutableSet set];
NSString *eventDataPath = [GDTCORFlatFileStorage eventDataStoragePath];
NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtPath:eventDataPath];
NSString *path;
while (YES) {
@autoreleasepool {
// Call `[enumerator nextObject]` under autorelease pool to make sure all autoreleased
// objects created under the hood are released on each iteration end to avoid unnecessary
// memory growth.
path = [enumerator nextObject];
if (path == nil) {
break;
}
NSString *fileName = [path lastPathComponent];
NSDictionary<NSString *, id> *eventComponents = [self eventComponentsFromFilename:fileName];
NSDate *expirationDate = eventComponents[kGDTCOREventComponentsExpirationKey];
if (expirationDate != nil && expirationDate.timeIntervalSince1970 < now) {
NSString *pathToDelete = [eventDataPath stringByAppendingPathComponent:path];
// Decode the expired event from the path to be deleted.
NSError *decodeError;
GDTCOREvent *event = (GDTCOREvent *)GDTCORDecodeArchiveAtPath([GDTCOREvent class],
pathToDelete, &decodeError);
if (event == nil || decodeError) {
GDTCORLogDebug(@"Error deserializing event while checking for expired events: %@",
decodeError);
event = nil;
}
// Remove the path to be deleted, adding the decoded event to the
// event set if the removal was successful.
NSError *removeError;
[fileManager removeItemAtPath:pathToDelete error:&removeError];
if (removeError != nil) {
GDTCORLogDebug(@"There was an error deleting an expired item: %@", removeError);
} else {
GDTCORLogDebug(@"Item deleted because it expired: %@", pathToDelete);
if (event) {
[expiredEvents addObject:event];
}
}
}
}
}
if (self.delegate != nil && [expiredEvents count] > 0) {
GDTCORLogDebug(@"Delegate notified that %@ events were dropped.", @(expiredEvents.count));
[self.delegate storage:self didRemoveExpiredEvents:[expiredEvents copy]];
}
[self.sizeTracker resetCachedSize];
});
}
- (void)storageSizeWithCallback:(void (^)(uint64_t storageSize))onComplete {
if (!onComplete) {
return;
}
dispatch_async(_storageQueue, ^{
onComplete([self.sizeTracker directoryContentSize]);
});
}
#pragma mark - Private not thread safe methods
/** Looks for directory paths containing events for a batch with the specified ID.
* @param batchID A batch ID.
* @param outError A pointer to `NSError *` to assign as possible error to.
* @return An array of an array of paths to directories for event batches with a specified batch ID
* or `nil` in the case of an error. Usually returns a single path but potentially return more in
* cases when the app is terminated while uploading a batch.
*/
- (nullable NSArray<NSString *> *)batchDirPathsForBatchID:(NSNumber *)batchID
error:(NSError **_Nonnull)outError {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray<NSString *> *batches =
[fileManager contentsOfDirectoryAtPath:[GDTCORFlatFileStorage batchDataStoragePath]
error:&error];
if (batches == nil) {
*outError = error;
GDTCORLogDebug(@"Failed to find event file paths for batchID: %@, error: %@", batchID, error);
return nil;
}
NSMutableArray<NSString *> *batchDirPaths = [NSMutableArray array];
for (NSString *path in batches) {
NSDictionary<NSString *, id> *components = [self batchComponentsFromFilename:path];
NSNumber *pathBatchID = components[kGDTCORBatchComponentsBatchIDKey];
if ([pathBatchID isEqual:batchID]) {
NSString *batchDirPath =
[[GDTCORFlatFileStorage batchDataStoragePath] stringByAppendingPathComponent:path];
[batchDirPaths addObject:batchDirPath];
}
}
return [batchDirPaths copy];
}
/** Makes a copy of the contents of a directory to a directory at the specified path.*/
- (BOOL)moveContentsOfDirectoryAtPath:(NSString *)sourcePath
to:(NSString *)destinationPath
error:(NSError **_Nonnull)outError {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray<NSString *> *contentsPaths = [fileManager contentsOfDirectoryAtPath:sourcePath
error:&error];
if (contentsPaths == nil) {
*outError = error;
return NO;
}
NSMutableArray<NSError *> *errors = [NSMutableArray array];
for (NSString *path in contentsPaths) {
NSString *contentDestinationPath = [destinationPath stringByAppendingPathComponent:path];
NSString *contentSourcePath = [sourcePath stringByAppendingPathComponent:path];
NSError *moveError;
if (![fileManager moveItemAtPath:contentSourcePath
toPath:contentDestinationPath
error:&moveError] &&
moveError) {
[errors addObject:moveError];
}
}
if (errors.count == 0) {
return YES;
} else {
NSError *combinedError = [NSError errorWithDomain:@"GDTCORFlatFileStorage"
code:-1
userInfo:@{NSUnderlyingErrorKey : errors}];
*outError = combinedError;
return NO;
}
}
- (void)syncThreadUnsafeRemoveBatchWithID:(nonnull NSNumber *)batchID
deleteEvents:(BOOL)deleteEvents {
NSError *error;
NSArray<NSString *> *batchDirPaths = [self batchDirPathsForBatchID:batchID error:&error];
if (batchDirPaths == nil) {
return;
}
NSFileManager *fileManager = [NSFileManager defaultManager];
void (^removeBatchDir)(NSString *batchDirPath) = ^(NSString *batchDirPath) {
NSError *error;
if ([fileManager removeItemAtPath:batchDirPath error:&error]) {
GDTCORLogDebug(@"Batch removed at path: %@", batchDirPath);
} else {
GDTCORLogDebug(@"Failed to remove batch at path: %@", batchDirPath);
}
};
for (NSString *batchDirPath in batchDirPaths) {
@autoreleasepool {
if (deleteEvents) {
removeBatchDir(batchDirPath);
} else {
NSString *batchDirName = [batchDirPath lastPathComponent];
NSDictionary<NSString *, id> *components = [self batchComponentsFromFilename:batchDirName];
NSString *targetValue = [components[kGDTCORBatchComponentsTargetKey] stringValue];
NSString *destinationPath;
if (targetValue) {
destinationPath = [[GDTCORFlatFileStorage eventDataStoragePath]
stringByAppendingPathComponent:targetValue];
}
// `- [NSFileManager moveItemAtPath:toPath:error:]` method fails if an item by the
// destination path already exists (which usually is the case for the current method). Move
// the events one by one instead.
if (destinationPath && [self moveContentsOfDirectoryAtPath:batchDirPath
to:destinationPath
error:&error]) {
GDTCORLogDebug(@"Batched events at path: %@ moved back to the storage: %@", batchDirPath,
destinationPath);
} else {
GDTCORLogDebug(@"Error encountered whilst moving events back: %@", error);
}
// Even if not all events where moved back to the storage, there is not much can be done at
// this point, so cleanup batch directory now to avoid cluttering.
removeBatchDir(batchDirPath);
}
}
}
[self.sizeTracker resetCachedSize];
}
#pragma mark - Private helper methods
+ (NSString *)eventDataStoragePath {
static NSString *eventDataPath;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
eventDataPath = [NSString stringWithFormat:@"%@/%@/gdt_event_data", GDTCORRootDirectory().path,
NSStringFromClass([self class])];
});
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:eventDataPath
withIntermediateDirectories:YES
attributes:0
error:&error];
GDTCORAssert(error == nil, @"Creating the library data path failed: %@", error);
return eventDataPath;
}
+ (NSString *)batchDataStoragePath {
static NSString *batchDataPath;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
batchDataPath = [NSString stringWithFormat:@"%@/%@/gdt_batch_data", GDTCORRootDirectory().path,
NSStringFromClass([self class])];
});
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:batchDataPath
withIntermediateDirectories:YES
attributes:0
error:&error];
GDTCORAssert(error == nil, @"Creating the batch data path failed: %@", error);
return batchDataPath;
}
+ (NSString *)libraryDataStoragePath {
static NSString *libraryDataPath;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
libraryDataPath =
[NSString stringWithFormat:@"%@/%@/gdt_library_data", GDTCORRootDirectory().path,
NSStringFromClass([self class])];
});
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:libraryDataPath
withIntermediateDirectories:YES
attributes:0
error:&error];
GDTCORAssert(error == nil, @"Creating the library data path failed: %@", error);
return libraryDataPath;
}
+ (NSString *)batchPathForTarget:(GDTCORTarget)target
batchID:(NSNumber *)batchID
expirationDate:(NSDate *)expirationDate {
return
[NSString stringWithFormat:@"%@/%ld%@%@%@%llu", [GDTCORFlatFileStorage batchDataStoragePath],
(long)target, kMetadataSeparator, batchID, kMetadataSeparator,
((uint64_t)expirationDate.timeIntervalSince1970)];
}
+ (NSString *)pathForTarget:(GDTCORTarget)target
eventID:(NSString *)eventID
qosTier:(NSNumber *)qosTier
expirationDate:(NSDate *)expirationDate
mappingID:(NSString *)mappingID {
NSMutableCharacterSet *allowedChars = [[NSCharacterSet alphanumericCharacterSet] mutableCopy];
[allowedChars addCharactersInString:kMetadataSeparator];
mappingID = [mappingID stringByAddingPercentEncodingWithAllowedCharacters:allowedChars];
return [NSString stringWithFormat:@"%@/%ld/%@%@%@%@%llu%@%@",
[GDTCORFlatFileStorage eventDataStoragePath], (long)target,
eventID, kMetadataSeparator, qosTier, kMetadataSeparator,
((uint64_t)expirationDate.timeIntervalSince1970),
kMetadataSeparator, mappingID];
}
- (void)pathsForTarget:(GDTCORTarget)target
eventIDs:(nullable NSSet<NSString *> *)eventIDs
qosTiers:(nullable NSSet<NSNumber *> *)qosTiers
mappingIDs:(nullable NSSet<NSString *> *)mappingIDs
onComplete:(void (^)(NSSet<NSString *> *paths))onComplete {
void (^completion)(NSSet<NSString *> *) = onComplete == nil ? ^(NSSet<NSString *> *paths){} : onComplete;
dispatch_async(_storageQueue, ^{
NSMutableSet<NSString *> *paths = [[NSMutableSet alloc] init];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *targetPath = [NSString
stringWithFormat:@"%@/%ld", [GDTCORFlatFileStorage eventDataStoragePath], (long)target];
[fileManager createDirectoryAtPath:targetPath
withIntermediateDirectories:YES
attributes:nil
error:nil];
NSError *error;
NSArray<NSString *> *dirPaths = [fileManager contentsOfDirectoryAtPath:targetPath error:&error];
if (error) {
GDTCORLogDebug(@"There was an error reading the contents of the target path: %@", error);
completion(paths);
return;
}
BOOL checkingIDs = eventIDs.count > 0;
BOOL checkingQosTiers = qosTiers.count > 0;
BOOL checkingMappingIDs = mappingIDs.count > 0;
BOOL checkingAnything = checkingIDs == NO && checkingQosTiers == NO && checkingMappingIDs == NO;
for (NSString *path in dirPaths) {
// Skip hidden files that are created as part of atomic file creation.
if ([path hasPrefix:@"."]) {
continue;
}
NSString *filePath = [targetPath stringByAppendingPathComponent:path];
if (checkingAnything) {
[paths addObject:filePath];
continue;
}
NSString *filename = [path lastPathComponent];
NSDictionary<NSString *, id> *eventComponents = [self eventComponentsFromFilename:filename];
if (!eventComponents) {
GDTCORLogDebug(@"There was an error reading the filename components: %@", eventComponents);
continue;
}
NSString *eventID = eventComponents[kGDTCOREventComponentsEventIDKey];
NSNumber *qosTier = eventComponents[kGDTCOREventComponentsQoSTierKey];
NSString *mappingID = eventComponents[kGDTCOREventComponentsMappingIDKey];
NSNumber *eventIDMatch = checkingIDs ? @([eventIDs containsObject:eventID]) : nil;
NSNumber *qosTierMatch = checkingQosTiers ? @([qosTiers containsObject:qosTier]) : nil;
NSNumber *mappingIDMatch =
checkingMappingIDs
? @([mappingIDs containsObject:[mappingID stringByRemovingPercentEncoding]])
: nil;
if ((eventIDMatch == nil || eventIDMatch.boolValue) &&
(qosTierMatch == nil || qosTierMatch.boolValue) &&
(mappingIDMatch == nil || mappingIDMatch.boolValue)) {
[paths addObject:filePath];
}
}
completion(paths);
});
}
- (void)nextBatchID:(void (^)(NSNumber *_Nullable batchID))nextBatchID {
__block int32_t lastBatchID = -1;
[self libraryDataForKey:gBatchIDCounterKey
onFetchComplete:^(NSData *_Nullable data, NSError *_Nullable getValueError) {
if (!getValueError) {
[data getBytes:(void *)&lastBatchID length:sizeof(int32_t)];
}
if (data == nil) {
lastBatchID = 0;
}
if (nextBatchID) {
nextBatchID(@(lastBatchID));
}
}
setNewValue:^NSData *_Nullable(void) {
if (lastBatchID != -1) {
int32_t incrementedValue = lastBatchID + 1;
return [NSData dataWithBytes:&incrementedValue length:sizeof(int32_t)];
}
return nil;
}];
}
- (nullable NSDictionary<NSString *, id> *)eventComponentsFromFilename:(NSString *)fileName {
NSArray<NSString *> *components = [fileName componentsSeparatedByString:kMetadataSeparator];
if (components.count >= 4) {
NSString *eventID = components[0];
NSNumber *qosTier = @(components[1].integerValue);
NSDate *expirationDate = [NSDate dateWithTimeIntervalSince1970:components[2].longLongValue];
NSString *mappingID = [[components subarrayWithRange:NSMakeRange(3, components.count - 3)]
componentsJoinedByString:kMetadataSeparator];
if (eventID == nil || qosTier == nil || mappingID == nil || expirationDate == nil) {
GDTCORLogDebug(@"There was an error parsing the event filename components: %@", components);
return nil;
}
return @{
kGDTCOREventComponentsEventIDKey : eventID,
kGDTCOREventComponentsQoSTierKey : qosTier,
kGDTCOREventComponentsExpirationKey : expirationDate,
kGDTCOREventComponentsMappingIDKey : mappingID
};
}
GDTCORLogDebug(@"The event filename could not be split: %@", fileName);
return nil;
}
- (nullable NSDictionary<NSString *, id> *)batchComponentsFromFilename:(NSString *)fileName {
NSArray<NSString *> *components = [fileName componentsSeparatedByString:kMetadataSeparator];
if (components.count == 3) {
NSNumber *target = @(components[0].integerValue);
NSNumber *batchID = @(components[1].integerValue);
NSDate *expirationDate = [NSDate dateWithTimeIntervalSince1970:components[2].doubleValue];
if (target == nil || batchID == nil || expirationDate == nil) {
GDTCORLogDebug(@"There was an error parsing the batch filename components: %@", components);
return nil;
}
return @{
kGDTCORBatchComponentsTargetKey : target,
kGDTCORBatchComponentsBatchIDKey : batchID,
kGDTCORBatchComponentsExpirationKey : expirationDate
};
}
GDTCORLogDebug(@"The batch filename could not be split: %@", fileName);
return nil;
}
#pragma mark - GDTCORLifecycleProtocol
- (void)appWillBackground:(GDTCORApplication *)app {
dispatch_async(_storageQueue, ^{
// Immediately request a background task to run until the end of the current queue of work,
// and cancel it once the work is done.
__block GDTCORBackgroundIdentifier bgID =
[app beginBackgroundTaskWithName:@"GDTStorage"
expirationHandler:^{
[app endBackgroundTask:bgID];
bgID = GDTCORBackgroundIdentifierInvalid;
}];
// End the background task if it's still valid.
[app endBackgroundTask:bgID];
bgID = GDTCORBackgroundIdentifierInvalid;
});
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,118 @@
/*
* 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer_Private.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h"
@implementation GDTCORLifecycle
+ (void)load {
[self sharedInstance];
}
/** Creates/returns the singleton instance of this class.
*
* @return The singleton instance of this class.
*/
+ (instancetype)sharedInstance {
static GDTCORLifecycle *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[GDTCORLifecycle alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(applicationDidEnterBackgroundNotification:)
name:kGDTCORApplicationDidEnterBackgroundNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(applicationWillEnterForegroundNotification:)
name:kGDTCORApplicationWillEnterForegroundNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(applicationWillTerminateNotification:)
name:kGDTCORApplicationWillTerminateNotification
object:nil];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)applicationDidEnterBackgroundNotification:(NSNotification *)notification {
GDTCORApplication *application = [GDTCORApplication sharedApplication];
if ([[GDTCORTransformer sharedInstance] respondsToSelector:@selector(appWillBackground:)]) {
GDTCORLogDebug(@"%@", @"Signaling GDTCORTransformer that the app is backgrounding.");
[[GDTCORTransformer sharedInstance] appWillBackground:application];
}
if ([[GDTCORUploadCoordinator sharedInstance] respondsToSelector:@selector(appWillBackground:)]) {
GDTCORLogDebug(@"%@", @"Signaling GDTCORUploadCoordinator that the app is backgrounding.");
[[GDTCORUploadCoordinator sharedInstance] appWillBackground:application];
}
if ([[GDTCORRegistrar sharedInstance] respondsToSelector:@selector(appWillBackground:)]) {
GDTCORLogDebug(@"%@", @"Signaling GDTCORRegistrar that the app is backgrounding.");
[[GDTCORRegistrar sharedInstance] appWillBackground:application];
}
}
- (void)applicationWillEnterForegroundNotification:(NSNotification *)notification {
GDTCORApplication *application = [GDTCORApplication sharedApplication];
if ([[GDTCORTransformer sharedInstance] respondsToSelector:@selector(appWillForeground:)]) {
GDTCORLogDebug(@"%@", @"Signaling GDTCORTransformer that the app is foregrounding.");
[[GDTCORTransformer sharedInstance] appWillForeground:application];
}
if ([[GDTCORUploadCoordinator sharedInstance] respondsToSelector:@selector(appWillForeground:)]) {
GDTCORLogDebug(@"%@", @"Signaling GDTCORUploadCoordinator that the app is foregrounding.");
[[GDTCORUploadCoordinator sharedInstance] appWillForeground:application];
}
if ([[GDTCORRegistrar sharedInstance] respondsToSelector:@selector(appWillForeground:)]) {
GDTCORLogDebug(@"%@", @"Signaling GDTCORRegistrar that the app is foregrounding.");
[[GDTCORRegistrar sharedInstance] appWillForeground:application];
}
}
- (void)applicationWillTerminateNotification:(NSNotification *)notification {
GDTCORApplication *application = [GDTCORApplication sharedApplication];
if ([[GDTCORTransformer sharedInstance] respondsToSelector:@selector(appWillTerminate:)]) {
GDTCORLogDebug(@"%@", @"Signaling GDTCORTransformer that the app is terminating.");
[[GDTCORTransformer sharedInstance] appWillTerminate:application];
}
if ([[GDTCORUploadCoordinator sharedInstance] respondsToSelector:@selector(appWillTerminate:)]) {
GDTCORLogDebug(@"%@", @"Signaling GDTCORUploadCoordinator that the app is terminating.");
[[GDTCORUploadCoordinator sharedInstance] appWillTerminate:application];
}
if ([[GDTCORRegistrar sharedInstance] respondsToSelector:@selector(appWillTerminate:)]) {
GDTCORLogDebug(@"%@", @"Signaling GDTCORRegistrar that the app is terminating.");
[[GDTCORRegistrar sharedInstance] appWillTerminate:application];
}
}
@end

View File

@ -0,0 +1,184 @@
// Copyright 2022 Google LLC
//
// 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
static NSString *const kDroppedEventCounterByLogSource = @"droppedEventCounterByLogSource";
typedef NSDictionary<NSNumber *, NSNumber *> GDTCORDroppedEventCounter;
@interface GDTCORLogSourceMetrics ()
/// A dictionary of log sources that map to counters that reflect the number of events dropped for a
/// given set of reasons (``GDTCOREventDropReason``).
@property(nonatomic, readonly)
NSDictionary<NSString *, GDTCORDroppedEventCounter *> *droppedEventCounterByLogSource;
@end
@implementation GDTCORLogSourceMetrics
+ (instancetype)metrics {
return [[self alloc] initWithDroppedEventCounterByLogSource:@{}];
}
+ (instancetype)metricsWithEvents:(NSArray<GDTCOREvent *> *)events
droppedForReason:(GDTCOREventDropReason)reason {
NSMutableDictionary<NSString *, GDTCORDroppedEventCounter *> *eventCounterByLogSource =
[NSMutableDictionary dictionary];
for (GDTCOREvent *event in [events copy]) {
// Dropped events with a `nil` or empty mapping ID (log source) are not recorded.
if (event.mappingID.length == 0) {
continue;
}
// Get the dropped event counter for the event's mapping ID (log source).
// If the dropped event counter for this event's mapping ID is `nil`,
// an empty mutable counter is returned.
NSMutableDictionary<NSNumber *, NSNumber *> *eventCounter = [NSMutableDictionary
dictionaryWithDictionary:eventCounterByLogSource[event.mappingID] ?: @{}];
// Increment the log source metrics for the given reason.
NSInteger currentEventCountForReason = [eventCounter[@(reason)] integerValue];
NSInteger updatedEventCountForReason = currentEventCountForReason + 1;
eventCounter[@(reason)] = @(updatedEventCountForReason);
// Update the mapping ID's (log source's) event counter with an immutable
// copy of the updated counter.
eventCounterByLogSource[event.mappingID] = [eventCounter copy];
}
return [[self alloc] initWithDroppedEventCounterByLogSource:[eventCounterByLogSource copy]];
}
- (instancetype)initWithDroppedEventCounterByLogSource:
(NSDictionary<NSString *, GDTCORDroppedEventCounter *> *)droppedEventCounterByLogSource {
self = [super init];
if (self) {
_droppedEventCounterByLogSource = [droppedEventCounterByLogSource copy];
}
return self;
}
- (GDTCORLogSourceMetrics *)logSourceMetricsByMergingWithLogSourceMetrics:
(GDTCORLogSourceMetrics *)metrics {
// Create new log source metrics by merging the current metrics with the given metrics.
NSDictionary<NSString *, GDTCORDroppedEventCounter *> *mergedEventCounterByLogSource = [[self
class] dictionaryByMergingDictionary:self.droppedEventCounterByLogSource
withOtherDictionary:metrics.droppedEventCounterByLogSource
uniquingKeysWithBlock:^NSDictionary *(NSDictionary *eventCounter1,
NSDictionary *eventCounter2) {
return [[self class]
dictionaryByMergingDictionary:eventCounter1
withOtherDictionary:eventCounter2
uniquingKeysWithBlock:^NSNumber *(NSNumber *eventCount1,
NSNumber *eventCount2) {
return @(eventCount1.integerValue + eventCount2.integerValue);
}];
}];
return
[[[self class] alloc] initWithDroppedEventCounterByLogSource:mergedEventCounterByLogSource];
}
/// Creates a new dictionary by merging together two given dictionaries.
/// @param dictionary A dictionary for merging.
/// @param otherDictionary Another dictionary for merging.
/// @param block A block that is called with the values for any duplicate keys that are encountered.
/// The block returns the desired value for the merged dictionary.
+ (NSDictionary *)dictionaryByMergingDictionary:(NSDictionary *)dictionary
withOtherDictionary:(NSDictionary *)otherDictionary
uniquingKeysWithBlock:(id (^)(id value1, id value2))block {
NSMutableDictionary *mergedDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionary];
[otherDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if (mergedDictionary[key] != nil) {
// The key exists in both given dictionaries so combine the corresponding values with the
// given block.
id newValue = block(mergedDictionary[key], obj);
mergedDictionary[key] = newValue;
} else {
mergedDictionary[key] = obj;
}
}];
return [mergedDictionary copy];
}
#pragma mark - Equality
- (BOOL)isEqualToLogSourceMetrics:(GDTCORLogSourceMetrics *)otherMetrics {
return [_droppedEventCounterByLogSource
isEqualToDictionary:otherMetrics.droppedEventCounterByLogSource];
}
- (BOOL)isEqual:(nullable id)object {
if (object == nil) {
return NO;
}
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
return NO;
}
return [self isEqualToLogSourceMetrics:(GDTCORLogSourceMetrics *)object];
}
- (NSUInteger)hash {
return [_droppedEventCounterByLogSource hash];
}
#pragma mark - NSSecureCoding
+ (BOOL)supportsSecureCoding {
return YES;
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
NSDictionary<NSString *, GDTCORDroppedEventCounter *> *droppedEventCounterByLogSource =
[coder decodeObjectOfClasses:
[NSSet setWithArray:@[ NSDictionary.class, NSString.class, NSNumber.class ]]
forKey:kDroppedEventCounterByLogSource];
if (!droppedEventCounterByLogSource ||
![droppedEventCounterByLogSource isKindOfClass:[NSDictionary class]]) {
// If any of the fields are corrupted, the initializer should fail.
return nil;
}
return [self initWithDroppedEventCounterByLogSource:droppedEventCounterByLogSource];
}
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
[coder encodeObject:self.droppedEventCounterByLogSource forKey:kDroppedEventCounterByLogSource];
}
#pragma mark - Description
- (NSString *)description {
return [NSString
stringWithFormat:@"%@ %@", [super description], self.droppedEventCounterByLogSource];
}
@end

View File

@ -0,0 +1,100 @@
// Copyright 2022 Google LLC
//
// 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h"
@implementation GDTCORMetrics
- (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate
collectionEndDate:(NSDate *)collectionEndDate
logSourceMetrics:(GDTCORLogSourceMetrics *)logSourceMetrics
currentCacheSize:(GDTCORStorageSizeBytes)currentCacheSize
maxCacheSize:(GDTCORStorageSizeBytes)maxCacheSize
bundleID:(NSString *)bundleID {
self = [super init];
if (self) {
_collectionStartDate = [collectionStartDate copy];
_collectionEndDate = [collectionEndDate copy];
_logSourceMetrics = logSourceMetrics;
_currentCacheSize = currentCacheSize;
_maxCacheSize = maxCacheSize;
_bundleID = [bundleID copy];
}
return self;
}
+ (instancetype)metricsWithMetricsMetadata:(GDTCORMetricsMetadata *)metricsMetadata
storageMetadata:(GDTCORStorageMetadata *)storageMetadata {
// The window of collection ends at the time of creating the metrics object.
NSDate *collectionEndDate = [NSDate date];
// The main bundle ID is associated with the created metrics.
NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier] ?: @"";
return [[GDTCORMetrics alloc] initWithCollectionStartDate:metricsMetadata.collectionStartDate
collectionEndDate:collectionEndDate
logSourceMetrics:metricsMetadata.logSourceMetrics
currentCacheSize:storageMetadata.currentCacheSize
maxCacheSize:storageMetadata.maxCacheSize
bundleID:bundleID];
}
#pragma mark - Equality
- (BOOL)isEqualToMetrics:(GDTCORMetrics *)otherMetrics {
return [self.collectionStartDate isEqualToDate:otherMetrics.collectionStartDate] &&
[self.collectionEndDate isEqualToDate:otherMetrics.collectionEndDate] &&
[self.logSourceMetrics isEqualToLogSourceMetrics:otherMetrics.logSourceMetrics] &&
[self.bundleID isEqualToString:otherMetrics.bundleID] &&
self.currentCacheSize == otherMetrics.currentCacheSize &&
self.maxCacheSize == otherMetrics.maxCacheSize;
}
- (BOOL)isEqual:(nullable id)object {
if (object == nil) {
return NO;
}
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
return NO;
}
return [self isEqualToMetrics:(GDTCORMetrics *)object];
}
- (NSUInteger)hash {
return [self.collectionStartDate hash] ^ [self.collectionEndDate hash] ^
[self.logSourceMetrics hash] ^ [self.bundleID hash] ^ [@(self.currentCacheSize) hash] ^
[@(self.maxCacheSize) hash];
}
#pragma mark - Description
- (NSString *)description {
return [NSString
stringWithFormat:
@"%@ {\n\tcollectionStartDate: %@,\n\tcollectionEndDate: %@,\n\tcurrentCacheSize: "
@"%llu,\n\tmaxCacheSize: %llu,\n\tbundleID: %@,\n\tlogSourceMetrics: %@}\n",
[super description], self.collectionStartDate, self.collectionEndDate,
self.currentCacheSize, self.maxCacheSize, self.bundleID, self.logSourceMetrics];
}
@end

View File

@ -0,0 +1,201 @@
// Copyright 2022 Google LLC
//
// 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsController.h"
#if __has_include(<FBLPromises/FBLPromises.h>)
#import <FBLPromises/FBLPromises.h>
#else
#import "FBLPromises.h"
#endif
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage+Promises.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetrics.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h"
@interface GDTCORMetricsController ()
/// The underlying storage object where metrics are stored.
@property(nonatomic) id<GDTCORStoragePromiseProtocol> storage;
@end
@implementation GDTCORMetricsController
+ (void)load {
#if GDT_TEST
[[GDTCORRegistrar sharedInstance] registerMetricsController:[self sharedInstance]
target:kGDTCORTargetTest];
#endif // GDT_TEST
// Only the Firelog backend supports metrics collection.
[[GDTCORRegistrar sharedInstance] registerMetricsController:[self sharedInstance]
target:kGDTCORTargetCSH];
[[GDTCORRegistrar sharedInstance] registerMetricsController:[self sharedInstance]
target:kGDTCORTargetFLL];
}
+ (instancetype)sharedInstance {
static id sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] initWithStorage:[GDTCORFlatFileStorage sharedInstance]];
});
return sharedInstance;
}
- (instancetype)initWithStorage:(id<GDTCORStoragePromiseProtocol>)storage {
self = [super init];
if (self) {
_storage = storage;
}
return self;
}
- (nonnull FBLPromise<NSNull *> *)logEventsDroppedForReason:(GDTCOREventDropReason)reason
events:(nonnull NSSet<GDTCOREvent *> *)events {
// No-op if there are no events to log.
if ([events count] == 0) {
return [FBLPromise resolvedWith:nil];
}
__auto_type handler = ^GDTCORMetricsMetadata *(GDTCORMetricsMetadata *_Nullable metricsMetadata,
NSError *_Nullable fetchError) {
GDTCORLogSourceMetrics *logSourceMetrics =
[GDTCORLogSourceMetrics metricsWithEvents:[events allObjects] droppedForReason:reason];
if (metricsMetadata) {
GDTCORLogSourceMetrics *updatedLogSourceMetrics = [metricsMetadata.logSourceMetrics
logSourceMetricsByMergingWithLogSourceMetrics:logSourceMetrics];
return [GDTCORMetricsMetadata
metadataWithCollectionStartDate:[metricsMetadata collectionStartDate]
logSourceMetrics:updatedLogSourceMetrics];
} else {
// There was an error (e.g. empty storage); `metricsMetadata` is nil.
GDTCORLogDebug(@"Error fetching metrics metadata: %@", fetchError);
return [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date]
logSourceMetrics:logSourceMetrics];
}
};
return [_storage fetchAndUpdateMetricsWithHandler:handler];
}
- (nonnull FBLPromise<GDTCORMetrics *> *)getAndResetMetrics {
__block GDTCORMetricsMetadata *_Nullable snapshottedMetricsMetadata = nil;
__auto_type handler = ^GDTCORMetricsMetadata *(GDTCORMetricsMetadata *_Nullable metricsMetadata,
NSError *_Nullable fetchError) {
if (metricsMetadata) {
snapshottedMetricsMetadata = metricsMetadata;
} else {
GDTCORLogDebug(@"Error fetching metrics metadata: %@", fetchError);
}
return [GDTCORMetricsMetadata metadataWithCollectionStartDate:[NSDate date]
logSourceMetrics:[GDTCORLogSourceMetrics metrics]];
};
return [_storage fetchAndUpdateMetricsWithHandler:handler]
.validate(^BOOL(NSNull *__unused _) {
// Break and reject the promise chain when storage contains no metrics
// metadata.
return snapshottedMetricsMetadata != nil;
})
.then(^FBLPromise *(NSNull *__unused _) {
// Fetch and return storage metadata (needed for metrics).
return [self.storage fetchStorageMetadata];
})
.then(^GDTCORMetrics *(GDTCORStorageMetadata *storageMetadata) {
// Use the fetched metrics & storage metadata to create and return a
// complete metrics object.
return [GDTCORMetrics metricsWithMetricsMetadata:snapshottedMetricsMetadata
storageMetadata:storageMetadata];
});
}
- (nonnull FBLPromise<NSNull *> *)offerMetrics:(nonnull GDTCORMetrics *)metrics {
// No-op if there are no metrics to offer.
if (metrics == nil) {
return [FBLPromise resolvedWith:nil];
}
__auto_type handler = ^GDTCORMetricsMetadata *(GDTCORMetricsMetadata *_Nullable metricsMetadata,
NSError *_Nullable fetchError) {
if (metricsMetadata) {
if (metrics.collectionStartDate.timeIntervalSince1970 <=
metricsMetadata.collectionStartDate.timeIntervalSince1970) {
// If the metrics to append are older than the metrics represented by
// the currently stored metrics, then return a new metadata object that
// incorporates the data from the given metrics.
return [GDTCORMetricsMetadata
metadataWithCollectionStartDate:[metrics collectionStartDate]
logSourceMetrics:[metricsMetadata.logSourceMetrics
logSourceMetricsByMergingWithLogSourceMetrics:
metrics.logSourceMetrics]];
} else {
// This catches an edge case where the given metrics to append are
// newer than metrics represented by the currently stored metrics
// metadata. In this case, return the existing metadata object as the
// given metrics are assumed to already be accounted for by the
// currently stored metadata.
return metricsMetadata;
}
} else {
// There was an error (e.g. empty storage); `metricsMetadata` is nil.
GDTCORLogDebug(@"Error fetching metrics metadata: %@", fetchError);
NSDate *now = [NSDate date];
if (metrics.collectionStartDate.timeIntervalSince1970 <= now.timeIntervalSince1970) {
// The given metrics are were recorded up until now. They wouldn't
// be offered if they were successfully uploaded so their
// corresponding metadata can be safely placed back in storage.
return [GDTCORMetricsMetadata metadataWithCollectionStartDate:metrics.collectionStartDate
logSourceMetrics:metrics.logSourceMetrics];
} else {
// This catches an edge case where the given metrics are from the
// future. If this occurs, ignore them and store an empty metadata
// object intended to track metrics metadata from this time forward.
return [GDTCORMetricsMetadata
metadataWithCollectionStartDate:[NSDate date]
logSourceMetrics:[GDTCORLogSourceMetrics metrics]];
}
}
};
return [_storage fetchAndUpdateMetricsWithHandler:handler];
}
#pragma mark - GDTCORStorageDelegate
- (void)storage:(id<GDTCORStorageProtocol>)storage
didRemoveExpiredEvents:(nonnull NSSet<GDTCOREvent *> *)events {
[self logEventsDroppedForReason:GDTCOREventDropReasonMessageTooOld events:events];
}
- (void)storage:(nonnull id<GDTCORStorageProtocol>)storage
didDropEvent:(nonnull GDTCOREvent *)event {
[self logEventsDroppedForReason:GDTCOREventDropReasonStorageFull
events:[NSSet setWithObject:event]];
}
@end

View File

@ -0,0 +1,96 @@
// Copyright 2022 Google LLC
//
// 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORMetricsMetadata.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORLogSourceMetrics.h"
static NSString *const kCollectionStartDate = @"collectionStartDate";
static NSString *const kLogSourceMetrics = @"logSourceMetrics";
@implementation GDTCORMetricsMetadata
+ (instancetype)metadataWithCollectionStartDate:(NSDate *)collectedSinceDate
logSourceMetrics:(GDTCORLogSourceMetrics *)logSourceMetrics {
return [[self alloc] initWithCollectionStartDate:collectedSinceDate
logSourceMetrics:logSourceMetrics];
}
- (instancetype)initWithCollectionStartDate:(NSDate *)collectionStartDate
logSourceMetrics:(GDTCORLogSourceMetrics *)logSourceMetrics {
self = [super init];
if (self) {
_collectionStartDate = [collectionStartDate copy];
_logSourceMetrics = logSourceMetrics;
}
return self;
}
#pragma mark - Equality
- (BOOL)isEqualToMetricsMetadata:(GDTCORMetricsMetadata *)otherMetricsMetadata {
return [self.collectionStartDate isEqualToDate:otherMetricsMetadata.collectionStartDate] &&
[self.logSourceMetrics isEqualToLogSourceMetrics:otherMetricsMetadata.logSourceMetrics];
}
- (BOOL)isEqual:(nullable id)object {
if (object == nil) {
return NO;
}
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
return NO;
}
return [self isEqualToMetricsMetadata:(GDTCORMetricsMetadata *)object];
}
- (NSUInteger)hash {
return [self.collectionStartDate hash] ^ [self.logSourceMetrics hash];
}
#pragma mark - NSSecureCoding
+ (BOOL)supportsSecureCoding {
return YES;
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
NSDate *collectionStartDate = [coder decodeObjectOfClass:[NSDate class]
forKey:kCollectionStartDate];
GDTCORLogSourceMetrics *logSourceMetrics =
[coder decodeObjectOfClass:[GDTCORLogSourceMetrics class] forKey:kLogSourceMetrics];
if (!collectionStartDate || !logSourceMetrics ||
![collectionStartDate isKindOfClass:[NSDate class]] ||
![logSourceMetrics isKindOfClass:[GDTCORLogSourceMetrics class]]) {
// If any of the fields are corrupted, the initializer should fail.
return nil;
}
return [self initWithCollectionStartDate:collectionStartDate logSourceMetrics:logSourceMetrics];
}
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
[coder encodeObject:self.collectionStartDate forKey:kCollectionStartDate];
[coder encodeObject:self.logSourceMetrics forKey:kLogSourceMetrics];
}
@end

View File

@ -0,0 +1,566 @@
/*
* 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h"
#import <sys/sysctl.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
#ifdef GDTCOR_VERSION
#define STR(x) STR_EXPAND(x)
#define STR_EXPAND(x) #x
NSString *const kGDTCORVersion = @STR(GDTCOR_VERSION);
#else
NSString *const kGDTCORVersion = @"Unknown";
#endif // GDTCOR_VERSION
const GDTCORBackgroundIdentifier GDTCORBackgroundIdentifierInvalid = 0;
NSString *const kGDTCORApplicationDidEnterBackgroundNotification =
@"GDTCORApplicationDidEnterBackgroundNotification";
NSString *const kGDTCORApplicationWillEnterForegroundNotification =
@"GDTCORApplicationWillEnterForegroundNotification";
NSString *const kGDTCORApplicationWillTerminateNotification =
@"GDTCORApplicationWillTerminateNotification";
NSURL *GDTCORRootDirectory(void) {
static NSURL *GDTPath;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *cachePath =
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
GDTPath =
[NSURL fileURLWithPath:[NSString stringWithFormat:@"%@/google-sdks-events", cachePath]];
GDTCORLogDebug(@"GDT's state will be saved to: %@", GDTPath);
});
NSError *error;
[[NSFileManager defaultManager] createDirectoryAtPath:GDTPath.path
withIntermediateDirectories:YES
attributes:nil
error:&error];
GDTCORAssert(error == nil, @"There was an error creating GDT's path");
return GDTPath;
}
BOOL GDTCORReachabilityFlagsReachable(GDTCORNetworkReachabilityFlags flags) {
#if !TARGET_OS_WATCH
BOOL reachable =
(flags & kSCNetworkReachabilityFlagsReachable) == kSCNetworkReachabilityFlagsReachable;
BOOL connectionRequired = (flags & kSCNetworkReachabilityFlagsConnectionRequired) ==
kSCNetworkReachabilityFlagsConnectionRequired;
return reachable && !connectionRequired;
#else
return (flags & kGDTCORNetworkReachabilityFlagsReachable) ==
kGDTCORNetworkReachabilityFlagsReachable;
#endif
}
BOOL GDTCORReachabilityFlagsContainWWAN(GDTCORNetworkReachabilityFlags flags) {
#if TARGET_OS_IOS
return (flags & kSCNetworkReachabilityFlagsIsWWAN) == kSCNetworkReachabilityFlagsIsWWAN;
#else
// Assume network connection not WWAN on macOS, tvOS, watchOS.
return NO;
#endif // TARGET_OS_IOS
}
GDTCORNetworkType GDTCORNetworkTypeMessage(void) {
#if !TARGET_OS_WATCH
SCNetworkReachabilityFlags reachabilityFlags = [GDTCORReachability currentFlags];
if ((reachabilityFlags & kSCNetworkReachabilityFlagsReachable) ==
kSCNetworkReachabilityFlagsReachable) {
if (GDTCORReachabilityFlagsContainWWAN(reachabilityFlags)) {
return GDTCORNetworkTypeMobile;
} else {
return GDTCORNetworkTypeWIFI;
}
}
#endif
return GDTCORNetworkTypeUNKNOWN;
}
GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage(void) {
// TODO(Xcode 15): When Xcode 15 is the minimum supported Xcode version,
// it will be unnecessary to check if `TARGET_OS_VISION` is defined.
#if TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION)
static NSDictionary<NSString *, NSNumber *> *CTRadioAccessTechnologyToNetworkSubTypeMessage;
static CTTelephonyNetworkInfo *networkInfo;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CTRadioAccessTechnologyToNetworkSubTypeMessage = @{
CTRadioAccessTechnologyGPRS : @(GDTCORNetworkMobileSubtypeGPRS),
CTRadioAccessTechnologyEdge : @(GDTCORNetworkMobileSubtypeEdge),
CTRadioAccessTechnologyWCDMA : @(GDTCORNetworkMobileSubtypeWCDMA),
CTRadioAccessTechnologyHSDPA : @(GDTCORNetworkMobileSubtypeHSDPA),
CTRadioAccessTechnologyHSUPA : @(GDTCORNetworkMobileSubtypeHSUPA),
CTRadioAccessTechnologyCDMA1x : @(GDTCORNetworkMobileSubtypeCDMA1x),
CTRadioAccessTechnologyCDMAEVDORev0 : @(GDTCORNetworkMobileSubtypeCDMAEVDORev0),
CTRadioAccessTechnologyCDMAEVDORevA : @(GDTCORNetworkMobileSubtypeCDMAEVDORevA),
CTRadioAccessTechnologyCDMAEVDORevB : @(GDTCORNetworkMobileSubtypeCDMAEVDORevB),
CTRadioAccessTechnologyeHRPD : @(GDTCORNetworkMobileSubtypeHRPD),
CTRadioAccessTechnologyLTE : @(GDTCORNetworkMobileSubtypeLTE),
};
networkInfo = [[CTTelephonyNetworkInfo alloc] init];
});
NSString *networkCurrentRadioAccessTechnology;
#if TARGET_OS_MACCATALYST
NSDictionary<NSString *, NSString *> *networkCurrentRadioAccessTechnologyDict =
networkInfo.serviceCurrentRadioAccessTechnology;
if (networkCurrentRadioAccessTechnologyDict.count) {
networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0];
}
#else // TARGET_OS_MACCATALYST
NSDictionary<NSString *, NSString *> *networkCurrentRadioAccessTechnologyDict =
networkInfo.serviceCurrentRadioAccessTechnology;
if (networkCurrentRadioAccessTechnologyDict.count) {
// In iOS 12, multiple radio technologies can be captured. We prefer not particular radio
// tech to another, so we'll just return the first value in the dictionary.
networkCurrentRadioAccessTechnology = networkCurrentRadioAccessTechnologyDict.allValues[0];
}
#endif // TARGET_OS_MACCATALYST
if (networkCurrentRadioAccessTechnology) {
NSNumber *networkMobileSubtype =
CTRadioAccessTechnologyToNetworkSubTypeMessage[networkCurrentRadioAccessTechnology];
return networkMobileSubtype.intValue;
} else {
return GDTCORNetworkMobileSubtypeUNKNOWN;
}
#else // TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION)
return GDTCORNetworkMobileSubtypeUNKNOWN;
#endif // TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION)
}
NSString *_Nonnull GDTCORDeviceModel(void) {
static NSString *deviceModel = @"Unknown";
#if TARGET_OS_IOS || TARGET_OS_TV
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
size_t size;
char *keyToExtract = "hw.machine";
sysctlbyname(keyToExtract, NULL, &size, NULL, 0);
if (size > 0) {
char *machine = calloc(1, size);
sysctlbyname(keyToExtract, machine, &size, NULL, 0);
deviceModel = [NSString stringWithCString:machine encoding:NSUTF8StringEncoding];
free(machine);
} else {
deviceModel = [UIDevice currentDevice].model;
}
});
#endif
return deviceModel;
}
NSData *_Nullable GDTCOREncodeArchive(id<NSSecureCoding> obj,
NSString *filePath,
NSError *_Nullable *error) {
BOOL result = NO;
if (filePath.length > 0) {
// TODO(ncooke3): For future cleanup this API shouldn't touch the file
// system unless it successfully encoded the given object.
result = [[NSFileManager defaultManager]
createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
withIntermediateDirectories:YES
attributes:nil
error:error];
if (result == NO || *error) {
GDTCORLogDebug(@"Attempt to create directory failed: path:%@ error:%@", filePath, *error);
return nil;
}
}
NSData *resultData;
resultData = [NSKeyedArchiver archivedDataWithRootObject:obj
requiringSecureCoding:YES
error:error];
if (resultData == nil || (error != NULL && *error != nil)) {
GDTCORLogDebug(@"Encoding an object failed: %@", *error);
return nil;
}
if (filePath.length > 0) {
result = [resultData writeToFile:filePath options:NSDataWritingAtomic error:error];
if (result == NO || (error != NULL && *error != nil)) {
if (error != NULL && *error != nil) {
GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *error);
} else {
GDTCORLogDebug(@"Attempt to write archive failed: path:%@", filePath);
}
} else {
GDTCORLogDebug(@"Writing archive succeeded: %@", filePath);
}
}
return resultData;
}
id<NSSecureCoding> _Nullable GDTCORDecodeArchiveAtPath(Class archiveClass,
NSString *_Nonnull archivePath,
NSError **_Nonnull error) {
NSData *data = [NSData dataWithContentsOfFile:archivePath options:0 error:error];
if (data == nil) {
// Reading the file failed and `error` will be populated.
return nil;
}
return GDTCORDecodeArchive(archiveClass, data, error);
}
id<NSSecureCoding> _Nullable GDTCORDecodeArchive(Class archiveClass,
NSData *_Nonnull archiveData,
NSError **_Nonnull error) {
return [NSKeyedUnarchiver unarchivedObjectOfClass:archiveClass fromData:archiveData error:error];
}
BOOL GDTCORWriteDataToFile(NSData *data, NSString *filePath, NSError *_Nullable *outError) {
BOOL result = NO;
if (filePath.length > 0) {
result = [[NSFileManager defaultManager]
createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
withIntermediateDirectories:YES
attributes:nil
error:outError];
if (result == NO || *outError) {
GDTCORLogDebug(@"Attempt to create directory failed: path:%@ error:%@", filePath, *outError);
return result;
}
}
if (filePath.length > 0) {
result = [data writeToFile:filePath options:NSDataWritingAtomic error:outError];
if (result == NO || *outError) {
GDTCORLogDebug(@"Attempt to write archive failed: path:%@ error:%@", filePath, *outError);
} else {
GDTCORLogDebug(@"Writing archive succeeded: %@", filePath);
}
}
return result;
}
@interface GDTCORApplication ()
/**
Private flag to match the existing `readonly` public flag. This will be accurate for all platforms,
since we handle each platform's lifecycle notifications separately.
*/
@property(atomic, readwrite) BOOL isRunningInBackground;
@end
@implementation GDTCORApplication
#if TARGET_OS_WATCH
/** A dispatch queue on which all task semaphores will populate and remove from
* gBackgroundIdentifierToSemaphoreMap.
*/
static dispatch_queue_t gSemaphoreQueue;
/** For mapping backgroundIdentifier to task semaphore. */
static NSMutableDictionary<NSNumber *, dispatch_semaphore_t> *gBackgroundIdentifierToSemaphoreMap;
#endif
+ (void)load {
GDTCORLogDebug(
@"%@", @"GDT is initializing. Please note that if you quit the app via the "
"debugger and not through a lifecycle event, event data will remain on disk but "
"storage won't have a reference to them since the singleton wasn't saved to disk.");
#if TARGET_OS_IOS || TARGET_OS_TV
// If this asserts, please file a bug at https://github.com/firebase/firebase-ios-sdk/issues.
GDTCORFatalAssert(
GDTCORBackgroundIdentifierInvalid == UIBackgroundTaskInvalid,
@"GDTCORBackgroundIdentifierInvalid and UIBackgroundTaskInvalid should be the same.");
#endif
[self sharedApplication];
}
+ (void)initialize {
#if TARGET_OS_WATCH
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gSemaphoreQueue = dispatch_queue_create("com.google.GDTCORApplication", DISPATCH_QUEUE_SERIAL);
GDTCORLogDebug(
@"%@",
@"GDTCORApplication is initializing on watchOS, gSemaphoreQueue has been initialized.");
gBackgroundIdentifierToSemaphoreMap = [[NSMutableDictionary alloc] init];
GDTCORLogDebug(@"%@", @"GDTCORApplication is initializing on watchOS, "
@"gBackgroundIdentifierToSemaphoreMap has been initialized.");
});
#endif
}
+ (nullable GDTCORApplication *)sharedApplication {
static GDTCORApplication *application;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
application = [[GDTCORApplication alloc] init];
});
return application;
}
- (instancetype)init {
self = [super init];
if (self) {
// This class will be instantiated in the foreground.
_isRunningInBackground = NO;
#if TARGET_OS_IOS || TARGET_OS_TV
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(iOSApplicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(iOSApplicationWillEnterForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
NSString *name = UIApplicationWillTerminateNotification;
[notificationCenter addObserver:self
selector:@selector(iOSApplicationWillTerminate:)
name:name
object:nil];
#if defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
if (@available(iOS 13, tvOS 13.0, *)) {
[notificationCenter addObserver:self
selector:@selector(iOSApplicationWillEnterForeground:)
name:UISceneWillEnterForegroundNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(iOSApplicationDidEnterBackground:)
name:UISceneWillDeactivateNotification
object:nil];
}
#endif // defined(__IPHONE_13_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
#elif TARGET_OS_OSX
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(macOSApplicationWillTerminate:)
name:NSApplicationWillTerminateNotification
object:nil];
#elif TARGET_OS_WATCH
// TODO: Notification on watchOS platform is currently posted by strings which are frangible.
// TODO: Needs improvements here.
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
selector:@selector(iOSApplicationDidEnterBackground:)
name:@"UIApplicationDidEnterBackgroundNotification"
object:nil];
[notificationCenter addObserver:self
selector:@selector(iOSApplicationWillEnterForeground:)
name:@"UIApplicationWillEnterForegroundNotification"
object:nil];
// Adds observers for app extension on watchOS platform
[notificationCenter addObserver:self
selector:@selector(iOSApplicationDidEnterBackground:)
name:NSExtensionHostDidEnterBackgroundNotification
object:nil];
[notificationCenter addObserver:self
selector:@selector(iOSApplicationWillEnterForeground:)
name:NSExtensionHostWillEnterForegroundNotification
object:nil];
#endif
}
return self;
}
#if TARGET_OS_WATCH
/** Generates and maps a unique background identifier to the given semaphore.
*
* @param semaphore The semaphore to map.
* @return A unique GDTCORBackgroundIdentifier mapped to the given semaphore.
*/
+ (GDTCORBackgroundIdentifier)createAndMapBackgroundIdentifierToSemaphore:
(dispatch_semaphore_t)semaphore {
__block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid;
dispatch_queue_t queue = gSemaphoreQueue;
NSMutableDictionary<NSNumber *, dispatch_semaphore_t> *map = gBackgroundIdentifierToSemaphoreMap;
if (queue && map) {
dispatch_sync(queue, ^{
bgID = arc4random();
NSNumber *bgIDNumber = @(bgID);
while (bgID == GDTCORBackgroundIdentifierInvalid || map[bgIDNumber]) {
bgID = arc4random();
bgIDNumber = @(bgID);
}
map[bgIDNumber] = semaphore;
});
}
return bgID;
}
/** Returns the semaphore mapped to given bgID and removes the value from the map.
*
* @param bgID The unique NSUInteger as GDTCORBackgroundIdentifier.
* @return The semaphore mapped by given bgID.
*/
+ (dispatch_semaphore_t)semaphoreForBackgroundIdentifier:(GDTCORBackgroundIdentifier)bgID {
__block dispatch_semaphore_t semaphore;
dispatch_queue_t queue = gSemaphoreQueue;
NSMutableDictionary<NSNumber *, dispatch_semaphore_t> *map = gBackgroundIdentifierToSemaphoreMap;
NSNumber *bgIDNumber = @(bgID);
if (queue && map) {
dispatch_sync(queue, ^{
semaphore = map[bgIDNumber];
[map removeObjectForKey:bgIDNumber];
});
}
return semaphore;
}
#endif
- (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name
expirationHandler:(void (^)(void))handler {
__block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid;
#if !TARGET_OS_WATCH
bgID = [[self sharedApplicationForBackgroundTask] beginBackgroundTaskWithName:name
expirationHandler:handler];
#if !NDEBUG
if (bgID != GDTCORBackgroundIdentifierInvalid) {
GDTCORLogDebug(@"Creating background task with name:%@ bgID:%ld", name, (long)bgID);
}
#endif // !NDEBUG
#elif TARGET_OS_WATCH
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
bgID = [GDTCORApplication createAndMapBackgroundIdentifierToSemaphore:semaphore];
if (bgID != GDTCORBackgroundIdentifierInvalid) {
GDTCORLogDebug(@"Creating activity with name:%@ bgID:%ld on watchOS.", name, (long)bgID);
}
[[self sharedNSProcessInfoForBackgroundTask]
performExpiringActivityWithReason:name
usingBlock:^(BOOL expired) {
if (expired) {
if (handler) {
handler();
}
dispatch_semaphore_signal(semaphore);
GDTCORLogDebug(
@"Activity with name:%@ bgID:%ld on watchOS is expiring.",
name, (long)bgID);
} else {
dispatch_semaphore_wait(
semaphore,
dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
}
}];
#endif
return bgID;
}
- (void)endBackgroundTask:(GDTCORBackgroundIdentifier)bgID {
#if !TARGET_OS_WATCH
if (bgID != GDTCORBackgroundIdentifierInvalid) {
GDTCORLogDebug(@"Ending background task with ID:%ld was successful", (long)bgID);
[[self sharedApplicationForBackgroundTask] endBackgroundTask:bgID];
return;
}
#elif TARGET_OS_WATCH
if (bgID != GDTCORBackgroundIdentifierInvalid) {
dispatch_semaphore_t semaphore = [GDTCORApplication semaphoreForBackgroundIdentifier:bgID];
GDTCORLogDebug(@"Ending activity with bgID:%ld on watchOS.", (long)bgID);
if (semaphore) {
dispatch_semaphore_signal(semaphore);
GDTCORLogDebug(@"Signaling semaphore with bgID:%ld on watchOS.", (long)bgID);
} else {
GDTCORLogDebug(@"Semaphore with bgID:%ld is nil on watchOS.", (long)bgID);
}
}
#endif // !TARGET_OS_WATCH
}
#pragma mark - App environment helpers
- (BOOL)isAppExtension {
BOOL appExtension = [[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"];
return appExtension;
}
/** Returns a UIApplication or NSProcessInfo instance if on the appropriate platform.
*
* @return The shared UIApplication or NSProcessInfo if on the appropriate platform.
*/
#if TARGET_OS_IOS || TARGET_OS_TV
- (nullable UIApplication *)sharedApplicationForBackgroundTask {
#elif TARGET_OS_WATCH
- (nullable NSProcessInfo *)sharedNSProcessInfoForBackgroundTask {
#else
- (nullable id)sharedApplicationForBackgroundTask {
#endif
id sharedInstance = nil;
#if TARGET_OS_IOS || TARGET_OS_TV
if (![self isAppExtension]) {
Class uiApplicationClass = NSClassFromString(@"UIApplication");
if (uiApplicationClass &&
[uiApplicationClass respondsToSelector:(NSSelectorFromString(@"sharedApplication"))]) {
sharedInstance = [uiApplicationClass sharedApplication];
}
}
#elif TARGET_OS_WATCH
sharedInstance = [NSProcessInfo processInfo];
#endif
return sharedInstance;
}
#pragma mark - UIApplicationDelegate and WKExtensionDelegate
#if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
- (void)iOSApplicationDidEnterBackground:(NSNotification *)notif {
_isRunningInBackground = YES;
NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is backgrounding.");
[notifCenter postNotificationName:kGDTCORApplicationDidEnterBackgroundNotification object:nil];
}
- (void)iOSApplicationWillEnterForeground:(NSNotification *)notif {
_isRunningInBackground = NO;
NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is foregrounding.");
[notifCenter postNotificationName:kGDTCORApplicationWillEnterForegroundNotification object:nil];
}
#endif // TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH
#pragma mark - UIApplicationDelegate
#if TARGET_OS_IOS || TARGET_OS_TV
- (void)iOSApplicationWillTerminate:(NSNotification *)notif {
NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is terminating.");
[notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil];
}
#endif // TARGET_OS_IOS || TARGET_OS_TV
#pragma mark - NSApplicationDelegate
#if TARGET_OS_OSX
- (void)macOSApplicationWillTerminate:(NSNotification *)notif {
NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
GDTCORLogDebug(@"%@", @"GDTCORPlatform is sending a notif that the app is terminating.");
[notifCenter postNotificationName:kGDTCORApplicationWillTerminateNotification object:nil];
}
#endif // TARGET_OS_OSX
@end

View File

@ -0,0 +1,77 @@
// Copyright 2023 Google LLC
//
// 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORProductData.h"
@implementation GDTCORProductData
- (instancetype)initWithProductID:(int32_t)productID {
self = [super init];
if (self) {
_productID = productID;
}
return self;
}
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
return [[[self class] alloc] initWithProductID:self.productID];
}
#pragma mark - Equality
- (BOOL)isEqualToProductData:(GDTCORProductData *)otherProductData {
return self.productID == otherProductData.productID;
}
- (BOOL)isEqual:(nullable id)object {
if (object == nil) {
return NO;
}
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
return NO;
}
return [self isEqualToProductData:(GDTCORProductData *)object];
}
- (NSUInteger)hash {
return self.productID;
}
#pragma mark - NSSecureCoding
/// NSCoding key for `productID` property.
static NSString *kProductIDKey = @"GDTCORProductDataProductIDKey";
+ (BOOL)supportsSecureCoding {
return YES;
}
- (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
int32_t productID = [coder decodeInt32ForKey:kProductIDKey];
return [self initWithProductID:productID];
}
- (void)encodeWithCoder:(nonnull NSCoder *)coder {
[coder encodeInt32:self.productID forKey:kProductIDKey];
}
@end

View File

@ -0,0 +1,125 @@
/*
* 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORReachability_Private.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import <netinet/in.h>
/** Sets the _callbackFlag ivar whenever the network changes.
*
* @param reachability The reachability object calling back.
* @param flags The new flag values.
* @param info Any data that might be passed in by the callback.
*/
static void GDTCORReachabilityCallback(GDTCORNetworkReachabilityRef reachability,
GDTCORNetworkReachabilityFlags flags,
void *info);
@implementation GDTCORReachability {
/** The reachability object. */
GDTCORNetworkReachabilityRef _reachabilityRef;
/** The queue on which callbacks and all work will occur. */
dispatch_queue_t _reachabilityQueue;
/** Flags specified by reachability callbacks. */
GDTCORNetworkReachabilityFlags _callbackFlags;
}
+ (void)initialize {
[self sharedInstance];
}
+ (instancetype)sharedInstance {
static GDTCORReachability *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[GDTCORReachability alloc] init];
});
return sharedInstance;
}
+ (GDTCORNetworkReachabilityFlags)currentFlags {
__block GDTCORNetworkReachabilityFlags currentFlags;
#if !TARGET_OS_WATCH
dispatch_sync([GDTCORReachability sharedInstance] -> _reachabilityQueue, ^{
GDTCORReachability *reachability = [GDTCORReachability sharedInstance];
currentFlags =
reachability->_callbackFlags ? reachability->_callbackFlags : reachability->_flags;
GDTCORLogDebug(@"Initial reachability flags determined: %d", currentFlags);
});
#else
currentFlags = kGDTCORNetworkReachabilityFlagsReachable;
#endif
return currentFlags;
}
- (instancetype)init {
self = [super init];
#if !TARGET_OS_WATCH
if (self) {
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET;
_reachabilityQueue =
dispatch_queue_create("com.google.GDTCORReachability", DISPATCH_QUEUE_SERIAL);
_reachabilityRef = SCNetworkReachabilityCreateWithAddress(
kCFAllocatorDefault, (const struct sockaddr *)&zeroAddress);
Boolean success = SCNetworkReachabilitySetDispatchQueue(_reachabilityRef, _reachabilityQueue);
if (!success) {
GDTCORLogWarning(GDTCORMCWReachabilityFailed, @"%@", @"The reachability queue wasn't set.");
}
success = SCNetworkReachabilitySetCallback(_reachabilityRef, GDTCORReachabilityCallback, NULL);
if (!success) {
GDTCORLogWarning(GDTCORMCWReachabilityFailed, @"%@",
@"The reachability callback wasn't set.");
}
// Get the initial set of flags.
dispatch_async(_reachabilityQueue, ^{
Boolean valid = SCNetworkReachabilityGetFlags(self->_reachabilityRef, &self->_flags);
if (!valid) {
GDTCORLogDebug(@"%@", @"Determining reachability failed.");
self->_flags = 0;
}
});
}
#endif
return self;
}
- (void)setCallbackFlags:(GDTCORNetworkReachabilityFlags)flags {
if (_callbackFlags != flags) {
self->_callbackFlags = flags;
}
}
@end
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
static void GDTCORReachabilityCallback(GDTCORNetworkReachabilityRef reachability,
GDTCORNetworkReachabilityFlags flags,
void *info) {
#pragma clang diagnostic pop
GDTCORLogDebug(@"Reachability changed, new flags: %d", flags);
[[GDTCORReachability sharedInstance] setCallbackFlags:flags];
}

View File

@ -0,0 +1,194 @@
/*
* Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
id<GDTCORStorageProtocol> _Nullable GDTCORStorageInstanceForTarget(GDTCORTarget target) {
return [GDTCORRegistrar sharedInstance].targetToStorage[@(target)];
}
id<GDTCORStoragePromiseProtocol> _Nullable GDTCORStoragePromiseInstanceForTarget(
GDTCORTarget target) {
id storage = [GDTCORRegistrar sharedInstance].targetToStorage[@(target)];
if ([storage conformsToProtocol:@protocol(GDTCORStoragePromiseProtocol)]) {
return storage;
} else {
return nil;
}
}
id<GDTCORMetricsControllerProtocol> _Nullable GDTCORMetricsControllerInstanceForTarget(
GDTCORTarget target) {
return [GDTCORRegistrar sharedInstance].targetToMetricsController[@(target)];
}
@implementation GDTCORRegistrar
// Manaully synthesize properties declared in `GDTCORRegistrar_Private.h` category.
@synthesize targetToUploader = _targetToUploader;
@synthesize targetToStorage = _targetToStorage;
@synthesize targetToMetricsController = _targetToMetricsController;
+ (instancetype)sharedInstance {
static GDTCORRegistrar *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[GDTCORRegistrar alloc] init];
});
return sharedInstance;
}
- (instancetype)init {
self = [super init];
if (self) {
_registrarQueue = dispatch_queue_create("com.google.GDTCORRegistrar", DISPATCH_QUEUE_SERIAL);
_targetToUploader = [NSMutableDictionary dictionary];
_targetToStorage = [NSMutableDictionary dictionary];
_targetToMetricsController = [NSMutableDictionary dictionary];
}
return self;
}
- (void)registerUploader:(id<GDTCORUploader>)backend target:(GDTCORTarget)target {
__weak GDTCORRegistrar *weakSelf = self;
dispatch_async(_registrarQueue, ^{
GDTCORRegistrar *strongSelf = weakSelf;
if (strongSelf) {
GDTCORLogDebug(@"Registered an uploader: %@ for target:%ld", backend, (long)target);
strongSelf->_targetToUploader[@(target)] = backend;
}
});
}
- (void)registerStorage:(id<GDTCORStorageProtocol>)storage target:(GDTCORTarget)target {
__weak GDTCORRegistrar *weakSelf = self;
dispatch_async(_registrarQueue, ^{
GDTCORRegistrar *strongSelf = weakSelf;
if (strongSelf) {
GDTCORLogDebug(@"Registered storage: %@ for target:%ld", storage, (long)target);
strongSelf->_targetToStorage[@(target)] = storage;
[self setMetricsControllerAsStorageDelegateForTarget:target];
}
});
}
- (void)registerMetricsController:(id<GDTCORMetricsControllerProtocol>)metricsController
target:(GDTCORTarget)target {
__weak GDTCORRegistrar *weakSelf = self;
dispatch_async(_registrarQueue, ^{
GDTCORRegistrar *strongSelf = weakSelf;
if (strongSelf) {
GDTCORLogDebug(@"Registered metrics controller: %@ for target:%ld", metricsController,
(long)target);
strongSelf->_targetToMetricsController[@(target)] = metricsController;
[self setMetricsControllerAsStorageDelegateForTarget:target];
}
});
}
- (NSMutableDictionary<NSNumber *, id<GDTCORUploader>> *)targetToUploader {
__block NSMutableDictionary<NSNumber *, id<GDTCORUploader>> *targetToUploader;
__weak GDTCORRegistrar *weakSelf = self;
dispatch_sync(_registrarQueue, ^{
GDTCORRegistrar *strongSelf = weakSelf;
if (strongSelf) {
targetToUploader = strongSelf->_targetToUploader;
}
});
return targetToUploader;
}
- (NSMutableDictionary<NSNumber *, id<GDTCORStorageProtocol>> *)targetToStorage {
__block NSMutableDictionary<NSNumber *, id<GDTCORStorageProtocol>> *targetToStorage;
__weak GDTCORRegistrar *weakSelf = self;
dispatch_sync(_registrarQueue, ^{
GDTCORRegistrar *strongSelf = weakSelf;
if (strongSelf) {
targetToStorage = strongSelf->_targetToStorage;
}
});
return targetToStorage;
}
- (NSMutableDictionary<NSNumber *, id<GDTCORMetricsControllerProtocol>> *)
targetToMetricsController {
__block NSMutableDictionary<NSNumber *, id<GDTCORMetricsControllerProtocol>>
*targetToMetricsController;
__weak GDTCORRegistrar *weakSelf = self;
dispatch_sync(_registrarQueue, ^{
GDTCORRegistrar *strongSelf = weakSelf;
if (strongSelf) {
targetToMetricsController = strongSelf->_targetToMetricsController;
}
});
return targetToMetricsController;
}
- (void)setMetricsControllerAsStorageDelegateForTarget:(GDTCORTarget)target {
_targetToStorage[@(target)].delegate = _targetToMetricsController[@(target)];
}
#pragma mark - GDTCORLifecycleProtocol
- (void)appWillBackground:(nonnull GDTCORApplication *)app {
NSArray<id<GDTCORUploader>> *uploaders = [self.targetToUploader allValues];
for (id<GDTCORUploader> uploader in uploaders) {
if ([uploader respondsToSelector:@selector(appWillBackground:)]) {
[uploader appWillBackground:app];
}
}
NSArray<id<GDTCORStorageProtocol>> *storages = [self.targetToStorage allValues];
for (id<GDTCORStorageProtocol> storage in storages) {
if ([storage respondsToSelector:@selector(appWillBackground:)]) {
[storage appWillBackground:app];
}
}
}
- (void)appWillForeground:(nonnull GDTCORApplication *)app {
NSArray<id<GDTCORUploader>> *uploaders = [self.targetToUploader allValues];
for (id<GDTCORUploader> uploader in uploaders) {
if ([uploader respondsToSelector:@selector(appWillForeground:)]) {
[uploader appWillForeground:app];
}
}
NSArray<id<GDTCORStorageProtocol>> *storages = [self.targetToStorage allValues];
for (id<GDTCORStorageProtocol> storage in storages) {
if ([storage respondsToSelector:@selector(appWillForeground:)]) {
[storage appWillForeground:app];
}
}
}
- (void)appWillTerminate:(nonnull GDTCORApplication *)app {
NSArray<id<GDTCORUploader>> *uploaders = [self.targetToUploader allValues];
for (id<GDTCORUploader> uploader in uploaders) {
if ([uploader respondsToSelector:@selector(appWillTerminate:)]) {
[uploader appWillTerminate:app];
}
}
NSArray<id<GDTCORStorageProtocol>> *storages = [self.targetToStorage allValues];
for (id<GDTCORStorageProtocol> storage in storages) {
if ([storage respondsToSelector:@selector(appWillTerminate:)]) {
[storage appWillTerminate:app];
}
}
}
@end

View File

@ -0,0 +1,39 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageEventSelector.h"
@implementation GDTCORStorageEventSelector
+ (instancetype)eventSelectorForTarget:(GDTCORTarget)target {
return [[self alloc] initWithTarget:target eventIDs:nil mappingIDs:nil qosTiers:nil];
}
- (instancetype)initWithTarget:(GDTCORTarget)target
eventIDs:(nullable NSSet<NSString *> *)eventIDs
mappingIDs:(nullable NSSet<NSString *> *)mappingIDs
qosTiers:(nullable NSSet<NSNumber *> *)qosTiers {
self = [super init];
if (self) {
_selectedTarget = target;
_selectedEventIDs = eventIDs;
_selectedMappingIDs = mappingIDs;
_selectedQosTiers = qosTiers;
}
return self;
}
@end

View File

@ -0,0 +1,36 @@
// Copyright 2022 Google LLC
//
// 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORStorageMetadata.h"
@implementation GDTCORStorageMetadata
- (instancetype)initWithCurrentCacheSize:(GDTCORStorageSizeBytes)currentCacheSize
maxCacheSize:(GDTCORStorageSizeBytes)maxCacheSize {
self = [super init];
if (self) {
_currentCacheSize = currentCacheSize;
_maxCacheSize = maxCacheSize;
}
return self;
}
+ (instancetype)metadataWithCurrentCacheSize:(GDTCORStorageSizeBytes)currentCacheSize
maxCacheSize:(GDTCORStorageSizeBytes)maxCacheSize {
return [[self alloc] initWithCurrentCacheSize:currentCacheSize maxCacheSize:maxCacheSize];
}
@end

View File

@ -0,0 +1,108 @@
/*
* Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer_Private.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventTransformer.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCOREvent_Private.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
@implementation GDTCORTransformer
+ (instancetype)sharedInstance {
static GDTCORTransformer *eventTransformer;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
eventTransformer = [[self alloc] init];
});
return eventTransformer;
}
- (instancetype)init {
return [self initWithApplication:[GDTCORApplication sharedApplication]];
}
- (instancetype)initWithApplication:(id<GDTCORApplicationProtocol>)application {
self = [super init];
if (self) {
_eventWritingQueue =
dispatch_queue_create("com.google.GDTCORTransformer", DISPATCH_QUEUE_SERIAL);
_application = application;
}
return self;
}
- (void)transformEvent:(GDTCOREvent *)event
withTransformers:(NSArray<id<GDTCOREventTransformer>> *)transformers
onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion {
GDTCORAssert(event, @"You can't write a nil event");
__block GDTCORBackgroundIdentifier bgID = GDTCORBackgroundIdentifierInvalid;
__auto_type __weak weakApplication = self.application;
bgID = [self.application beginBackgroundTaskWithName:@"GDTTransformer"
expirationHandler:^{
[weakApplication endBackgroundTask:bgID];
bgID = GDTCORBackgroundIdentifierInvalid;
}];
__auto_type completionWrapper = ^(BOOL wasWritten, NSError *_Nullable error) {
if (completion) {
completion(wasWritten, error);
}
if (bgID != GDTCORBackgroundIdentifierInvalid) {
// The work is done, cancel the background task if it's valid.
[weakApplication endBackgroundTask:bgID];
} else {
GDTCORLog(GDTCORMCDDebugLog, GDTCORLoggingLevelWarnings,
@"Attempted to cancel invalid background task in GDTCORTransformer.");
}
bgID = GDTCORBackgroundIdentifierInvalid;
};
dispatch_async(_eventWritingQueue, ^{
GDTCOREvent *transformedEvent = event;
for (id<GDTCOREventTransformer> transformer in transformers) {
if ([transformer respondsToSelector:@selector(transformGDTEvent:)]) {
GDTCORLogDebug(@"Applying a transformer to event %@", event);
transformedEvent = [transformer transformGDTEvent:event];
if (!transformedEvent) {
completionWrapper(NO, nil);
return;
}
} else {
GDTCORLogError(GDTCORMCETransformerDoesntImplementTransform,
@"Transformer doesn't implement transformGDTEvent: %@", transformer);
completionWrapper(NO, nil);
return;
}
}
id<GDTCORStorageProtocol> storage =
[GDTCORRegistrar sharedInstance].targetToStorage[@(event.target)];
[storage storeEvent:transformedEvent onComplete:completionWrapper];
});
}
@end

View File

@ -0,0 +1,108 @@
/*
* Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTransport.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransport_Private.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h"
@implementation GDTCORTransport
- (nullable instancetype)initWithMappingID:(NSString *)mappingID
transformers:
(nullable NSArray<id<GDTCOREventTransformer>> *)transformers
target:(GDTCORTarget)target {
GDTCORAssert(mappingID.length > 0, @"A mapping ID cannot be nil or empty");
GDTCORAssert(target > 0, @"A target cannot be negative or 0");
if (mappingID == nil || mappingID.length == 0 || target <= 0) {
return nil;
}
self = [super init];
if (self) {
_mappingID = mappingID;
_transformers = transformers;
_target = target;
_transformerInstance = [GDTCORTransformer sharedInstance];
}
GDTCORLogDebug(@"Transport object created. mappingID:%@ transformers:%@ target:%ld", mappingID,
transformers, (long)target);
return self;
}
- (void)sendTelemetryEvent:(GDTCOREvent *)event
onComplete:
(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion {
event.qosTier = GDTCOREventQoSTelemetry;
[self sendEvent:event onComplete:completion];
}
- (void)sendDataEvent:(GDTCOREvent *)event
onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion {
GDTCORAssert(event.qosTier != GDTCOREventQoSTelemetry, @"Use -sendTelemetryEvent, please.");
[self sendEvent:event onComplete:completion];
}
- (void)sendTelemetryEvent:(GDTCOREvent *)event {
[self sendTelemetryEvent:event onComplete:nil];
}
- (void)sendDataEvent:(GDTCOREvent *)event {
[self sendDataEvent:event onComplete:nil];
}
- (GDTCOREvent *)eventForTransport {
return [[GDTCOREvent alloc] initWithMappingID:_mappingID target:_target];
}
- (GDTCOREvent *)eventForTransportWithProductData:(GDTCORProductData *)productData {
return [[GDTCOREvent alloc] initWithMappingID:_mappingID productData:productData target:_target];
}
#pragma mark - Private helper methods
/** Sends the given event through the transport pipeline.
*
* @param event The event to send.
* @param completion A block that will be called when the event has been written or dropped.
*/
- (void)sendEvent:(GDTCOREvent *)event
onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion {
// TODO: Determine if sending an event before registration is allowed.
GDTCORAssert(event, @"You can't send a nil event");
GDTCOREvent *copiedEvent = [event copy];
copiedEvent.clockSnapshot = [GDTCORClock snapshot];
[self.transformerInstance transformEvent:copiedEvent
withTransformers:_transformers
onComplete:completion];
}
#pragma mark - Force Category Linking
extern void GDTCORInclude_GDTCORLogSourceMetrics_Internal_Category(void);
/// Does nothing when called, and not meant to be called.
///
/// This method forces the linker to include categories even if
/// users do not include the '-ObjC' linker flag in their project.
+ (void)noop {
GDTCORInclude_GDTCORLogSourceMetrics_Internal_Category();
}
@end

View File

@ -0,0 +1,30 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadBatch.h"
@implementation GDTCORUploadBatch
- (instancetype)initWithBatchID:(NSNumber *)batchID events:(NSSet<GDTCOREvent *> *)events {
self = [super init];
if (self) {
_batchID = batchID;
_events = events;
}
return self;
}
@end

View File

@ -0,0 +1,176 @@
/*
* Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORUploadCoordinator.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORAssert.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
#import "GoogleDataTransport/GDTCORLibrary/Private/GDTCORRegistrar_Private.h"
@implementation GDTCORUploadCoordinator
+ (instancetype)sharedInstance {
static GDTCORUploadCoordinator *sharedUploader;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedUploader = [[GDTCORUploadCoordinator alloc] init];
[sharedUploader startTimer];
});
return sharedUploader;
}
- (instancetype)init {
self = [super init];
if (self) {
_coordinationQueue =
dispatch_queue_create("com.google.GDTCORUploadCoordinator", DISPATCH_QUEUE_SERIAL);
_registrar = [GDTCORRegistrar sharedInstance];
_timerInterval = 30 * NSEC_PER_SEC;
_timerLeeway = 5 * NSEC_PER_SEC;
}
return self;
}
- (void)forceUploadForTarget:(GDTCORTarget)target {
dispatch_async(_coordinationQueue, ^{
GDTCORLogDebug(@"Forcing an upload of target %ld", (long)target);
GDTCORUploadConditions conditions = [self uploadConditions];
conditions |= GDTCORUploadConditionHighPriority;
[self uploadTargets:@[ @(target) ] conditions:conditions];
});
}
#pragma mark - Private helper methods
/** Starts a timer that checks whether or not events can be uploaded at regular intervals. It will
* check the next-upload clocks of all targets to determine if an upload attempt can be made.
*/
- (void)startTimer {
dispatch_async(_coordinationQueue, ^{
if (self->_timer) {
// The timer has been already started.
return;
}
// Delay the timer slightly so it doesn't run while +load calls are still running.
dispatch_time_t deadline = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC / 2);
self->_timer =
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self->_coordinationQueue);
dispatch_source_set_timer(self->_timer, deadline, self->_timerInterval, self->_timerLeeway);
dispatch_source_set_event_handler(self->_timer, ^{
if (![[GDTCORApplication sharedApplication] isRunningInBackground]) {
GDTCORUploadConditions conditions = [self uploadConditions];
GDTCORLogDebug(@"%@", @"Upload timer fired");
[self uploadTargets:[self.registrar.targetToUploader allKeys] conditions:conditions];
}
});
GDTCORLogDebug(@"%@", @"Upload timer started");
dispatch_resume(self->_timer);
});
}
/** Stops the currently running timer. */
- (void)stopTimer {
if (_timer) {
dispatch_source_cancel(_timer);
_timer = nil;
}
}
/** Triggers the uploader implementations for the given targets to upload.
*
* @param targets An array of targets to trigger.
* @param conditions The set of upload conditions.
*/
- (void)uploadTargets:(NSArray<NSNumber *> *)targets conditions:(GDTCORUploadConditions)conditions {
dispatch_async(_coordinationQueue, ^{
// TODO: The reachability signal may be not reliable enough to prevent an upload attempt.
// See https://developer.apple.com/videos/play/wwdc2019/712/ (49:40) for more details.
if ((conditions & GDTCORUploadConditionNoNetwork) == GDTCORUploadConditionNoNetwork) {
return;
}
for (NSNumber *target in targets) {
id<GDTCORUploader> uploader = self->_registrar.targetToUploader[target];
[uploader uploadTarget:target.intValue withConditions:conditions];
}
});
}
- (void)signalToStoragesToCheckExpirations {
// The same storage may be associated with several targets. Make sure to check for expirations
// only once per storage.
NSSet<id<GDTCORStorageProtocol>> *storages =
[NSSet setWithArray:[_registrar.targetToStorage allValues]];
for (id<GDTCORStorageProtocol> storage in storages) {
[storage checkForExpirations];
}
}
/** Returns the registered storage for the given NSNumber wrapped GDTCORTarget.
*
* @param target The NSNumber wrapping of a GDTCORTarget to find the storage instance of.
* @return The storage instance for the given target.
*/
- (nullable id<GDTCORStorageProtocol>)storageForTarget:(NSNumber *)target {
id<GDTCORStorageProtocol> storage = [GDTCORRegistrar sharedInstance].targetToStorage[target];
GDTCORAssert(storage, @"A storage must be registered for target %@", target);
return storage;
}
/** Returns the current upload conditions after making determinations about the network connection.
*
* @return The current upload conditions.
*/
- (GDTCORUploadConditions)uploadConditions {
GDTCORNetworkReachabilityFlags currentFlags = [GDTCORReachability currentFlags];
BOOL networkConnected = GDTCORReachabilityFlagsReachable(currentFlags);
if (!networkConnected) {
return GDTCORUploadConditionNoNetwork;
}
BOOL isWWAN = GDTCORReachabilityFlagsContainWWAN(currentFlags);
if (isWWAN) {
return GDTCORUploadConditionMobileData;
} else {
return GDTCORUploadConditionWifiData;
}
}
#pragma mark - GDTCORLifecycleProtocol
- (void)appWillForeground:(GDTCORApplication *)app {
// -startTimer is thread-safe.
[self startTimer];
[self signalToStoragesToCheckExpirations];
}
- (void)appWillBackground:(GDTCORApplication *)app {
dispatch_async(_coordinationQueue, ^{
[self stopTimer];
});
}
- (void)appWillTerminate:(GDTCORApplication *)application {
dispatch_async(_coordinationQueue, ^{
[self stopTimer];
});
}
@end

View File

@ -0,0 +1,95 @@
/*
* 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORConsoleLogger.h"
NS_ASSUME_NONNULL_BEGIN
/** A block type that could be run instead of normal assertion logging. No return type, no params.
*/
typedef void (^GDTCORAssertionBlock)(void);
/** Returns the result of executing a soft-linked method present in unit tests that allows a block
* to be run instead of normal assertion logging. This helps ameliorate issues with catching
* exceptions that occur on a dispatch_queue.
*
* @return A block that can be run instead of normal assert printing.
*/
FOUNDATION_EXPORT GDTCORAssertionBlock _Nullable GDTCORAssertionBlockToRunInstead(void);
#if defined(NS_BLOCK_ASSERTIONS)
#define GDTCORAssert(condition, ...) \
do { \
} while (0);
#define GDTCORFatalAssert(condition, ...) \
do { \
} while (0);
#else // defined(NS_BLOCK_ASSERTIONS)
/** Asserts using a console log, unless a block was specified to be run instead.
*
* @param condition The condition you'd expect to be YES.
*/
#define GDTCORAssert(condition, format, ...) \
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (__builtin_expect(!(condition), 0)) { \
GDTCORAssertionBlock assertionBlock = GDTCORAssertionBlockToRunInstead(); \
if (assertionBlock) { \
assertionBlock(); \
} else { \
NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
__assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
GDTCORLogAssert(NO, __assert_file__, __LINE__, format, ##__VA_ARGS__); \
__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
} \
} \
} while (0);
/** Asserts by logging to the console and throwing an exception if NS_BLOCK_ASSERTIONS is not
* defined.
*
* @param condition The condition you'd expect to be YES.
*/
#define GDTCORFatalAssert(condition, format, ...) \
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (__builtin_expect(!(condition), 0)) { \
GDTCORAssertionBlock assertionBlock = GDTCORAssertionBlockToRunInstead(); \
if (assertionBlock) { \
assertionBlock(); \
} else { \
NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
__assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
GDTCORLogAssert(YES, __assert_file__, __LINE__, format, ##__VA_ARGS__); \
[[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
object:self \
file:__assert_file__ \
lineNumber:__LINE__ \
description:format, ##__VA_ARGS__]; \
__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
} \
} \
} while (0);
#endif // defined(NS_BLOCK_ASSERTIONS)
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,66 @@
/*
* Copyright 2020 Google LLC
*
* 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h"
NS_ASSUME_NONNULL_BEGIN
/** The class calculates and caches the specified directory content size and uses add/remove signals
* from client the client to keep the size up to date without accessing file system.
* This is an internal class designed to be used by `GDTCORFlatFileStorage`.
* NOTE: The class is not thread-safe. The client must take care of synchronization.
*/
@interface GDTCORDirectorySizeTracker : NSObject
- (instancetype)init NS_UNAVAILABLE;
/** Initializes the object with a directory path.
* @param path The directory path to track content size.
*/
- (instancetype)initWithDirectoryPath:(NSString *)path;
/** Returns a cached or calculates (if there is no cached) directory content size.
* @return The directory content size in bytes calculated based on `NSURLFileSizeKey`.
*/
- (GDTCORStorageSizeBytes)directoryContentSize;
/** The client must call this method or `resetCachedSize` method each time a file or directory is
* added to the tracked directory.
* @param path The path to the added file. If the path is outside the tracked directory then the
* @param fileSize The size of the added file.
* method is no-op.
*/
- (void)fileWasAddedAtPath:(NSString *)path withSize:(GDTCORStorageSizeBytes)fileSize;
/** The client must call this method or `resetCachedSize` method each time a file or directory is
* removed from the tracked directory.
* @param path The path to the removed file. If the path is outside the tracked directory then the
* @param fileSize The size of the removed file.
* method is no-op.
*/
- (void)fileWasRemovedAtPath:(NSString *)path withSize:(GDTCORStorageSizeBytes)fileSize;
/** Invalidates cached directory size. */
- (void)resetCachedSize;
/** Returns URL resource value for `NSURLFileSizeKey` key for the specified URL. */
- (GDTCORStorageSizeBytes)fileSizeAtURL:(NSURL *)fileURL;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,27 @@
// Copyright 2022 Google LLC
//
// 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 <Foundation/Foundation.h>
/// The reason the event was "dropped". An event is considered "dropped" when it is no longer
/// tracked by the SDK (i.e. deleted).
typedef NS_ENUM(NSInteger, GDTCOREventDropReason) {
GDTCOREventDropReasonUnknown = 0,
GDTCOREventDropReasonMessageTooOld,
GDTCOREventDropReasonStorageFull,
GDTCOREventDropReasonPayloadTooBig,
GDTCOREventDropReasonMaxRetriesReached,
GDTCOREventDropReasonInvalidPayload,
GDTCOREventDropReasonServerError
};

View File

@ -0,0 +1,63 @@
/*
* 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h"
@class GDTCOREvent;
NS_ASSUME_NONNULL_BEGIN
/** A protocol defining the lifecycle events objects in the library must respond to immediately. */
@protocol GDTCORLifecycleProtocol <NSObject>
@optional
/** Indicates an imminent app termination in the rare occurrence when -applicationWillTerminate: has
* been called.
*
* @param app The GDTCORApplication instance.
*/
- (void)appWillTerminate:(GDTCORApplication *)app;
/** Indicates that the app is moving to background and eventual suspension or the current UIScene is
* deactivating.
*
* @param app The GDTCORApplication instance.
*/
- (void)appWillBackground:(GDTCORApplication *)app;
/** Indicates that the app is resuming operation or a UIScene is activating.
*
* @param app The GDTCORApplication instance.
*/
- (void)appWillForeground:(GDTCORApplication *)app;
@end
/** This class manages the library's response to app lifecycle events.
*
* When backgrounding, the library doesn't stop processing events, it's just that several background
* tasks will end up being created for every event that's sent, and the stateful objects of the
* library (GDTCORStorage and GDTCORUploadCoordinator instances) will deserialize themselves from
* and to disk before and after every operation, respectively.
*/
@interface GDTCORLifecycle : NSObject
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,57 @@
/*
* Copyright 2022 Google LLC
*
* 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h"
@class FBLPromise<ResultType>;
@class GDTCOREvent;
@class GDTCORMetrics;
NS_ASSUME_NONNULL_BEGIN
/// A storage delegate that can perform metrics related tasks.
@protocol GDTCORMetricsControllerProtocol <GDTCORStorageDelegate>
/// Updates the corresponding log source metricss for the given events dropped for a given
/// reason.
/// @param reason The reason why the events are being dropped.
/// @param events The events that being dropped.
- (FBLPromise<NSNull *> *)logEventsDroppedForReason:(GDTCOREventDropReason)reason
events:(NSSet<GDTCOREvent *> *)events;
/// Gets and resets the currently stored metrics.
/// @return A promise resolving with the metrics retrieved before the reset.
- (FBLPromise<GDTCORMetrics *> *)getAndResetMetrics;
/// Offers metrics for re-storing in storage.
/// @note If the metrics are determined to be from the future, they will be ignored.
/// @param metrics The metrics to offer for storage.
- (FBLPromise<NSNull *> *)offerMetrics:(GDTCORMetrics *)metrics;
@end
/// Returns a metrics controller instance for the given target.
/// @param target The target to retrieve a corresponding metrics controller from.
/// @return The given target's corresponding metrics controller instance, or `nil` if it does not
/// have one.
FOUNDATION_EXPORT
id<GDTCORMetricsControllerProtocol> _Nullable GDTCORMetricsControllerInstanceForTarget(
GDTCORTarget target);
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,232 @@
/*
* 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 <Foundation/Foundation.h>
#if !TARGET_OS_WATCH
#import <SystemConfiguration/SystemConfiguration.h>
#endif
#if TARGET_OS_IOS || TARGET_OS_TV
#import <UIKit/UIKit.h>
#elif TARGET_OS_OSX
#import <AppKit/AppKit.h>
#elif TARGET_OS_WATCH
#import <WatchKit/WatchKit.h>
#endif // TARGET_OS_IOS || TARGET_OS_TV
// TODO(Xcode 15): When Xcode 15 is the minimum supported Xcode version,
// it will be unnecessary to check if `TARGET_OS_VISION` is defined.
#if TARGET_OS_IOS && (!defined(TARGET_OS_VISION) || !TARGET_OS_VISION)
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#endif
NS_ASSUME_NONNULL_BEGIN
/** The GoogleDataTransport library version. */
FOUNDATION_EXPORT NSString *const kGDTCORVersion;
/** A notification sent out if the app is backgrounding. */
FOUNDATION_EXPORT NSString *const kGDTCORApplicationDidEnterBackgroundNotification;
/** A notification sent out if the app is foregrounding. */
FOUNDATION_EXPORT NSString *const kGDTCORApplicationWillEnterForegroundNotification;
/** A notification sent out if the app is terminating. */
FOUNDATION_EXPORT NSString *const kGDTCORApplicationWillTerminateNotification;
/** The different possible network connection type. */
typedef NS_ENUM(NSInteger, GDTCORNetworkType) {
GDTCORNetworkTypeUNKNOWN = 0,
GDTCORNetworkTypeWIFI = 1,
GDTCORNetworkTypeMobile = 2,
};
/** The different possible network connection mobile subtype. */
typedef NS_ENUM(NSInteger, GDTCORNetworkMobileSubtype) {
GDTCORNetworkMobileSubtypeUNKNOWN = 0,
GDTCORNetworkMobileSubtypeGPRS = 1,
GDTCORNetworkMobileSubtypeEdge = 2,
GDTCORNetworkMobileSubtypeWCDMA = 3,
GDTCORNetworkMobileSubtypeHSDPA = 4,
GDTCORNetworkMobileSubtypeHSUPA = 5,
GDTCORNetworkMobileSubtypeCDMA1x = 6,
GDTCORNetworkMobileSubtypeCDMAEVDORev0 = 7,
GDTCORNetworkMobileSubtypeCDMAEVDORevA = 8,
GDTCORNetworkMobileSubtypeCDMAEVDORevB = 9,
GDTCORNetworkMobileSubtypeHRPD = 10,
GDTCORNetworkMobileSubtypeLTE = 11,
};
#if !TARGET_OS_WATCH
/** Define SCNetworkReachabilityFlags as GDTCORNetworkReachabilityFlags on non-watchOS. */
typedef SCNetworkReachabilityFlags GDTCORNetworkReachabilityFlags;
/** Define SCNetworkReachabilityRef as GDTCORNetworkReachabilityRef on non-watchOS. */
typedef SCNetworkReachabilityRef GDTCORNetworkReachabilityRef;
#else
/** The different possible reachabilityFlags option on watchOS. */
typedef NS_OPTIONS(uint32_t, GDTCORNetworkReachabilityFlags) {
kGDTCORNetworkReachabilityFlagsReachable = 1 << 1,
// TODO(doudounan): Add more options on watchOS if watchOS network connection information relative
// APIs available in the future.
};
/** Define a struct as GDTCORNetworkReachabilityRef on watchOS to store network connection
* information. */
typedef struct {
// TODO(doudounan): Store network connection information on watchOS if watchOS network connection
// information relative APIs available in the future.
} GDTCORNetworkReachabilityRef;
#endif
/** Returns a URL to the root directory under which all GDT-associated data must be saved.
*
* @return A URL to the root directory under which all GDT-associated data must be saved.
*/
NSURL *GDTCORRootDirectory(void);
/** Compares flags with the reachable flag (on non-watchos with both reachable and
* connectionRequired flags), if available, and returns YES if network reachable.
*
* @param flags The set of reachability flags.
* @return YES if the network is reachable, NO otherwise.
*/
BOOL GDTCORReachabilityFlagsReachable(GDTCORNetworkReachabilityFlags flags);
/** Compares flags with the WWAN reachability flag, if available, and returns YES if present.
*
* @param flags The set of reachability flags.
* @return YES if the WWAN flag is set, NO otherwise.
*/
BOOL GDTCORReachabilityFlagsContainWWAN(GDTCORNetworkReachabilityFlags flags);
/** Generates an enum message GDTCORNetworkType representing network connection type.
*
* @return A GDTCORNetworkType representing network connection type.
*/
GDTCORNetworkType GDTCORNetworkTypeMessage(void);
/** Generates an enum message GDTCORNetworkMobileSubtype representing network connection mobile
* subtype.
*
* @return A GDTCORNetworkMobileSubtype representing network connection mobile subtype.
*/
GDTCORNetworkMobileSubtype GDTCORNetworkMobileSubTypeMessage(void);
/** Identifies the model of the device on which the library is currently working on.
*
* @return A NSString representing the device model.
*/
NSString *_Nonnull GDTCORDeviceModel(void);
/** Writes the given object to the given fileURL and populates the given error if it fails.
*
* @param obj The object to encode.
* @param filePath The path to write the object to. Can be nil if you just need the data.
* @param error The error to populate if something goes wrong.
* @return The data of the archive. If error is nil, it's been written to disk.
*/
NSData *_Nullable GDTCOREncodeArchive(id<NSSecureCoding> obj,
NSString *_Nullable filePath,
NSError *_Nullable *error);
/// Decodes an object of the given class from the given archive path and populates the given error
/// if it fails.
/// @param archiveClass The class of the archive's root object.
/// @param archivePath The path to the archived data.
/// @param error The error to populate if something goes wrong.
id<NSSecureCoding> _Nullable GDTCORDecodeArchiveAtPath(Class archiveClass,
NSString *_Nonnull archivePath,
NSError **_Nonnull error);
/// Decodes an object of the given class from the given data and populates the given error if it
/// fails.
/// @param archiveClass The class of the archive's root object.
/// @param archiveData The data to decode.
/// @param error The error to populate if something goes wrong.
id<NSSecureCoding> _Nullable GDTCORDecodeArchive(Class archiveClass,
NSData *_Nonnull archiveData,
NSError **_Nonnull error);
/** Writes the provided data to a file at the provided path. Intermediate directories will be
* created as needed.
* @param data The file content.
* @param filePath The path to the file to write the provided data.
* @param outError The error to populate if something goes wrong.
* @return `YES` in the case of success, `NO` otherwise.
*/
BOOL GDTCORWriteDataToFile(NSData *data, NSString *filePath, NSError *_Nullable *outError);
/** A typedef identify background identifiers. */
typedef volatile NSUInteger GDTCORBackgroundIdentifier;
/** A background task's invalid sentinel value. */
FOUNDATION_EXPORT const GDTCORBackgroundIdentifier GDTCORBackgroundIdentifierInvalid;
#if TARGET_OS_IOS || TARGET_OS_TV
/** A protocol that wraps UIApplicationDelegate, WKExtensionDelegate or NSObject protocol, depending
* on the platform.
*/
@protocol GDTCORApplicationDelegate <UIApplicationDelegate>
#elif TARGET_OS_OSX
@protocol GDTCORApplicationDelegate <NSApplicationDelegate>
#elif TARGET_OS_WATCH
@protocol GDTCORApplicationDelegate <WKExtensionDelegate>
#else
@protocol GDTCORApplicationDelegate <NSObject>
#endif // TARGET_OS_IOS || TARGET_OS_TV
@end
@protocol GDTCORApplicationProtocol <NSObject>
@required
/** Flag to determine if the application is running in the background. */
@property(atomic, readonly) BOOL isRunningInBackground;
/** Creates a background task with the returned identifier if on a suitable platform.
*
* @name name The name of the task, useful for debugging which background tasks are running.
* @param handler The handler block that is called if the background task expires.
* @return An identifier for the background task, or GDTCORBackgroundIdentifierInvalid if one
* couldn't be created.
*/
- (GDTCORBackgroundIdentifier)beginBackgroundTaskWithName:(NSString *)name
expirationHandler:(void (^__nullable)(void))handler;
/** Ends the background task if the identifier is valid.
*
* @param bgID The background task to end.
*/
- (void)endBackgroundTask:(GDTCORBackgroundIdentifier)bgID;
@end
/** A cross-platform application class. */
@interface GDTCORApplication : NSObject <GDTCORApplicationProtocol, GDTCORApplicationDelegate>
/** Creates and/or returns the shared application instance.
*
* @return The shared application instance.
*/
+ (nullable GDTCORApplication *)sharedApplication;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,31 @@
/*
* 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORPlatform.h"
NS_ASSUME_NONNULL_BEGIN
/** This class helps determine upload conditions by determining connectivity. */
@interface GDTCORReachability : NSObject
/** The current set flags indicating network conditions */
+ (GDTCORNetworkReachabilityFlags)currentFlags;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,59 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORUploader.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h"
NS_ASSUME_NONNULL_BEGIN
/** Manages the registration of targets with the transport SDK. */
@interface GDTCORRegistrar : NSObject <GDTCORLifecycleProtocol>
/** Creates and/or returns the singleton instance.
*
* @return The singleton instance of this class.
*/
+ (instancetype)sharedInstance;
/** Registers a backend implementation with the GoogleDataTransport infrastructure.
*
* @param backend The backend object to register with the given target.
* @param target The target this backend object will be responsible for.
*/
- (void)registerUploader:(id<GDTCORUploader>)backend target:(GDTCORTarget)target;
/** Registers a storage implementation with the GoogleDataTransport infrastructure.
*
* @param storage The storage object to register with the given target.
* @param target The target this storage object will be responsible for.
*/
- (void)registerStorage:(id<GDTCORStorageProtocol>)storage target:(GDTCORTarget)target;
/** Registers a metrics controller implementation with the GoogleDataTransport infrastructure.
*
* @param metricsController The metrics controller object to register with the given target.
* @param target The target this metrics controller object will be responsible for.
*/
- (void)registerMetricsController:(id<GDTCORMetricsControllerProtocol>)metricsController
target:(GDTCORTarget)target;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,61 @@
/*
* Copyright 2020 Google LLC
*
* 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h"
NS_ASSUME_NONNULL_BEGIN
/** This class enables the finding of events by matching events with the properties of this class.
*/
@interface GDTCORStorageEventSelector : NSObject
/** The target to find events for. Required. */
@property(readonly, nonatomic) GDTCORTarget selectedTarget;
/** Finds a specific event. */
@property(nullable, readonly, nonatomic) NSSet<NSString *> *selectedEventIDs;
/** Finds all events of a mappingID. */
@property(nullable, readonly, nonatomic) NSSet<NSString *> *selectedMappingIDs;
/** Finds all events matching the qosTiers in this list. */
@property(nullable, readonly, nonatomic) NSSet<NSNumber *> *selectedQosTiers;
/** Initializes an event selector that will find all events for the given target.
*
* @param target The selected target.
* @return An immutable event selector instance.
*/
+ (instancetype)eventSelectorForTarget:(GDTCORTarget)target;
/** Instantiates an event selector.
*
* @param target The selected target.
* @param eventIDs Optional param to find an event matching this eventID.
* @param mappingIDs Optional param to find events matching this mappingID.
* @param qosTiers Optional param to find events matching the given QoS tiers.
* @return An immutable event selector instance.
*/
- (instancetype)initWithTarget:(GDTCORTarget)target
eventIDs:(nullable NSSet<NSString *> *)eventIDs
mappingIDs:(nullable NSSet<NSString *> *)mappingIDs
qosTiers:(nullable NSSet<NSNumber *> *)qosTiers;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,210 @@
/*
* Copyright 2020 Google LLC
*
* 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageEventSelector.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h"
@class GDTCOREvent;
@class GDTCORClock;
@class GDTCORUploadBatch;
@class FBLPromise<ValueType>;
@protocol GDTCORStorageDelegate;
NS_ASSUME_NONNULL_BEGIN
typedef void (^GDTCORStorageBatchBlock)(NSNumber *_Nullable newBatchID,
NSSet<GDTCOREvent *> *_Nullable batchEvents);
#pragma mark - GDTCORStorageProtocol
/** Defines the interface a storage subsystem is expected to implement. */
@protocol GDTCORStorageProtocol <NSObject, GDTCORLifecycleProtocol>
/// The object that acts as the delegate of the storage instance.
@property(nonatomic, weak, nullable) id<GDTCORStorageDelegate> delegate;
@required
/** Stores an event and calls onComplete with a non-nil error if anything went wrong.
*
* @param event The event to store
* @param completion The completion block to call after an attempt to store the event has been made.
*/
- (void)storeEvent:(GDTCOREvent *)event
onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion;
/** Returns YES if some events have been stored for the given target, NO otherwise.
*
* @param onComplete The completion block to invoke when determining if there are events is done.
*/
- (void)hasEventsForTarget:(GDTCORTarget)target onComplete:(void (^)(BOOL hasEvents))onComplete;
/** Constructs an event batch with the given event selector. Events in this batch will not be
* returned in any queries or other batches until the batch is removed.
*
* @param eventSelector The event selector used to find the events.
* @param expiration The expiration time of the batch. If removeBatchWithID:deleteEvents:onComplete:
* is not called within this time frame, the batch will be removed with its events deleted.
* @param onComplete The completion handler to be called when the events have been fetched.
*/
- (void)batchWithEventSelector:(nonnull GDTCORStorageEventSelector *)eventSelector
batchExpiration:(nonnull NSDate *)expiration
onComplete:(nonnull GDTCORStorageBatchBlock)onComplete;
/** Removes the event batch.
*
* @param batchID The batchID to remove.
* @param deleteEvents If YES, the events in this batch are deleted.
* @param onComplete The completion handler to call when the batch removal process has completed.
*/
- (void)removeBatchWithID:(NSNumber *)batchID
deleteEvents:(BOOL)deleteEvents
onComplete:(void (^_Nullable)(void))onComplete;
/** Finds the batchIDs for the given target and calls the callback block.
*
* @param target The target.
* @param onComplete The block to invoke with the set of current batchIDs.
*/
- (void)batchIDsForTarget:(GDTCORTarget)target
onComplete:(void (^)(NSSet<NSNumber *> *_Nullable batchIDs))onComplete;
/** Checks the storage for expired events and batches, deletes them if they're expired. */
- (void)checkForExpirations;
/** Persists the given data with the given key.
*
* @param data The data to store.
* @param key The unique key to store it to.
* @param onComplete An block to be run when storage of the data is complete.
*/
- (void)storeLibraryData:(NSData *)data
forKey:(NSString *)key
onComplete:(nullable void (^)(NSError *_Nullable error))onComplete;
/** Retrieves the stored data for the given key and optionally sets a new value.
*
* @param key The key corresponding to the desired data.
* @param onFetchComplete The callback to invoke with the data once it's retrieved.
* @param setValueBlock This optional block can provide a new value to set.
*/
- (void)libraryDataForKey:(nonnull NSString *)key
onFetchComplete:(nonnull void (^)(NSData *_Nullable data,
NSError *_Nullable error))onFetchComplete
setNewValue:(NSData *_Nullable (^_Nullable)(void))setValueBlock;
/** Removes data from storage and calls the callback when complete.
*
* @param key The key of the data to remove.
* @param onComplete The callback that will be invoked when removing the data is complete.
*/
- (void)removeLibraryDataForKey:(NSString *)key
onComplete:(void (^)(NSError *_Nullable error))onComplete;
/** Calculates and returns the total disk size that this storage consumes.
*
* @param onComplete The callback that will be invoked once storage size calculation is complete.
*/
- (void)storageSizeWithCallback:(void (^)(GDTCORStorageSizeBytes storageSize))onComplete;
@end
#pragma mark - GDTCORStoragePromiseProtocol
// TODO(ncooke3): Consider complete replacing block based API by promise API.
@class GDTCORMetricsMetadata;
@class GDTCORStorageMetadata;
/** Promise based version of API defined in GDTCORStorageProtocol. See API docs for corresponding
* methods in GDTCORStorageProtocol. */
@protocol GDTCORStoragePromiseProtocol <GDTCORStorageProtocol>
- (FBLPromise<NSSet<NSNumber *> *> *)batchIDsForTarget:(GDTCORTarget)target;
- (FBLPromise<NSNull *> *)removeBatchWithID:(NSNumber *)batchID deleteEvents:(BOOL)deleteEvents;
- (FBLPromise<NSNull *> *)removeBatchesWithIDs:(NSSet<NSNumber *> *)batchIDs
deleteEvents:(BOOL)deleteEvents;
- (FBLPromise<NSNull *> *)removeAllBatchesForTarget:(GDTCORTarget)target
deleteEvents:(BOOL)deleteEvents;
/// Fetches metrics metadata from storage, passes them to the given handler, and writes the
/// resulting metrics metadata from the given handler to storage.
/// @note This API is thread-safe.
/// @param handler A handler to process the fetch result and return an updated value to store.
/// @return A promise that is fulfilled if the update is successful, and rejected otherwise.
- (FBLPromise<NSNull *> *)fetchAndUpdateMetricsWithHandler:
(GDTCORMetricsMetadata * (^)(GDTCORMetricsMetadata *_Nullable fetchedMetadata,
NSError *_Nullable fetchError))handler;
/// Fetches and returns storage metadata.
- (FBLPromise<GDTCORStorageMetadata *> *)fetchStorageMetadata;
/** See `hasEventsForTarget:onComplete:`.
* @return A promise object that is resolved with @YES if there are events for the specified target
* and @NO otherwise.
*/
- (FBLPromise<NSNumber *> *)hasEventsForTarget:(GDTCORTarget)target;
/** See `batchWithEventSelector:batchExpiration:onComplete:`
* The promise is rejected when there are no events for the specified selector.
*/
- (FBLPromise<GDTCORUploadBatch *> *)batchWithEventSelector:
(GDTCORStorageEventSelector *)eventSelector
batchExpiration:(NSDate *)expiration;
@end
/** Retrieves the storage instance for the given target.
*
* @param target The target.
* * @return The storage instance registered for the target, or nil if there is none.
*/
FOUNDATION_EXPORT
id<GDTCORStorageProtocol> _Nullable GDTCORStorageInstanceForTarget(GDTCORTarget target);
FOUNDATION_EXPORT
id<GDTCORStoragePromiseProtocol> _Nullable GDTCORStoragePromiseInstanceForTarget(
GDTCORTarget target);
#pragma mark - GDTCORStorageDelegate
/// A type that can be delegated actions from a storage instance.
@protocol GDTCORStorageDelegate <NSObject>
/// Tells the delegate that the storage instance has removed a set of expired events.
/// @param storage The storage instance informing the delegate of this impending event.
/// @param events A set of events that were removed from storage due to their expiration.
- (void)storage:(id<GDTCORStorageProtocol>)storage
didRemoveExpiredEvents:(NSSet<GDTCOREvent *> *)events;
/// Tells the delegate that the storage instance has dropped an event due to the event cache being
/// full.
/// @param storage The storage instance informing the delegate of this impending event.
/// @param event An event that was dropped due to the event cache being full.
- (void)storage:(id<GDTCORStorageProtocol>)storage didDropEvent:(GDTCOREvent *)event;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,18 @@
// Copyright 2022 Google LLC
//
// 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 <Foundation/Foundation.h>
/// The data type to represent storage size.
typedef uint64_t GDTCORStorageSizeBytes;

View File

@ -0,0 +1,59 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTargets.h"
NS_ASSUME_NONNULL_BEGIN
/** Options that define a set of upload conditions. This is used to help minimize end user data
* consumption impact.
*/
typedef NS_OPTIONS(NSInteger, GDTCORUploadConditions) {
/** An upload shouldn't be attempted, because there's no network. */
GDTCORUploadConditionNoNetwork = 1 << 0,
/** An upload would likely use mobile data. */
GDTCORUploadConditionMobileData = 1 << 1,
/** An upload would likely use wifi data. */
GDTCORUploadConditionWifiData = 1 << 2,
/** An upload uses some sort of network connection, but it's unclear which. */
GDTCORUploadConditionUnclearConnection = 1 << 3,
/** A high priority event has occurred. */
GDTCORUploadConditionHighPriority = 1 << 4,
};
/** This protocol defines the common interface for uploader implementations. */
@protocol GDTCORUploader <NSObject, GDTCORLifecycleProtocol>
@required
/** Uploads events to the backend using this specific backend's chosen format.
*
* @param conditions The conditions that the upload attempt is likely to occur under.
*/
- (void)uploadTarget:(GDTCORTarget)target withConditions:(GDTCORUploadConditions)conditions;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,27 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREndpoints.h"
@interface GDTCOREndpoints ()
/** Returns the list of all the upload URLs used by the transport library.
*
* @return Map of the transport target and the URL used for uploading the events for that target.
*/
+ (NSDictionary<NSNumber *, NSURL *> *)uploadURLs;
@end

View File

@ -0,0 +1,33 @@
/*
* Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREvent.h"
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORClock.h"
NS_ASSUME_NONNULL_BEGIN
@interface GDTCOREvent ()
/** The unique ID of the event. This property is for testing only. */
@property(nonatomic, readwrite) NSString *eventID;
/** Generates a unique event ID. */
+ (NSString *)nextEventID;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,28 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORFlatFileStorage.h"
@class FBLPromise<ValueType>;
NS_ASSUME_NONNULL_BEGIN
/// The category extends `GDTCORFlatFileStorage` API with `GDTCORStoragePromiseProtocol` methods.
@interface GDTCORFlatFileStorage (Promises) <GDTCORStoragePromiseProtocol>
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,158 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageEventSelector.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageProtocol.h"
@class GDTCOREvent;
@class GDTCORUploadCoordinator;
NS_ASSUME_NONNULL_BEGIN
/** The event components eventID dictionary key. */
FOUNDATION_EXPORT NSString *const kGDTCOREventComponentsEventIDKey;
/** The event components qosTier dictionary key. */
FOUNDATION_EXPORT NSString *const kGDTCOREventComponentsQoSTierKey;
/** The event components mappingID dictionary key. */
FOUNDATION_EXPORT NSString *const kGDTCOREventComponentsMappingIDKey;
/** The event components expirationDate dictionary key. */
FOUNDATION_EXPORT NSString *const kGDTCOREventComponentsExpirationKey;
/** The batch components target dictionary key. */
FOUNDATION_EXPORT NSString *const kGDTCORBatchComponentsTargetKey;
/** The batch components batchID dictionary key. */
FOUNDATION_EXPORT NSString *const kGDTCORBatchComponentsBatchIDKey;
/** The batch components expiration dictionary key. */
FOUNDATION_EXPORT NSString *const kGDTCORBatchComponentsExpirationKey;
/** The maximum allowed disk space taken by the stored data. */
FOUNDATION_EXPORT const uint64_t kGDTCORFlatFileStorageSizeLimit;
FOUNDATION_EXPORT NSString *const GDTCORFlatFileStorageErrorDomain;
typedef NS_ENUM(NSInteger, GDTCORFlatFileStorageError) {
GDTCORFlatFileStorageErrorSizeLimitReached = 0
};
/** Manages the storage of events. This class is thread-safe.
*
* Event files will be stored as follows:
* <app cache>/google-sdk-events/<classname>/gdt_event_data/<target>/<eventID>.<qosTier>.<mappingID>
*
* Library data will be stored as follows:
* <app cache>/google-sdk-events/<classname>/gdt_library_data/<libraryDataKey>
*
* Batch data will be stored as follows:
* <app
* cache>/google-sdk-events/<classname>/gdt_batch_data/<target>.<batchID>/<eventID>.<qosTier>.<mappingID>
*/
@interface GDTCORFlatFileStorage : NSObject <GDTCORStorageProtocol, GDTCORLifecycleProtocol>
/** The queue on which all storage work will occur. */
@property(nonatomic) dispatch_queue_t storageQueue;
/** The upload coordinator instance used by this storage instance. */
@property(nonatomic) GDTCORUploadCoordinator *uploadCoordinator;
/** Creates and/or returns the storage singleton.
*
* @return The storage singleton.
*/
+ (instancetype)sharedInstance;
/** Returns the base directory under which all events will be stored.
*
* @return The base directory under which all events will be stored.
*/
+ (NSString *)eventDataStoragePath;
/** Returns the base directory under which all library data will be stored.
*
* @return The base directory under which all library data will be stored.
*/
+ (NSString *)libraryDataStoragePath;
/** Returns the base directory under which all batch data will be stored.
*
* @return The base directory under which all batch data will be stored.
*/
+ (NSString *)batchDataStoragePath;
/** */
+ (NSString *)batchPathForTarget:(GDTCORTarget)target
batchID:(NSNumber *)batchID
expirationDate:(NSDate *)expirationDate;
/** Returns a constructed storage path based on the given values. This path may not exist.
*
* @param target The target, which is necessary to be given a path.
* @param eventID The eventID.
* @param qosTier The qosTier.
* @param expirationDate The expirationDate as a 1970-relative time interval.
* @param mappingID The mappingID.
* @return The path representing the combination of the given parameters.
*/
+ (NSString *)pathForTarget:(GDTCORTarget)target
eventID:(NSString *)eventID
qosTier:(NSNumber *)qosTier
expirationDate:(NSDate *)expirationDate
mappingID:(NSString *)mappingID;
/** Returns extant paths that match all of the given parameters.
*
* @param eventIDs The list of eventIDs to look for, or nil for any.
* @param qosTiers The list of qosTiers to look for, or nil for any.
* @param mappingIDs The list of mappingIDs to look for, or nil for any.
* @param onComplete The completion to call once the paths have been discovered.
*/
- (void)pathsForTarget:(GDTCORTarget)target
eventIDs:(nullable NSSet<NSString *> *)eventIDs
qosTiers:(nullable NSSet<NSNumber *> *)qosTiers
mappingIDs:(nullable NSSet<NSString *> *)mappingIDs
onComplete:(void (^)(NSSet<NSString *> *paths))onComplete;
/** Fetches the current batchID counter value from library storage, increments it, and sets the new
* value. Returns nil if a batchID was not able to be created for some reason.
*
* @param onComplete A block to execute when creating the next batchID is complete.
*/
- (void)nextBatchID:(void (^)(NSNumber *_Nullable batchID))onComplete;
/** Constructs a dictionary of event filename components.
*
* @param fileName The event filename to split.
* @return The dictionary of event component keys to their values.
*/
- (nullable NSDictionary<NSString *, id> *)eventComponentsFromFilename:(NSString *)fileName;
/** Constructs a dictionary of batch filename components.
*
* @param fileName The batch folder name to split.
* @return The dictionary of batch component keys to their values.
*/
- (nullable NSDictionary<NSString *, id> *)batchComponentsFromFilename:(NSString *)fileName;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,53 @@
// Copyright 2022 Google LLC
//
// 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h"
@class GDTCOREvent;
NS_ASSUME_NONNULL_BEGIN
/// A model object that tracks, per log source, the number of events dropped for a variety of
/// reasons. An event is considered "dropped" when the event is no longer persisted by the SDK.
@interface GDTCORLogSourceMetrics : NSObject <NSSecureCoding>
/// Creates an empty log source metrics instance.
+ (instancetype)metrics;
/// Creates a log source metrics for a collection of events that were dropped for a given reason.
/// @param events The collection of events that were dropped.
/// @param reason The reason for which given events were dropped.
+ (instancetype)metricsWithEvents:(NSArray<GDTCOREvent *> *)events
droppedForReason:(GDTCOREventDropReason)reason;
/// This API is unavailable.
- (instancetype)init NS_UNAVAILABLE;
/// Returns a log source metrics instance created by merging the receiving log
/// source metrics with the given log source metrics.
/// @param logSourceMetrics The given log source metrics to merge with.
- (GDTCORLogSourceMetrics *)logSourceMetricsByMergingWithLogSourceMetrics:
(GDTCORLogSourceMetrics *)logSourceMetrics;
/// Returns a Boolean value that indicates whether the receiving log source metrics is equal to
/// the given log source metrics.
/// @param otherLogSourceMetrics The log source metrics with which to compare the
/// receiving log source metrics.
- (BOOL)isEqualToLogSourceMetrics:(GDTCORLogSourceMetrics *)otherLogSourceMetrics;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,61 @@
// Copyright 2022 Google LLC
//
// 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCOREventDataObject.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h"
@class GDTCORLogSourceMetrics;
@class GDTCORMetricsMetadata;
@class GDTCORStorageMetadata;
NS_ASSUME_NONNULL_BEGIN
/// An object representing metrics that represent a snapshot of the SDK's state and performance.
@interface GDTCORMetrics : NSObject
/// The start of the time window over which the metrics were collected.
@property(nonatomic, readonly) NSDate *collectionStartDate;
/// The log source metrics associated with the metrics.
@property(nonatomic, readonly) GDTCORLogSourceMetrics *logSourceMetrics;
/// The end of the time window over which the metrics were collected.
@property(nonatomic, readonly) NSDate *collectionEndDate;
/// The number of bytes the event cache was consuming in storage.
@property(nonatomic, readonly) GDTCORStorageSizeBytes currentCacheSize;
/// The maximum number of bytes that the event cache is allowed to grow.
@property(nonatomic, readonly) GDTCORStorageSizeBytes maxCacheSize;
/// The bundle ID associated with the metrics being collected.
@property(nonatomic, readonly) NSString *bundleID;
/// Creates a metrics instance with the provided metadata.
/// @param metricsMetadata The provided metrics metadata.
/// @param storageMetadata The provided storage metadata.
+ (instancetype)metricsWithMetricsMetadata:(GDTCORMetricsMetadata *)metricsMetadata
storageMetadata:(GDTCORStorageMetadata *)storageMetadata;
/// Returns a Boolean value that indicates whether the receiving metrics is equal to the given
/// metrics.
/// @param otherMetrics The metrics with which to compare the receiving metrics.
- (BOOL)isEqualToMetrics:(GDTCORMetrics *)otherMetrics;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,37 @@
// Copyright 2022 Google LLC
//
// 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORMetricsControllerProtocol.h"
@protocol GDTCORStoragePromiseProtocol;
NS_ASSUME_NONNULL_BEGIN
@interface GDTCORMetricsController : NSObject <GDTCORMetricsControllerProtocol>
/// Returns the event metrics controller singleton.
+ (instancetype)sharedInstance;
/// Designated initializer.
/// @param storage The storage object to read and write metrics data from.
- (instancetype)initWithStorage:(id<GDTCORStoragePromiseProtocol>)storage NS_DESIGNATED_INITIALIZER;
/// This API is unavailable.
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,50 @@
// Copyright 2022 Google LLC
//
// 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCOREventDropReason.h"
@class GDTCORLogSourceMetrics;
NS_ASSUME_NONNULL_BEGIN
/// An encodable model object that contains metadata that is persisted in storage until ready to be
/// used to create a ``GDTCORMetrics`` instance.
@interface GDTCORMetricsMetadata : NSObject <NSSecureCoding>
/// The start of the time window over which the metrics were collected.
@property(nonatomic, copy, readonly) NSDate *collectionStartDate;
/// The log source metrics associated with the metrics.
@property(nonatomic, copy, readonly) GDTCORLogSourceMetrics *logSourceMetrics;
/// Creates a metrics metadata object with the provided information.
/// @param collectedSinceDate The start of the time window over which the metrics were collected.
/// @param logSourceMetrics The metrics object that tracks metrics for each log source.
+ (instancetype)metadataWithCollectionStartDate:(NSDate *)collectedSinceDate
logSourceMetrics:(GDTCORLogSourceMetrics *)logSourceMetrics;
/// This API is unavailable.
- (instancetype)init NS_UNAVAILABLE;
/// Returns a Boolean value that indicates whether the receiving metrics metadata is equal to
/// the given metrics metadata.
/// @param otherMetricsMetadata The metrics metadata with which to compare the
/// receiving metrics metadata.
- (BOOL)isEqualToMetricsMetadata:(GDTCORMetricsMetadata *)otherMetricsMetadata;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,30 @@
/*
* 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORReachability.h"
@interface GDTCORReachability ()
/** Allows manually setting the flags for testing purposes. */
@property(nonatomic, readwrite) GDTCORNetworkReachabilityFlags flags;
/** Creates/returns the singleton instance of this class.
*
* @return The singleton instance of this class.
*/
+ (instancetype)sharedInstance;
@end

View File

@ -0,0 +1,39 @@
/*
* Copyright 2018 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 "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h"
@interface GDTCORRegistrar ()
NS_ASSUME_NONNULL_BEGIN
/** The concurrent queue on which all registration occurs. */
@property(nonatomic, readonly) dispatch_queue_t registrarQueue;
/** A map of targets to backend implementations. */
@property(atomic, readonly) NSMutableDictionary<NSNumber *, id<GDTCORUploader>> *targetToUploader;
/** A map of targets to storage instances. */
@property(atomic, readonly)
NSMutableDictionary<NSNumber *, id<GDTCORStorageProtocol>> *targetToStorage;
/** A map of targets to metrics controller instances. */
@property(atomic, readonly)
NSMutableDictionary<NSNumber *, id<GDTCORMetricsControllerProtocol>> *targetToMetricsController;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,41 @@
// Copyright 2022 Google LLC
//
// 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORStorageSizeBytes.h"
NS_ASSUME_NONNULL_BEGIN
/// A model object that contains metadata about the current state of the SDK's storage container.
@interface GDTCORStorageMetadata : NSObject
/// The number of bytes the event cache is consuming in storage.
@property(nonatomic, readonly) GDTCORStorageSizeBytes currentCacheSize;
/// The maximum number of bytes that the event cache may consume in storage.
@property(nonatomic, readonly) GDTCORStorageSizeBytes maxCacheSize;
/// Creates a storage metadata object with the provided information.
/// @param currentCacheSize The current number of bytes the event cache is consuming.
/// @param maxCacheSize The current maximum capacity (in bytes) that the event cache may consume.
+ (instancetype)metadataWithCurrentCacheSize:(GDTCORStorageSizeBytes)currentCacheSize
maxCacheSize:(GDTCORStorageSizeBytes)maxCacheSize;
/// This API is unavailable.
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,57 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h"
@class GDTCOREvent;
@protocol GDTCOREventTransformer;
NS_ASSUME_NONNULL_BEGIN
/** Manages the transforming of events. It's desirable for this to be its own class
* because running all events through a single instance ensures that transformers are thread-safe.
* Having a per-transport queue to run on isn't sufficient because transformer objects could
* maintain state (or at least, there's nothing to stop them from doing that) and the same instances
* may be used across multiple instances.
*/
@interface GDTCORTransformer : NSObject <GDTCORLifecycleProtocol>
/** Instantiates or returns the event transformer singleton.
*
* @return The singleton instance of the event transformer.
*/
+ (instancetype)sharedInstance;
/** Writes the result of applying the given transformers' `transformGDTEvent:` method on the given
* event.
*
* @note If the app is suspended, a background task will be created to complete work in-progress,
* but this method will not send any further events until the app is resumed.
*
* @param event The event to apply transformers on.
* @param transformers The list of transformers to apply.
* @param completion A block to run when an event was written to disk or dropped.
*/
- (void)transformEvent:(GDTCOREvent *)event
withTransformers:(nullable NSArray<id<GDTCOREventTransformer>> *)transformers
onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,37 @@
/*
* Copyright 2020 Google LLC
*
* 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 "GoogleDataTransport/GDTCORLibrary/Private/GDTCORTransformer.h"
@protocol GDTCORApplicationProtocol;
NS_ASSUME_NONNULL_BEGIN
@interface GDTCORTransformer ()
/** The queue on which all work will occur. */
@property(nonatomic) dispatch_queue_t eventWritingQueue;
/** The application instance that is used to begin/end background tasks. */
@property(nonatomic, readonly) id<GDTCORApplicationProtocol> application;
/** The internal initializer. Should be used in tests only to create an instance with a
* particular(fake) application instance. */
- (instancetype)initWithApplication:(id<GDTCORApplicationProtocol>)application;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,39 @@
/*
* 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 "GoogleDataTransport/GDTCORLibrary/Public/GoogleDataTransport/GDTCORTransport.h"
@class GDTCORTransformer;
NS_ASSUME_NONNULL_BEGIN
@interface GDTCORTransport ()
/** The mapping identifier that the target backend will use to map the transport bytes to proto. */
@property(nonatomic) NSString *mappingID;
/** The transformers that will operate on events sent by this transport. */
@property(nonatomic) NSArray<id<GDTCOREventTransformer>> *transformers;
/** The target backend of this transport. */
@property(nonatomic) NSInteger target;
/** The transformer instance to used to transform events. Allows injecting a fake during testing. */
@property(nonatomic) GDTCORTransformer *transformerInstance;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,37 @@
/*
* Copyright 2020 Google LLC
*
* 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 <Foundation/Foundation.h>
@class GDTCOREvent;
NS_ASSUME_NONNULL_BEGIN
/// A data object representing a batch of events scheduled for upload.
@interface GDTCORUploadBatch : NSObject
/// An ID used to identify the batch in the storage.
@property(nonatomic, readonly) NSNumber *batchID;
/// The collection of the events in the batch.
@property(nonatomic, readonly) NSSet<GDTCOREvent *> *events;
/// The default initializer. See also docs for the corresponding properties.
- (instancetype)initWithBatchID:(NSNumber *)batchID events:(NSSet<GDTCOREvent *> *)events;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,68 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORLifecycle.h"
#import "GoogleDataTransport/GDTCORLibrary/Internal/GDTCORRegistrar.h"
@class GDTCORClock;
NS_ASSUME_NONNULL_BEGIN
/** This class connects storage and uploader implementations, providing events to an uploader
* and informing the storage what events were successfully uploaded or not.
*/
@interface GDTCORUploadCoordinator : NSObject <GDTCORLifecycleProtocol>
/** The queue on which all upload coordination will occur. Also used by a dispatch timer. */
/** Creates and/or returrns the singleton.
*
* @return The singleton instance of this class.
*/
+ (instancetype)sharedInstance;
/** The queue on which all upload coordination will occur. */
@property(nonatomic, readonly) dispatch_queue_t coordinationQueue;
/** A timer that will causes regular checks for events to upload. */
@property(nonatomic, readonly, nullable) dispatch_source_t timer;
/** The interval the timer will fire. */
@property(nonatomic, readonly) uint64_t timerInterval;
/** Some leeway given to libdispatch for the timer interval event. */
@property(nonatomic, readonly) uint64_t timerLeeway;
/** The registrar object the coordinator will use. Generally used for testing. */
@property(nonatomic) GDTCORRegistrar *registrar;
/** Forces the backend specified by the target to upload the provided set of events. This should
* only ever happen when the QoS tier of an event requires it.
*
* @param target The target that should force an upload.
*/
- (void)forceUploadForTarget:(GDTCORTarget)target;
/** Starts the upload timer. */
- (void)startTimer;
/** Stops the upload timer from running. */
- (void)stopTimer;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,66 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** This class manages the device clock and produces snapshots of the current time. */
@interface GDTCORClock : NSObject <NSSecureCoding>
/** The wallclock time, UTC, in milliseconds. */
@property(nonatomic, readonly) int64_t timeMillis;
/** The offset from UTC in seconds. */
@property(nonatomic, readonly) int64_t timezoneOffsetSeconds;
/** The kernel boot time when this clock was created in nanoseconds. */
@property(nonatomic, readonly) int64_t kernelBootTimeNanoseconds;
/** The device uptime when this clock was created in nanoseconds. */
@property(nonatomic, readonly) int64_t uptimeNanoseconds;
@property(nonatomic, readonly) int64_t kernelBootTime DEPRECATED_MSG_ATTRIBUTE(
"Please use `kernelBootTimeNanoseconds` instead");
@property(nonatomic, readonly)
int64_t uptime DEPRECATED_MSG_ATTRIBUTE("Please use `uptimeNanoseconds` instead");
/** Creates a GDTCORClock object using the current time and offsets.
*
* @return A new GDTCORClock object representing the current time state.
*/
+ (instancetype)snapshot;
/** Creates a GDTCORClock object representing a time in the future, relative to now.
*
* @param millisInTheFuture The millis in the future from now this clock should represent.
* @return An instance representing a future time.
*/
+ (instancetype)clockSnapshotInTheFuture:(uint64_t)millisInTheFuture;
/** Compares one clock with another, returns YES if the caller is after the parameter.
*
* @return YES if the calling clock's time is after the given clock's time.
*/
- (BOOL)isAfter:(GDTCORClock *)otherClock;
/** Returns value of `uptime` property in milliseconds. */
- (int64_t)uptimeMilliseconds;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,144 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
/** The current logging level. This value and higher will be printed. Declared as volatile to make
* getting and setting atomic.
*/
FOUNDATION_EXPORT volatile NSInteger GDTCORConsoleLoggerLoggingLevel;
/** A list of logging levels that GDT supports. */
typedef NS_ENUM(NSInteger, GDTCORLoggingLevel) {
/** Causes all logs to be printed. */
GDTCORLoggingLevelDebug = 1,
/** Causes all non-debug logs to be printed. */
GDTCORLoggingLevelVerbose = 2,
/** Causes warnings and errors to be printed. */
GDTCORLoggingLevelWarnings = 3,
/** Causes errors to be printed. This is the default value. */
GDTCORLoggingLevelErrors = 4
};
/** A list of message codes to print in the logger that help to correspond printed messages with
* code locations.
*
* Prefixes:
* - MCD => MessageCodeDebug
* - MCW => MessageCodeWarning
* - MCE => MessageCodeError
*/
typedef NS_ENUM(NSInteger, GDTCORMessageCode) {
/** For debug logs. */
GDTCORMCDDebugLog = 0,
/** For warning messages concerning transportBytes: not being implemented by a data object. */
GDTCORMCWDataObjectMissingBytesImpl = 1,
/** For warning messages concerning a failed event upload. */
GDTCORMCWUploadFailed = 2,
/** For warning messages concerning a forced event upload. */
GDTCORMCWForcedUpload = 3,
/** For warning messages concerning a failed reachability call. */
GDTCORMCWReachabilityFailed = 4,
/** For warning messages concerning a database warning. */
GDTCORMCWDatabaseWarning = 5,
/** For warning messages concerning the reading of a event file. */
GDTCORMCWFileReadError = 6,
/** For error messages concerning transformGDTEvent: not being implemented by an event
transformer. */
GDTCORMCETransformerDoesntImplementTransform = 1000,
/** For error messages concerning the creation of a directory failing. */
GDTCORMCEDirectoryCreationError = 1001,
/** For error messages concerning the writing of a event file. */
GDTCORMCEFileWriteError = 1002,
/** For error messages concerning the lack of a prioritizer for a given backend. */
GDTCORMCEPrioritizerError = 1003,
/** For error messages concerning a package delivery API violation. */
GDTCORMCEDeliverTwice = 1004,
/** For error messages concerning an error in an implementation of -transportBytes. */
GDTCORMCETransportBytesError = 1005,
/** For general purpose error messages in a dependency. */
GDTCORMCEGeneralError = 1006,
/** For fatal errors. Please go to https://github.com/firebase/firebase-ios-sdk/issues and open
* an issue if you encounter an error with this code.
*/
GDTCORMCEFatalAssertion = 1007,
/** For error messages concerning the reading of a event file. */
GDTCORMCEFileReadError = 1008,
/** For errors related to running sqlite. */
GDTCORMCEDatabaseError = 1009,
};
/** Prints the given code and format string to the console.
*
* @param code The message code describing the nature of the log.
* @param logLevel The log level of this log.
* @param format The format string.
*/
FOUNDATION_EXPORT
void GDTCORLog(GDTCORMessageCode code, GDTCORLoggingLevel logLevel, NSString *_Nonnull format, ...)
NS_FORMAT_FUNCTION(3, 4);
/** Prints an assert log to the console.
*
* @param wasFatal Send YES if the assertion should be fatal, NO otherwise.
* @param file The file in which the failure occurred.
* @param line The line number of the failure.
* @param format The format string.
*/
FOUNDATION_EXPORT void GDTCORLogAssert(BOOL wasFatal,
NSString *_Nonnull file,
NSInteger line,
NSString *_Nullable format,
...) NS_FORMAT_FUNCTION(4, 5);
/** Returns the string that represents some message code.
*
* @param code The code to convert to a string.
* @return The string representing the message code.
*/
FOUNDATION_EXPORT NSString *_Nonnull GDTCORMessageCodeEnumToString(GDTCORMessageCode code);
#define GDTCORLogDebug(MESSAGE_FORMAT, ...) \
GDTCORLog(GDTCORMCDDebugLog, GDTCORLoggingLevelDebug, MESSAGE_FORMAT, __VA_ARGS__);
// A define to wrap GULLogWarning with slightly more convenient usage.
#define GDTCORLogWarning(MESSAGE_CODE, MESSAGE_FORMAT, ...) \
GDTCORLog(MESSAGE_CODE, GDTCORLoggingLevelWarnings, MESSAGE_FORMAT, __VA_ARGS__);
// A define to wrap GULLogError with slightly more convenient usage and a failing assert.
#define GDTCORLogError(MESSAGE_CODE, MESSAGE_FORMAT, ...) \
GDTCORLog(MESSAGE_CODE, GDTCORLoggingLevelErrors, MESSAGE_FORMAT, __VA_ARGS__);

View File

@ -0,0 +1,36 @@
/*
* Copyright 2018 Google LLC
*
* 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 <Foundation/Foundation.h>
#import "GDTCORTargets.h"
NS_ASSUME_NONNULL_BEGIN
/* Class that manages the endpoints used by Google data transport library. */
@interface GDTCOREndpoints : NSObject
- (instancetype)init NS_UNAVAILABLE;
/** Returns the upload URL for a target specified. If the target is not available, returns nil.
*
* @param target GoogleDataTransport target for which the upload URL is being looked up for.
* @return URL that will be used for uploading the events for the provided target.
*/
+ (nullable NSURL *)uploadURLForTarget:(GDTCORTarget)target;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,102 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
#import "GDTCOREventDataObject.h"
#import "GDTCORTargets.h"
@class GDTCORClock;
@class GDTCORProductData;
NS_ASSUME_NONNULL_BEGIN
/** The different possible quality of service specifiers. High values indicate high priority. */
typedef NS_ENUM(NSInteger, GDTCOREventQoS) {
/** The QoS tier wasn't set, and won't ever be sent. */
GDTCOREventQoSUnknown = 0,
/** This event is internal telemetry data that should not be sent on its own if possible. */
GDTCOREventQoSTelemetry = 1,
/** This event should be sent, but in a batch only roughly once per day. */
GDTCOREventQoSDaily = 2,
/** This event should be sent when requested by the uploader. */
GDTCOREventQosDefault = 3,
/** This event should be sent immediately along with any other data that can be batched. */
GDTCOREventQoSFast = 4,
/** This event should only be uploaded on wifi. */
GDTCOREventQoSWifiOnly = 5,
};
@interface GDTCOREvent : NSObject <NSSecureCoding>
/** The unique ID of the event. */
@property(readonly, nonatomic) NSString *eventID;
/** The mapping identifier, to allow backends to map the transport bytes to a proto. */
@property(nullable, readonly, nonatomic) NSString *mappingID;
/** The identifier for the backend this event will eventually be sent to. */
@property(readonly, nonatomic) GDTCORTarget target;
/** The data object encapsulated in the transport of your choice, as long as it implements
* the GDTCOREventDataObject protocol. */
@property(nullable, nonatomic) id<GDTCOREventDataObject> dataObject;
/** The serialized bytes from calling [dataObject transportBytes]. */
@property(nullable, readonly, nonatomic) NSData *serializedDataObjectBytes;
/** The quality of service tier this event belongs to. */
@property(nonatomic) GDTCOREventQoS qosTier;
/** The clock snapshot at the time of the event. */
@property(nonatomic) GDTCORClock *clockSnapshot;
/** The expiration date of the event. Default is 604800 seconds (7 days) from creation. */
@property(nonatomic) NSDate *expirationDate;
/** Bytes that can be used by an uploader later on. */
@property(nullable, nonatomic) NSData *customBytes;
/** The product data that the event is associated with, if any. */
@property(nullable, readonly, nonatomic) GDTCORProductData *productData;
/** Initializes an instance using the given mapping ID and target.
*
* @param mappingID The mapping identifier.
* @param target The event's target identifier.
* @return An instance of this class.
*/
- (nullable instancetype)initWithMappingID:(NSString *)mappingID target:(GDTCORTarget)target;
/** Initializes an instance using the given mapping ID, product data, and target.
*
* @param mappingID The mapping identifier.
* @param productData The product data to associate this event with.
* @param target The event's target identifier.
* @return An instance of this class.
*/
- (nullable instancetype)initWithMappingID:(NSString *)mappingID
productData:(nullable GDTCORProductData *)productData
target:(GDTCORTarget)target;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,36 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** This protocol defines the common interface that event protos should implement regardless of the
* underlying transport technology (protobuf, nanopb, etc).
*/
@protocol GDTCOREventDataObject <NSObject>
@required
/** Returns the serialized proto bytes of the implementing event proto.
*
* @return the serialized proto bytes of the implementing event proto.
*/
- (NSData *)transportBytes;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,38 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
@class GDTCOREvent;
NS_ASSUME_NONNULL_BEGIN
/** Defines the API that event transformers must adopt. */
@protocol GDTCOREventTransformer <NSObject>
@required
/** Transforms an event by applying some logic to it. Events returned can be nil, for example, in
* instances where the event should be sampled.
*
* @param event The event to transform.
* @return A transformed event, or nil if the transformation dropped the event.
*/
- (nullable GDTCOREvent *)transformGDTEvent:(GDTCOREvent *)event;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,29 @@
// Copyright 2023 Google LLC
//
// 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 <Foundation/Foundation.h>
@interface GDTCORProductData : NSObject <NSCopying, NSSecureCoding>
/// The product ID.
@property(nonatomic, readonly) int32_t productID;
/// Initializes an instance using the given product ID.
/// - Parameter productID: A 32-bit integer.
- (instancetype)initWithProductID:(int32_t)productID;
/// This API is unavailable.
- (instancetype)init NS_UNAVAILABLE;
@end

View File

@ -0,0 +1,39 @@
/*
* 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 <Foundation/Foundation.h>
/** The list of targets supported by the shared transport infrastructure.
* These targets map to a specific backend designed to accept GDT payloads. If
* adding a new target, please use the previous value +1.
*/
typedef NS_ENUM(NSInteger, GDTCORTarget) {
/** Target used for testing purposes. */
kGDTCORTargetTest = 999,
/** Target used by internal clients. See go/firelog for more information. */
kGDTCORTargetCCT = 1000,
/** Target mapping to the Firelog backend. See go/firelog for more information. */
kGDTCORTargetFLL = 1001,
/** Special-purpose Crashlytics target. Please do not use it without permission. */
kGDTCORTargetCSH = 1002,
/** Target used for integration testing. */
kGDTCORTargetINT = 1003,
};

View File

@ -0,0 +1,101 @@
/*
* Copyright 2018 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 <Foundation/Foundation.h>
#import "GDTCOREventTransformer.h"
#import "GDTCORTargets.h"
@class GDTCOREvent;
@class GDTCORProductData;
NS_ASSUME_NONNULL_BEGIN
@interface GDTCORTransport : NSObject
// Please use the designated initializer.
- (instancetype)init NS_UNAVAILABLE;
/** Initializes a new transport that will send events to the given target backend.
*
* @param mappingID The mapping identifier used by the backend to map the data object transport
* bytes to a proto.
* @param transformers A list of transformers to be applied to events that are sent.
* @param target The target backend of this transport.
* @return A transport that will send events.
*/
- (nullable instancetype)initWithMappingID:(NSString *)mappingID
transformers:
(nullable NSArray<id<GDTCOREventTransformer>> *)transformers
target:(GDTCORTarget)target NS_DESIGNATED_INITIALIZER;
/** Copies and sends an internal telemetry event. Events sent using this API are lower in priority,
* and sometimes won't be sent on their own.
*
* @note This will convert the event's data object to data and release the original event.
*
* @param event The event to send.
* @param completion A block that will be called when the event has been written or dropped.
*/
- (void)sendTelemetryEvent:(GDTCOREvent *)event
onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion;
/** Copies and sends an internal telemetry event. Events sent using this API are lower in priority,
* and sometimes won't be sent on their own.
*
* @note This will convert the event's data object to data and release the original event.
*
* @param event The event to send.
*/
- (void)sendTelemetryEvent:(GDTCOREvent *)event;
/** Copies and sends an SDK service data event. Events send using this API are higher in priority,
* and will cause a network request at some point in the relative near future.
*
* @note This will convert the event's data object to data and release the original event.
*
* @param event The event to send.
* @param completion A block that will be called when the event has been written or dropped.
*/
- (void)sendDataEvent:(GDTCOREvent *)event
onComplete:(void (^_Nullable)(BOOL wasWritten, NSError *_Nullable error))completion;
/** Copies and sends an SDK service data event. Events send using this API are higher in priority,
* and will cause a network request at some point in the relative near future.
*
* @note This will convert the event's data object to data and release the original event.
*
* @param event The event to send.
*/
- (void)sendDataEvent:(GDTCOREvent *)event;
/** Creates an event for use by this transport.
*
* @return An event that is suited for use by this transport.
*/
- (GDTCOREvent *)eventForTransport;
/**
* Creates an event with the given product data for use by this transport.
*
* @param productData The given product data to associate with the created event.
* @return An event that is suited for use by this transport.
*/
- (GDTCOREvent *)eventForTransportWithProductData:(nonnull GDTCORProductData *)productData;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,25 @@
/*
* Copyright 2018 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 "GDTCORClock.h"
#import "GDTCORConsoleLogger.h"
#import "GDTCOREndpoints.h"
#import "GDTCOREvent.h"
#import "GDTCOREventDataObject.h"
#import "GDTCOREventTransformer.h"
#import "GDTCORProductData.h"
#import "GDTCORTargets.h"
#import "GDTCORTransport.h"

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyTracking</key>
<false/>
<key>NSPrivacyTrackingDomains</key>
<array>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array>
<dict>
<key>NSPrivacyCollectedDataType</key>
<string>NSPrivacyCollectedDataTypeOtherDiagnosticData</string>
<key>NSPrivacyCollectedDataTypeLinked</key>
<false/>
<key>NSPrivacyCollectedDataTypeTracking</key>
<false/>
<key>NSPrivacyCollectedDataTypePurposes</key>
<array>
<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>
</array>
</dict>
</array>
<key>NSPrivacyAccessedAPITypes</key>
<array>
</array>
</dict>
</plist>

202
Pods/GoogleDataTransport/LICENSE generated Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

236
Pods/GoogleDataTransport/README.md generated Normal file
View File

@ -0,0 +1,236 @@
[![Version](https://img.shields.io/cocoapods/v/GoogleDataTransport.svg?style=flat)](https://cocoapods.org/pods/GoogleDataTransport)
[![License](https://img.shields.io/cocoapods/l/GoogleDataTransport.svg?style=flat)](https://cocoapods.org/pods/GoogleDataTransport)
[![Platform](https://img.shields.io/cocoapods/p/GoogleDataTransport.svg?style=flat)](https://cocoapods.org/pods/GoogleDataTransport)
[![Actions Status][gh-datatransport-badge]][gh-actions]
# GoogleDataTransport
This library is for internal Google use only. It allows the logging of data and
telemetry from Google SDKs.
## Integration Testing
These instructions apply to minor and patch version updates. Major versions need
a customized adaptation.
After the CI is green:
* Determine the next version for release by checking the
[tagged releases](https://github.com/google/GoogleDataTransport/tags).
Ensure that the next release version keeps the Swift PM and CocoaPods versions in sync.
* Verify that the releasing version is the latest entry in the [CHANGELOG.md](CHANGELOG.md),
updating it if necessary.
* Update the version in the podspec to match the latest entry in the [CHANGELOG.md](CHANGELOG.md)
* Checkout the `main` branch and ensure it is up to date.
```console
git checkout main
git pull
```
* Add the CocoaPods tag (`{version}` will be the latest version in the [podspec](GoogleDataTransport.podspec#L3))
```console
git tag CocoaPods-{version}
git push origin CocoaPods-{version}
```
* Push the podspec to the designated repo
* If this version of GDT is intended to launch **before or with** the next Firebase release:
<details>
<summary>Push to <b>SpecsStaging</b></summary>
```console
pod repo push --skip-tests --use-json staging GoogleDataTransport.podspec
```
If the command fails with `Unable to find the 'staging' repo.`, add the staging repo with:
```console
pod repo add staging git@github.com:firebase/SpecsStaging.git
```
</details>
* Otherwise:
<details>
<summary>Push to <b>SpecsDev</b></summary>
```console
pod repo push --skip-tests --use-json dev GoogleDataTransport.podspec
```
If the command fails with `Unable to find the 'dev' repo.`, add the dev repo with:
```console
pod repo add dev git@github.com:firebase/SpecsDev.git
```
</details>
* Run Firebase CI by waiting until next nightly or adding a PR that touches `Gemfile`.
* On google3, create a workspace and new CL. Then copybara and run a global TAP.
<pre>
/google/data/ro/teams/copybara/copybara third_party/firebase/ios/Releases/GoogleDataTransport/copy.bara.sky \
--piper-description-behavior=OVERWRITE \
--destination-cl=<b>YOUR_CL</b> gdt
</pre>
## Publishing
The release process is as follows:
1. [Tag and release for Swift PM](#swift-package-manager)
2. [Publish to CocoaPods](#cocoapods)
3. [Create GitHub Release](#create-github-release)
4. [Perform post release cleanup](#post-release-cleanup)
### Swift Package Manager
By creating and [pushing a tag](https://github.com/google/GoogleDataTransport/tags)
for Swift PM, the newly tagged version will be immediately released for public use.
Given this, please verify the intended time of release for Swift PM.
* Add a version tag for Swift PM
```console
git tag {version}
git push origin {version}
```
*Note: Ensure that any inflight PRs that depend on the new `GoogleDataTransport` version are updated to point to the
newly tagged version rather than a checksum.*
### CocoaPods
* Publish the newly versioned pod to CocoaPods
It's recommended to point to the `GoogleDataTransport.podspec` in `staging` to make sure the correct spec is being published.
> [!WARNING]
> Manually update your local SpecsStaging clone or run `pod repo update`
> before proceeding.
```console
pod trunk push ~/.cocoapods/repos/staging/GoogleDataTransport/{version}/GoogleDataTransport.podspec.json --skip-tests
```
The pod push was successful if the above command logs: `🚀 GoogleDataTransport ({version}) successfully published`.
In addition, a new commit that publishes the new version (co-authored by [CocoaPodsAtGoogle](https://github.com/CocoaPodsAtGoogle))
should appear in the [CocoaPods specs repo](https://github.com/CocoaPods/Specs). Last, the latest version should be displayed
on [GoogleDataTransport's CocoaPods page](https://cocoapods.org/pods/GoogleDataTransport).
### [Create GitHub Release](https://github.com/google/GoogleDataTransport/releases/new/)
Update the [release template](https://github.com/google/GoogleDataTransport/releases/new/)'s **Tag version** and **Release title**
fields with the latest version. In addition, reference the [Release Notes](./CHANGELOG.md) in the release's description.
See [this release](https://github.com/google/GoogleDataTransport/releases/edit/9.0.1) for an example.
*Don't forget to perform the [post release cleanup](#post-release-cleanup)!*
### Post Release Cleanup
<details>
<summary>Clean up <b>SpecsStaging</b></summary>
```console
pwd=$(pwd)
mkdir -p /tmp/release-cleanup && cd $_
git clone git@github.com:firebase/SpecsStaging.git
cd SpecsStaging/
git rm -rf GoogleDataTransport/
git commit -m "Post publish cleanup"
git push origin master
rm -rf /tmp/release-cleanup
cd $pwd
```
</details>
## Set logging level
### Swift
- Import `GoogleDataTransport` module:
```swift
import GoogleDataTransport
```
- Set logging level global variable to the desired value before calling `FirebaseApp.configure()`:
```swift
GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevel.debug.rawValue
```
### Objective-C
- Import `GoogleDataTransport`:
```objective-c
#import <GoogleDataTransport/GoogleDataTransport.h>
```
- Set logging level global variable to the desired value before calling `-[FIRApp configure]`:
```objective-c
GDTCORConsoleLoggerLoggingLevel = GDTCORLoggingLevelDebug;
```
## Prereqs
- `gem install --user cocoapods cocoapods-generate`
- `brew install protobuf nanopb-generator`
- `easy_install --user protobuf`
## To develop
- Run `./GoogleDataTransport/generate_project.sh` after installing the prereqs
## When adding new logging endpoint
- Use commands similar to:
- `python -c "line='https://www.firebase.com'; print line[0::2]" `
- `python -c "line='https://www.firebase.com'; print line[1::2]" `
## When adding internal code that shouldn't be easily usable on github
- Consider using go/copybara-library/scrubbing#cc_scrub
## Development
Ensure that you have at least the following software:
* Xcode 12.0 (or later)
* CocoaPods 1.10.0 (or later)
* [CocoaPods generate](https://github.com/square/cocoapods-generate)
For the pod that you want to develop:
`pod gen GoogleDataTransport.podspec --local-sources=./ --auto-open --platforms=ios`
Note: If the CocoaPods cache is out of date, you may need to run
`pod repo update` before the `pod gen` command.
Note: Set the `--platforms` option to `macos` or `tvos` to develop/test for
those platforms. Since 10.2, Xcode does not properly handle multi-platform
CocoaPods workspaces.
### Development for Catalyst
* `pod gen GoogleDataTransport.podspec --local-sources=./ --auto-open --platforms=ios`
* Check the Mac box in the App-iOS Build Settings
* Sign the App in the Settings Signing & Capabilities tab
* Click Pods in the Project Manager
* Add Signing to the iOS host app and unit test targets
* Select the Unit-unit scheme
* Run it to build and test
Alternatively disable signing in each target:
* Go to Build Settings tab
* Click `+`
* Select `Add User-Defined Setting`
* Add `CODE_SIGNING_REQUIRED` setting with a value of `NO`
### Code Formatting
To ensure that the code is formatted consistently, run the script
[./scripts/check.sh](https://github.com/firebase/firebase-ios-sdk/blob/main/scripts/check.sh)
before creating a PR.
GitHub Actions will verify that any code changes are done in a style compliant
way. Install `clang-format` and `mint`:
```console
brew install clang-format@18
brew install mint
```
### Running Unit Tests
Select a scheme and press Command-u to build a component and run its unit tests.
## Contributing
See [Contributing](CONTRIBUTING.md) for more information on contributing to the Firebase
iOS SDK.
## License
The contents of this repository is licensed under the
[Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
[gh-actions]: https://github.com/firebase/firebase-ios-sdk/actions
[gh-datatransport-badge]: https://github.com/firebase/firebase-ios-sdk/workflows/datatransport/badge.svg