blob: 1e33e67d50e854a62d193f1639608eab4d045e18 [file] [log] [blame]
/*
*
* Copyright (c) 2013-2017 Nest Labs, Inc.
* All rights reserved.
*
* 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.
*/
/**
* @file
* Base-64 utility functions.
*
*/
#import "Base64Encoding.h"
#import "NLLogging.h"
enum {
kUnknownChar = -1,
kPaddingChar = -2,
kIgnoreChar = -3
};
@implementation Base64Encoding
{
NSData *_charMapData;
char *_charMap;
int _reverseCharMap[128];
int _shift;
int _mask;
char _paddingChar;
int _padLen;
}
+ (id)createBase64StringEncoding {
Base64Encoding *result = [self stringEncodingWithString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"];
[result setPaddingChar:'='];
[result setUsePaddingDuringEncoding:YES];
return result;
}
NS_INLINE int lcm(int a, int b) {
for (int aa = a, bb = b; ; ) {
if (aa == bb)
return aa;
else if (aa < bb)
aa += a;
else
bb += b;
}
}
+ (id)stringEncodingWithString:(NSString *)string {
return [[self alloc] initWithString:string];
}
- (id)initWithString:(NSString *)string {
if ((self = [super init])) {
_charMapData = [string dataUsingEncoding:NSASCIIStringEncoding];
if (!_charMapData) {
WDM_LOG_DEBUG(@"Unable to convert string to ASCII");
return nil;
}
_charMap = (char *)[_charMapData bytes];
NSUInteger length = [_charMapData length];
if (length < 2 || length > 128 || length & (length - 1)) {
WDM_LOG_DEBUG(@"Length not a power of 2 between 2 and 128");
return nil;
}
memset(_reverseCharMap, kUnknownChar, sizeof(_reverseCharMap));
for (unsigned int i = 0; i < length; i++) {
if (_reverseCharMap[(int)_charMap[i]] != kUnknownChar) {
WDM_LOG_DEBUG(@"Duplicate character at pos %d", i);
return nil;
}
_reverseCharMap[(int)_charMap[i]] = i;
}
for (NSUInteger i = 1; i < length; i <<= 1)
_shift++;
_mask = (1 << _shift) - 1;
_padLen = lcm(8, _shift) / _shift;
}
return self;
}
- (void)dealloc {
_charMapData = nil;
}
- (NSString *)description {
// TODO(iwade) track synonyms
return [NSString stringWithFormat:@"<Base%d StringEncoder: %@>",
1 << _shift, _charMapData];
}
- (NSString *)encode:(NSData *)inData {
NSUInteger inLen = [inData length];
if (inLen <= 0) {
WDM_LOG_DEBUG(@"Empty input");
return @"";
}
unsigned char *inBuf = (unsigned char *)[inData bytes];
NSUInteger inPos = 0;
NSUInteger outLen = (inLen * 8 + _shift - 1) / _shift;
if (_usePaddingDuringEncoding) {
outLen = ((outLen + _padLen - 1) / _padLen) * _padLen;
}
NSMutableData *outData = [NSMutableData dataWithLength:outLen];
unsigned char *outBuf = (unsigned char *)[outData mutableBytes];
NSUInteger outPos = 0;
int buffer = inBuf[inPos++];
int bitsLeft = 8;
while (bitsLeft > 0 || inPos < inLen) {
if (bitsLeft < _shift) {
if (inPos < inLen) {
buffer <<= 8;
buffer |= (inBuf[inPos++] & 0xff);
bitsLeft += 8;
} else {
int pad = _shift - bitsLeft;
buffer <<= pad;
bitsLeft += pad;
}
}
int idx = (buffer >> (bitsLeft - _shift)) & _mask;
bitsLeft -= _shift;
outBuf[outPos++] = _charMap[idx];
}
if (_usePaddingDuringEncoding) {
while (outPos < outLen)
outBuf[outPos++] = _paddingChar;
}
assert(outPos == outLen); // Underflowed output buffer assertion
[outData setLength:outPos];
return [[NSString alloc] initWithData:outData encoding:NSASCIIStringEncoding];
}
- (NSData *)decode:(NSString *)inString {
char *inBuf = (char *)[inString cStringUsingEncoding:NSASCIIStringEncoding];
if (!inBuf) {
WDM_LOG_DEBUG(@"unable to convert buffer to ASCII");
return nil;
}
NSUInteger inLen = strlen(inBuf);
NSUInteger outLen = inLen * _shift / 8;
NSMutableData *outData = [NSMutableData dataWithLength:outLen];
unsigned char *outBuf = (unsigned char *)[outData mutableBytes];
NSUInteger outPos = 0;
int buffer = 0;
int bitsLeft = 0;
BOOL expectPad = NO;
for (NSUInteger i = 0; i < inLen; i++) {
int val = _reverseCharMap[(int)inBuf[i]];
switch (val) {
case kIgnoreChar:
break;
case kPaddingChar:
expectPad = YES;
break;
case kUnknownChar:
WDM_LOG_DEBUG(@"Unexpected data in input pos %lu", (unsigned long)i);
return nil;
default:
if (expectPad) {
WDM_LOG_DEBUG(@"Expected further padding characters");
return nil;
}
buffer <<= _shift;
buffer |= val & _mask;
bitsLeft += _shift;
if (bitsLeft >= 8) {
outBuf[outPos++] = (unsigned char)(buffer >> (bitsLeft - 8));
bitsLeft -= 8;
}
break;
}
}
if (bitsLeft && buffer & ((1 << bitsLeft) - 1)) {
WDM_LOG_DEBUG(@"Incomplete trailing data");
return nil;
}
// Shorten buffer if needed due to padding chars
assert(outPos <= outLen); // Overflowed buffer
[outData setLength:outPos];
return outData;
}
- (void)setPaddingChar:(char)c {
_paddingChar = c;
_reverseCharMap[(int)c] = kPaddingChar;
}
@end