Skip to content

Commit 54e2226

Browse files
authored
Add compression to CCT (#4718)
Also address a small bug in FLL whereby the compressed data was used no matter what (even if nil because of compression failure).
1 parent 68949a6 commit 54e2226

File tree

5 files changed

+170
-84
lines changed

5 files changed

+170
-84
lines changed

GoogleDataTransportCCTSupport/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# v1.3.1
2+
- Adds compression to requests to CCT.
3+
- Requests going to the FLL backend will only use compressed data when smaller.
24
- Added extensive debug logging that can be turned on by changing
35
GDT_VERBOSE_LOGGING to 1 in GDTCORConsoleLogger.h.
46

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright 2020 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import "GDTCCTLibrary/Private/GDTCCTCompressionHelper.h"
18+
19+
#import <zlib.h>
20+
21+
@implementation GDTCCTCompressionHelper
22+
23+
+ (nullable NSData *)gzippedData:(NSData *)data {
24+
#if defined(__LP64__) && __LP64__
25+
// Don't support > 32bit length for 64 bit, see note in header.
26+
if (data.length > UINT_MAX) {
27+
return nil;
28+
}
29+
#endif
30+
31+
const uint kChunkSize = 1024;
32+
33+
const void *bytes = [data bytes];
34+
NSUInteger length = [data length];
35+
36+
int level = Z_DEFAULT_COMPRESSION;
37+
if (!bytes || !length) {
38+
return nil;
39+
}
40+
41+
z_stream strm;
42+
bzero(&strm, sizeof(z_stream));
43+
44+
int memLevel = 8; // Default.
45+
int windowBits = 15 + 16; // Enable gzip header instead of zlib header.
46+
47+
int retCode;
48+
if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits, memLevel,
49+
Z_DEFAULT_STRATEGY)) != Z_OK) {
50+
return nil;
51+
}
52+
53+
// Hint the size at 1/4 the input size.
54+
NSMutableData *result = [NSMutableData dataWithCapacity:(length / 4)];
55+
unsigned char output[kChunkSize];
56+
57+
// Setup the input.
58+
strm.avail_in = (unsigned int)length;
59+
strm.next_in = (unsigned char *)bytes;
60+
61+
// Collect the data.
62+
do {
63+
// update what we're passing in
64+
strm.avail_out = kChunkSize;
65+
strm.next_out = output;
66+
retCode = deflate(&strm, Z_FINISH);
67+
if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) {
68+
deflateEnd(&strm);
69+
return nil;
70+
}
71+
// Collect what we got.
72+
unsigned gotBack = kChunkSize - strm.avail_out;
73+
if (gotBack > 0) {
74+
[result appendBytes:output length:gotBack];
75+
}
76+
77+
} while (retCode == Z_OK);
78+
79+
// If the loop exits, it used all input and the stream ended.
80+
NSAssert(strm.avail_in == 0,
81+
@"Should have finished deflating without using all input, %u bytes left", strm.avail_in);
82+
NSAssert(retCode == Z_STREAM_END,
83+
@"thought we finished deflate w/o getting a result of stream end, code %d", retCode);
84+
85+
// Clean up.
86+
deflateEnd(&strm);
87+
88+
return result;
89+
}
90+
91+
+ (BOOL)isGzipped:(NSData *)data {
92+
const UInt8 *bytes = (const UInt8 *)data.bytes;
93+
return (data.length >= 2 && bytes[0] == 0x1f && bytes[1] == 0x8b);
94+
}
95+
96+
@end

GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTCCTUploader.m

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#import <nanopb/pb_decode.h>
2525
#import <nanopb/pb_encode.h>
2626

27+
#import "GDTCCTLibrary/Private/GDTCCTCompressionHelper.h"
2728
#import "GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h"
2829
#import "GDTCCTLibrary/Private/GDTCCTPrioritizer.h"
2930

@@ -115,12 +116,6 @@ - (void)uploadPackage:(GDTCORUploadPackage *)package {
115116
return;
116117
}
117118
NSURL *serverURL = self.serverURL ? self.serverURL : [self defaultServerURL];
118-
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:serverURL];
119-
NSString *userAgent = [NSString stringWithFormat:@"datatransport/%@ fllsupport/%@ apple/",
120-
kGDTCORVersion, kGDTCCTSupportSDKVersion];
121-
[request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
122-
request.HTTPMethod = @"POST";
123-
GDTCORLogDebug("CCT: request created: %@", request);
124119

125120
id completionHandler = ^(NSData *_Nullable data, NSURLResponse *_Nullable response,
126121
NSError *_Nullable error) {
@@ -165,8 +160,13 @@ - (void)uploadPackage:(GDTCORUploadPackage *)package {
165160
self->_currentUploadPackage = package;
166161
NSData *requestProtoData =
167162
[self constructRequestProtoFromPackage:(GDTCORUploadPackage *)package];
163+
NSData *gzippedData = [GDTCCTCompressionHelper gzippedData:requestProtoData];
164+
BOOL usingGzipData = gzippedData != nil && gzippedData.length < requestProtoData.length;
165+
NSData *dataToSend = usingGzipData ? gzippedData : requestProtoData;
166+
NSURLRequest *request = [self constructRequestWithURL:serverURL data:dataToSend];
167+
GDTCORLogDebug("CCT: request created: %@", request);
168168
self.currentTask = [self.uploaderSession uploadTaskWithRequest:request
169-
fromData:requestProtoData
169+
fromData:dataToSend
170170
completionHandler:completionHandler];
171171
GDTCORLogDebug("%@", @"CCT: The upload task is about to begin.");
172172
[self.currentTask resume];
@@ -234,6 +234,28 @@ - (nonnull NSData *)constructRequestProtoFromPackage:(GDTCORUploadPackage *)pack
234234
return data ? data : [[NSData alloc] init];
235235
}
236236

237+
/** Constructs a request to CCT given a URL and request body data.
238+
*
239+
* @param URL The URL to send the request to.
240+
* @param data The request body data.
241+
* @return A new NSURLRequest ready to be sent to CCT.
242+
*/
243+
- (NSURLRequest *)constructRequestWithURL:(NSURL *)URL data:(NSData *)data {
244+
BOOL isGzipped = [GDTCCTCompressionHelper isGzipped:data];
245+
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
246+
[request setValue:@"application/x-protobuf" forHTTPHeaderField:@"Content-Type"];
247+
if (isGzipped) {
248+
[request setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
249+
}
250+
[request setValue:@"gzip" forHTTPHeaderField:@"Accept-Encoding"];
251+
NSString *userAgent = [NSString stringWithFormat:@"datatransport/%@ cctsupport/%@ apple/",
252+
kGDTCORVersion, kGDTCCTSupportSDKVersion];
253+
[request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
254+
request.HTTPMethod = @"POST";
255+
[request setHTTPBody:data];
256+
return request;
257+
}
258+
237259
#pragma mark - GDTCORUploadPackageProtocol
238260

239261
- (void)packageExpired:(GDTCORUploadPackage *)package {

GoogleDataTransportCCTSupport/GDTCCTLibrary/GDTFLLUploader.m

Lines changed: 3 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
#import "GDTCCTLibrary/Private/GDTFLLUploader.h"
1818

19-
#import <zlib.h>
20-
2119
#import <GoogleDataTransport/GDTCORConsoleLogger.h>
2220
#import <GoogleDataTransport/GDTCORPlatform.h>
2321
#import <GoogleDataTransport/GDTCORRegistrar.h>
@@ -26,6 +24,7 @@
2624
#import <nanopb/pb_decode.h>
2725
#import <nanopb/pb_encode.h>
2826

27+
#import "GDTCCTLibrary/Private/GDTCCTCompressionHelper.h"
2928
#import "GDTCCTLibrary/Private/GDTCCTNanopbHelpers.h"
3029
#import "GDTCCTLibrary/Private/GDTFLLPrioritizer.h"
3130

@@ -189,13 +188,13 @@ - (void)uploadPackage:(GDTCORUploadPackage *)package {
189188
self->_currentUploadPackage = package;
190189
NSData *requestProtoData =
191190
[self constructRequestProtoFromPackage:(GDTCORUploadPackage *)package];
192-
NSData *gzippedData = [GDTFLLUploader gzippedData:requestProtoData];
191+
NSData *gzippedData = [GDTCCTCompressionHelper gzippedData:requestProtoData];
193192
BOOL usingGzipData = gzippedData != nil && gzippedData.length < requestProtoData.length;
194193
NSData *dataToSend = usingGzipData ? gzippedData : requestProtoData;
195194
NSURLRequest *request = [self constructRequestWithURL:serverURL data:dataToSend];
196195
GDTCORLogDebug("FLL: request created: %@", request);
197196
self.currentTask = [self.uploaderSession uploadTaskWithRequest:request
198-
fromData:gzippedData
197+
fromData:dataToSend
199198
completionHandler:completionHandler];
200199
GDTCORLogDebug("%@", @"FLL: The upload task is about to begin.");
201200
[self.currentTask resume];
@@ -238,79 +237,6 @@ - (BOOL)readyToUploadWithConditions:(GDTCORUploadConditions)conditions {
238237

239238
#pragma mark - Private helper methods
240239

241-
/** Compresses the given data and returns a new data object.
242-
*
243-
* @note Reduced version from GULNSData+zlib.m of GoogleUtilities.
244-
* @return Compressed data, or nil if there was an error.
245-
*/
246-
+ (nullable NSData *)gzippedData:(NSData *)data {
247-
#if defined(__LP64__) && __LP64__
248-
// Don't support > 32bit length for 64 bit, see note in header.
249-
if (data.length > UINT_MAX) {
250-
return nil;
251-
}
252-
#endif
253-
254-
const uint kChunkSize = 1024;
255-
256-
const void *bytes = [data bytes];
257-
NSUInteger length = [data length];
258-
259-
int level = Z_DEFAULT_COMPRESSION;
260-
if (!bytes || !length) {
261-
return nil;
262-
}
263-
264-
z_stream strm;
265-
bzero(&strm, sizeof(z_stream));
266-
267-
int memLevel = 8; // Default.
268-
int windowBits = 15 + 16; // Enable gzip header instead of zlib header.
269-
270-
int retCode;
271-
if ((retCode = deflateInit2(&strm, level, Z_DEFLATED, windowBits, memLevel,
272-
Z_DEFAULT_STRATEGY)) != Z_OK) {
273-
return nil;
274-
}
275-
276-
// Hint the size at 1/4 the input size.
277-
NSMutableData *result = [NSMutableData dataWithCapacity:(length / 4)];
278-
unsigned char output[kChunkSize];
279-
280-
// Setup the input.
281-
strm.avail_in = (unsigned int)length;
282-
strm.next_in = (unsigned char *)bytes;
283-
284-
// Collect the data.
285-
do {
286-
// update what we're passing in
287-
strm.avail_out = kChunkSize;
288-
strm.next_out = output;
289-
retCode = deflate(&strm, Z_FINISH);
290-
if ((retCode != Z_OK) && (retCode != Z_STREAM_END)) {
291-
deflateEnd(&strm);
292-
return nil;
293-
}
294-
// Collect what we got.
295-
unsigned gotBack = kChunkSize - strm.avail_out;
296-
if (gotBack > 0) {
297-
[result appendBytes:output length:gotBack];
298-
}
299-
300-
} while (retCode == Z_OK);
301-
302-
// If the loop exits, it used all input and the stream ended.
303-
NSAssert(strm.avail_in == 0,
304-
@"Should have finished deflating without using all input, %u bytes left", strm.avail_in);
305-
NSAssert(retCode == Z_STREAM_END,
306-
@"thought we finished deflate w/o getting a result of stream end, code %d", retCode);
307-
308-
// Clean up.
309-
deflateEnd(&strm);
310-
311-
return result;
312-
}
313-
314240
/** Constructs data given an upload package.
315241
*
316242
* @param package The upload package used to construct the request proto bytes.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2020 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#import <Foundation/Foundation.h>
18+
19+
NS_ASSUME_NONNULL_BEGIN
20+
21+
/** A class with methods to help with gzipped data. */
22+
@interface GDTCCTCompressionHelper : NSObject
23+
24+
/** Compresses the given data and returns a new data object.
25+
*
26+
* @note Reduced version from GULNSData+zlib.m of GoogleUtilities.
27+
* @return Compressed data, or nil if there was an error.
28+
*/
29+
+ (nullable NSData *)gzippedData:(NSData *)data;
30+
31+
/** Returns YES if the data looks like it was gzip compressed by checking for the gzip magic number.
32+
*
33+
* @note: From https://en.wikipedia.org/wiki/Gzip, gzip's magic number is 1f 8b.
34+
* @return YES if the data appears gzipped, NO otherwise.
35+
*/
36+
+ (BOOL)isGzipped:(NSData *)data;
37+
38+
@end
39+
40+
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)