TwitterNetworkLayerTests/TNLXContentEncoding.m (365 lines of code) (raw):
//
// TNLXContentEncoding.m
// TwitterNetworkLayer
//
// Created on 11/21/16.
// Copyright © 2020 Twitter. All rights reserved.
//
#include <zlib.h>
#import <TwitterNetworkLayer/TNLContentCoding.h>
#import "TNLXContentEncoding.h"
typedef NS_ENUM(UInt32, TNLXZLibContentEncoderMode)
{
TNLXZLibContentEncoderDEFLATE = 'defl',
TNLXZLibContentEncoderGZIP = 'gzip',
};
static const NSUInteger kMinBodySizeForCompression = 512;
#define kGZIP_WINDOW_BITS_OFFSET (16)
#define kWINDOW_BITS (15)
#define kMEM_LIMIT (8)
#define kGZIP_WINDOW_BITS (kGZIP_WINDOW_BITS_OFFSET+kWINDOW_BITS)
#define kDEFLATE_WINDOW_BITS (-MAX_WBITS)
static const size_t kZipBufferSize = ((4 * 1024) /*page size*/ * 4);
typedef void(^TIPXDataEnumerateBlock)(const void *bytes, NSRange byteRange, BOOL *stop);
@interface TNLXZLibContentEncoder : NSObject <TNLContentEncoder>
@property (nonatomic, readonly) TNLXZLibContentEncoderMode mode;
- (instancetype)initWithMode:(TNLXZLibContentEncoderMode)mode;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end
@interface TNLXZLibContentDecoderContext : NSObject <TNLContentDecoderContext>
@property (nonatomic, readonly) TNLXZLibContentEncoderMode mode;
@property (nonatomic, readonly, nonnull, unsafe_unretained) id<TNLContentDecoderClient> tnl_decoderClient;
- (instancetype)initWithMode:(TNLXZLibContentEncoderMode)mode client:(id<TNLContentDecoderClient>)client;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (BOOL)decodeData:(NSData *)data error:(out NSError **)error;
- (BOOL)finalizeAndReturnError:(out NSError **)error;
@end
@interface TNLXZLibContentDecoder : NSObject <TNLContentDecoder>
@property (nonatomic, readonly) TNLXZLibContentEncoderMode mode;
- (instancetype)initWithMode:(TNLXZLibContentEncoderMode)mode;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end
@interface TNLXBase64ContentDecoderContext : NSObject <TNLContentDecoderContext>
@property (nonatomic, readonly, nonnull, unsafe_unretained) id<TNLContentDecoderClient> tnl_decoderClient;
- (instancetype)initWithClient:(id<TNLContentDecoderClient>)client;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (BOOL)decodeData:(NSData *)data error:(out NSError **)error;
- (BOOL)finalizeAndReturnError:(out NSError **)error;
@end
@interface TNLXBase64ContentEncoder : NSObject <TNLContentEncoder>
@end
@interface TNLXBase64ContentDecoder : NSObject <TNLContentDecoder>
@end
@implementation TNLXContentEncoding
+ (id<TNLContentEncoder>)GZIPContentEncoder
{
return [[TNLXZLibContentEncoder alloc] initWithMode:TNLXZLibContentEncoderGZIP];
}
+ (id<TNLContentDecoder>)GZIPContentDecoder
{
return [[TNLXZLibContentDecoder alloc] initWithMode:TNLXZLibContentEncoderGZIP];
}
+ (id<TNLContentEncoder>)DEFLATEContentEncoder
{
return [[TNLXZLibContentEncoder alloc] initWithMode:TNLXZLibContentEncoderDEFLATE];
}
+ (id<TNLContentDecoder>)DEFLATEContentDecoder
{
return [[TNLXZLibContentDecoder alloc] initWithMode:TNLXZLibContentEncoderDEFLATE];
}
+ (id<TNLContentEncoder>)Base64ContentEncoder
{
return [[TNLXBase64ContentEncoder alloc] init];
}
+ (id<TNLContentDecoder>)Base64ContentDecoder
{
return [[TNLXBase64ContentDecoder alloc] init];
}
@end
@implementation TNLXZLibContentEncoder
- (instancetype)initWithMode:(TNLXZLibContentEncoderMode)mode
{
if (self = [super init]) {
_mode = mode;
}
return self;
}
- (NSString *)tnl_contentEncodingType
{
return (TNLXZLibContentEncoderGZIP == _mode) ? @"gzip" : @"deflate";
}
- (NSData *)tnl_encodeHTTPBody:(NSData *)bodyData
error:(out NSError **)error
{
if (bodyData.length < kMinBodySizeForCompression) {
if (error) {
*error = [NSError errorWithDomain:TNLContentEncodingErrorDomain
code:TNLContentEncodingErrorCodeSkipEncoding
userInfo:nil];
}
return nil;
}
NSMutableData *mData = [NSMutableData data];
__block int zRetVal;
z_stream zStream;
__block z_streamp zStreamPtr = &zStream;
memset(zStreamPtr, 0, sizeof(z_stream));
zRetVal = deflateInit2(zStreamPtr,
Z_DEFAULT_COMPRESSION,
Z_DEFLATED,
(TNLXZLibContentEncoderGZIP == _mode) ? kGZIP_WINDOW_BITS : kDEFLATE_WINDOW_BITS,
kMEM_LIMIT,
Z_DEFAULT_STRATEGY);
if (zRetVal == Z_OK) {
unsigned char *outBuffer = (unsigned char *)malloc(kZipBufferSize);
TIPXDataEnumerateBlock enumBlock = ^(const void *bytes, NSRange byteRange, BOOL *stop) {
zStreamPtr->avail_in = (uInt)byteRange.length;
zStreamPtr->next_in = (z_const Byte *)bytes;
const int flush = byteRange.length > 0 ? Z_NO_FLUSH : Z_FINISH;
do {
zStreamPtr->avail_out = kZipBufferSize;
zStreamPtr->next_out = outBuffer;
zRetVal = deflate(zStreamPtr, flush);
if (Z_OK != zRetVal && Z_STREAM_END != zRetVal) {
// failure
break;
}
const uInt bytesMoved = kZipBufferSize - zStreamPtr->avail_out;
if (bytesMoved) {
[mData appendBytes:outBuffer length:bytesMoved];
}
if (Z_STREAM_END == zRetVal) {
// done
break;
}
if (Z_FINISH != flush && zStreamPtr->avail_in == 0) {
// no more data to consume
break;
}
} while (true);
if (Z_OK != zRetVal) {
*stop = YES;
}
};
[bodyData enumerateByteRangesUsingBlock:enumBlock];
if (zRetVal == Z_OK) {
BOOL fakeStop;
enumBlock(NULL, NSMakeRange(bodyData.length, 0), &fakeStop);
if (zRetVal == Z_STREAM_END) {
zRetVal = Z_OK;
} else if (zRetVal == Z_OK) {
zRetVal = Z_STREAM_ERROR;
}
}
free(outBuffer);
(void)deflateEnd(zStreamPtr);
}
if (Z_OK != zRetVal) {
mData = nil;
if (error) {
*error = [NSError errorWithDomain:@"zlib.error" code:zRetVal userInfo:nil];
}
}
return mData;
}
@end
@implementation TNLXZLibContentDecoder
- (instancetype)initWithMode:(TNLXZLibContentEncoderMode)mode
{
if (self = [super init]) {
_mode = mode;
}
return self;
}
- (NSString *)tnl_contentEncodingType
{
return (TNLXZLibContentEncoderGZIP == _mode) ? @"gzip" : @"deflate";
}
- (id<TNLContentDecoderContext>)tnl_initializeDecodingWithContentEncoding:(NSString *)contentEncodingValue
client:(id<TNLContentDecoderClient>)client
error:(out NSError **)error
{
return [[TNLXZLibContentDecoderContext alloc] initWithMode:_mode client:client];
}
- (BOOL)tnl_decode:(TNLXZLibContentDecoderContext *)context
additionalData:(NSData *)data
error:(out NSError **)error
{
return [context decodeData:data error:error];
}
- (BOOL)tnl_finalizeDecoding:(TNLXZLibContentDecoderContext *)context error:(out NSError **)error
{
return [context finalizeAndReturnError:error];
}
@end
@implementation TNLXZLibContentDecoderContext
{
z_stream _zStream;
unsigned char _outBuffer[kZipBufferSize];
unsigned char *_outRef;
struct {
int zStatus;
BOOL didInit:1;
} _flags;
}
- (instancetype)initWithMode:(TNLXZLibContentEncoderMode)mode
client:(id<TNLContentDecoderClient>)client
{
if (self = [super init]) {
_mode = mode;
_tnl_decoderClient = client;
_flags.zStatus = inflateInit2(&_zStream,
(TNLXZLibContentEncoderGZIP == _mode) ? kGZIP_WINDOW_BITS : kDEFLATE_WINDOW_BITS);
if (Z_OK == _flags.zStatus) {
_outRef = _outBuffer;
_flags.didInit = 1;
}
}
return self;
}
- (void)dealloc
{
if (_flags.didInit) {
inflateEnd(&_zStream);
}
}
- (BOOL)decodeData:(NSData *)data error:(out NSError * __autoreleasing *)error
{
if (Z_OK == _flags.zStatus || Z_STREAM_END == _flags.zStatus) {
[data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
*stop = ![self decodeBytes:bytes
length:byteRange.length
error:error];
if (self->_flags.zStatus != Z_OK && self->_flags.zStatus != Z_STREAM_END) {
*stop = YES;
}
}];
}
if (error && *error) {
return NO;
}
if (Z_OK != _flags.zStatus && Z_STREAM_END != _flags.zStatus) {
if (error) {
*error = [NSError errorWithDomain:@"zlib.error"
code:_flags.zStatus
userInfo:nil];
}
return NO;
}
return YES;
}
- (BOOL)decodeBytes:(const Byte *)bytes
length:(NSUInteger)length
error:(out NSError **)error
{
_zStream.avail_in = (uInt)length;
_zStream.next_in = (z_const Byte *)bytes;
BOOL inflateSuccess = NO;
do {
_zStream.avail_out = kZipBufferSize;
_zStream.next_out = _outRef;
_flags.zStatus = inflate(&_zStream, Z_NO_FLUSH);
inflateSuccess = (Z_OK == _flags.zStatus || Z_STREAM_END == _flags.zStatus);
uInt bytesMoved = kZipBufferSize - _zStream.avail_out;
if (inflateSuccess && bytesMoved) {
id<TNLContentDecoderClient> delegate = self.tnl_decoderClient;
NSData *data = [NSData dataWithBytes:_outRef length:bytesMoved];
if (![delegate tnl_dataWasDecoded:data error:error]) {
return NO;
}
}
} while (_zStream.avail_out == 0 && inflateSuccess);
assert(_zStream.avail_in == 0);
return YES;
}
- (BOOL)finalizeAndReturnError:(out NSError **)error
{
if (_flags.zStatus == Z_OK) {
if (![self decodeBytes:NULL length:0 error:error]) {
return NO;
}
}
if (_flags.zStatus == Z_STREAM_END) {
return YES;
}
if (error) {
*error = [NSError errorWithDomain:@"zlib.error"
code:((_flags.zStatus == Z_OK) ? Z_STREAM_ERROR : _flags.zStatus)
userInfo:nil];
}
return NO;
}
@end
@implementation TNLXBase64ContentEncoder
- (NSString *)tnl_contentEncodingType
{
return @"base64";
}
- (NSData *)tnl_encodeHTTPBody:(NSData *)bodyData error:(out NSError **)error
{
return [bodyData base64EncodedDataWithOptions:(NSDataBase64Encoding64CharacterLineLength |
NSDataBase64EncodingEndLineWithCarriageReturn |
NSDataBase64EncodingEndLineWithLineFeed)];
}
@end
@implementation TNLXBase64ContentDecoder
- (NSString *)tnl_contentEncodingType
{
return @"base64";
}
- (id<TNLContentDecoderContext>)tnl_initializeDecodingWithContentEncoding:(NSString *)contentEncodingValue
client:(id<TNLContentDecoderClient>)client
error:(out NSError **)error
{
return [[TNLXBase64ContentDecoderContext alloc] initWithClient:client];
}
- (BOOL)tnl_decode:(TNLXBase64ContentDecoderContext *)context
additionalData:(NSData *)data
error:(out NSError **)error
{
return [context decodeData:data error:error];
}
- (BOOL)tnl_finalizeDecoding:(TNLXBase64ContentDecoderContext *)context
error:(out NSError **)error
{
return [context finalizeAndReturnError:error];
}
@end
@implementation TNLXBase64ContentDecoderContext
{
NSMutableData *_carryOverData;
}
- (instancetype)initWithClient:(id<TNLContentDecoderClient>)client
{
if (self = [super init]) {
_tnl_decoderClient = client;
}
return self;
}
- (BOOL)decodeData:(NSData *)data error:(out NSError **)error
{
NSData *decodedData = nil;
if (_carryOverData) {
[_carryOverData appendData:data];
decodedData = [[NSData alloc] initWithBase64EncodedData:_carryOverData
options:NSDataBase64DecodingIgnoreUnknownCharacters];
if (decodedData) {
_carryOverData = nil;
}
} else {
decodedData = [[NSData alloc] initWithBase64EncodedData:data
options:NSDataBase64DecodingIgnoreUnknownCharacters];
if (!decodedData) {
_carryOverData = [data mutableCopy];
}
}
if (decodedData) {
return [_tnl_decoderClient tnl_dataWasDecoded:decodedData error:error];
}
return YES;
}
- (BOOL)finalizeAndReturnError:(out NSError **)error
{
if (_carryOverData) {
if (error) {
*error = [NSError errorWithDomain:NSPOSIXErrorDomain code:EINVAL userInfo:nil];
}
return NO;
}
return YES;
}
@end