aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2010-01-15 23:01:01 +0000
committerGravatar gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2010-01-15 23:01:01 +0000
commit2bb0d1179cb0fe86385eaf087f9a924d1802910f (patch)
treed216356f43945142a277bcd06b7a290e8743e2b7
parentb2fa9805eb63d5daa1dd8fab1edf0c85fb7ebfc0 (diff)
[Author: iwade]
Added GTMStringEncoding which is a generic base 2-128 encoder/decoder with support for custom character maps. R=thomasvl APPROVED=thomasvl DELTA=723 (723 added, 0 deleted, 0 changed)
-rw-r--r--Foundation/GTMBase64.h6
-rw-r--r--Foundation/GTMNSData+Hex.h6
-rw-r--r--Foundation/GTMStringEncoding.h88
-rw-r--r--Foundation/GTMStringEncoding.m289
-rw-r--r--Foundation/GTMStringEncodingTest.m309
-rw-r--r--GTM.xcodeproj/project.pbxproj12
-rw-r--r--GTMiPhone.xcodeproj/project.pbxproj10
-rw-r--r--ReleaseNotes.txt3
8 files changed, 723 insertions, 0 deletions
diff --git a/Foundation/GTMBase64.h b/Foundation/GTMBase64.h
index 169b1c3..8a3b919 100644
--- a/Foundation/GTMBase64.h
+++ b/Foundation/GTMBase64.h
@@ -16,6 +16,12 @@
// the License.
//
+
+// WARNING: This class provides a subset of the functionality available in
+// GTMStringEncoding and may go away in the future.
+// Please consider using GTMStringEncoding instead.
+
+
#import <Foundation/Foundation.h>
#import "GTMDefines.h"
diff --git a/Foundation/GTMNSData+Hex.h b/Foundation/GTMNSData+Hex.h
index b1e668d..ed21f17 100644
--- a/Foundation/GTMNSData+Hex.h
+++ b/Foundation/GTMNSData+Hex.h
@@ -16,6 +16,12 @@
// the License.
//
+
+// WARNING: This class provides a subset of the functionality available in
+// GTMStringEncoding and may go away in the future.
+// Please consider using GTMStringEncoding instead.
+
+
#import <Foundation/Foundation.h>
/// Helpers for dealing w/ hex encoded strings.
diff --git a/Foundation/GTMStringEncoding.h b/Foundation/GTMStringEncoding.h
new file mode 100644
index 0000000..b74f2d9
--- /dev/null
+++ b/Foundation/GTMStringEncoding.h
@@ -0,0 +1,88 @@
+//
+// GTMStringEncoding.h
+//
+// Copyright 2010 Google Inc.
+//
+// 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 "GTMDefines.h"
+
+// A generic class for arbitrary base-2 to 128 string encoding and decoding.
+@interface GTMStringEncoding : NSObject {
+ @private
+ NSData *charMapData_;
+ char *charMap_;
+ int reverseCharMap_[128];
+ int shift_;
+ int mask_;
+ BOOL doPad_;
+ char paddingChar_;
+ int padLen_;
+}
+
+// Create a new, autoreleased GTMStringEncoding object with a standard encoding.
++ (id)binaryStringEncoding;
++ (id)hexStringEncoding;
++ (id)rfc4648Base32StringEncoding;
++ (id)rfc4648Base32HexStringEncoding;
++ (id)crockfordBase32StringEncoding;
++ (id)rfc4648Base64StringEncoding;
++ (id)rfc4648Base64WebsafeStringEncoding;
+
+// Create a new, autoreleased GTMStringEncoding object with the given string,
+// as described below.
++ (id)stringEncodingWithString:(NSString *)string;
+
+// Initialize a new GTMStringEncoding object with the string.
+//
+// The length of the string must be a power of 2, at least 2 and at most 128.
+// Only 7-bit ASCII characters are permitted in the string.
+//
+// These characters are the canonical set emitted during encoding.
+// If the characters have alternatives (e.g. case, easily transposed) then use
+// addDecodeSynonyms: to configure them.
+- (id)initWithString:(NSString *)string;
+
+// Add decoding synonyms as specified in the synonyms argument.
+//
+// It should be a sequence of one previously reverse mapped character,
+// followed by one or more non-reverse mapped character synonyms.
+// Only 7-bit ASCII characters are permitted in the string.
+//
+// e.g. If a GTMStringEncoder object has already been initialised with a set
+// of characters excluding I, L and O (to avoid confusion with digits) and you
+// want to accept them as digits you can call addDecodeSynonyms:@"0oO1iIlL".
+- (void)addDecodeSynonyms:(NSString *)synonyms;
+
+// A sequence of characters to ignore if they occur during encoding.
+// Only 7-bit ASCII characters are permitted in the string.
+- (void)ignoreCharacters:(NSString *)chars;
+
+// Indicates whether padding is performed during encoding.
+- (BOOL)doPad;
+- (void)setDoPad:(BOOL)doPad;
+
+// Sets the padding character to use during encoding.
+- (void)setPaddingChar:(char)c;
+
+// Encode a raw binary buffer to a 7-bit ASCII string.
+- (NSString *)encode:(NSData *)data;
+- (NSString *)encodeString:(NSString *)string;
+
+// Decode a 7-bit ASCII string to a raw binary buffer.
+- (NSData *)decode:(NSString *)string;
+- (NSString *)stringByDecoding:(NSString *)string;
+
+@end
diff --git a/Foundation/GTMStringEncoding.m b/Foundation/GTMStringEncoding.m
new file mode 100644
index 0000000..ee92ecf
--- /dev/null
+++ b/Foundation/GTMStringEncoding.m
@@ -0,0 +1,289 @@
+//
+// GTMStringEncoding.m
+//
+// Copyright 2009 Google Inc.
+//
+// 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 "GTMStringEncoding.h"
+
+enum {
+ kUnknownChar = -1,
+ kPaddingChar = -2,
+ kIgnoreChar = -3
+};
+
+@implementation GTMStringEncoding
+
++ (id)binaryStringEncoding {
+ return [self stringEncodingWithString:@"01"];
+}
+
++ (id)hexStringEncoding {
+ GTMStringEncoding *ret = [self stringEncodingWithString:
+ @"0123456789ABCDEF"];
+ [ret addDecodeSynonyms:@"AaBbCcDdEeFf"];
+ return ret;
+}
+
++ (id)rfc4648Base32StringEncoding {
+ GTMStringEncoding *ret = [self stringEncodingWithString:
+ @"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"];
+ [ret setPaddingChar:'='];
+ [ret setDoPad:YES];
+ return ret;
+}
+
++ (id)rfc4648Base32HexStringEncoding {
+ GTMStringEncoding *ret = [self stringEncodingWithString:
+ @"0123456789ABCDEFGHIJKLMNOPQRSTUV"];
+ [ret setPaddingChar:'='];
+ [ret setDoPad:YES];
+ return ret;
+}
+
++ (id)crockfordBase32StringEncoding {
+ GTMStringEncoding *ret = [self stringEncodingWithString:
+ @"0123456789ABCDEFGHJKMNPQRSTVWXYZ"];
+ [ret addDecodeSynonyms:
+ @"0oO1iIlLAaBbCcDdEeFfGgHhJjKkMmNnPpQqRrSsTtVvWwXxYyZz"];
+ return ret;
+}
+
++ (id)rfc4648Base64StringEncoding {
+ GTMStringEncoding *ret = [self stringEncodingWithString:
+ @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"];
+ [ret setPaddingChar:'='];
+ [ret setDoPad:YES];
+ return ret;
+}
+
++ (id)rfc4648Base64WebsafeStringEncoding {
+ GTMStringEncoding *ret = [self stringEncodingWithString:
+ @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"];
+ [ret setPaddingChar:'='];
+ [ret setDoPad:YES];
+ return ret;
+}
+
+GTM_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] autorelease];
+}
+
+- (id)initWithString:(NSString *)string {
+ if ((self = [super init])) {
+ charMapData_ = [[string dataUsingEncoding:NSASCIIStringEncoding] retain];
+ if (!charMapData_) {
+ _GTMDevLog(@"Unable to convert string to ASCII");
+ [self release];
+ return nil;
+ }
+ charMap_ = (char *)[charMapData_ bytes];
+ NSUInteger length = [charMapData_ length];
+ if (length < 2 || length > 128 || length & (length - 1)) {
+ _GTMDevLog(@"Length not a power of 2 between 2 and 128");
+ [self release];
+ return nil;
+ }
+
+ memset(reverseCharMap_, kUnknownChar, sizeof(reverseCharMap_));
+ for (NSUInteger i = 0; i < length; i++) {
+ if (reverseCharMap_[(int)charMap_[i]] != kUnknownChar) {
+ _GTMDevLog(@"Duplicate character at pos %d", i);
+ [self release];
+ 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_ release];
+ [super dealloc];
+}
+
+- (NSString *)description {
+ // TODO(iwade) track synonyms
+ return [NSString stringWithFormat:@"<Base%d StringEncoder: %@>",
+ 1 << shift_, charMapData_];
+}
+
+- (void)addDecodeSynonyms:(NSString *)synonyms {
+ char *buf = (char *)[synonyms cStringUsingEncoding:NSASCIIStringEncoding];
+ int val = kUnknownChar;
+ while (*buf) {
+ int c = *buf++;
+ if (reverseCharMap_[c] == kUnknownChar) {
+ reverseCharMap_[c] = val;
+ } else {
+ val = reverseCharMap_[c];
+ }
+ }
+}
+
+- (void)ignoreCharacters:(NSString *)chars {
+ char *buf = (char *)[chars cStringUsingEncoding:NSASCIIStringEncoding];
+ while (*buf) {
+ int c = *buf++;
+ _GTMDevAssert(reverseCharMap_[c] == kUnknownChar,
+ @"Character already mapped");
+ reverseCharMap_[c] = kIgnoreChar;
+ }
+}
+
+- (BOOL)doPad {
+ return doPad_;
+}
+
+- (void)setDoPad:(BOOL)doPad {
+ doPad_ = doPad;
+}
+
+- (void)setPaddingChar:(char)c {
+ paddingChar_ = c;
+ reverseCharMap_[(int)c] = kPaddingChar;
+}
+
+- (NSString *)encode:(NSData *)inData {
+ NSUInteger inLen = [inData length];
+ if (inLen <= 0) {
+ _GTMDevLog(@"Empty input");
+ return @"";
+ }
+ unsigned char *inBuf = (unsigned char *)[inData bytes];
+ NSUInteger inPos = 0;
+
+ NSUInteger outLen = (inLen * 8 + shift_ - 1) / shift_;
+ if (doPad_) {
+ 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 (doPad_) {
+ while (outPos < outLen)
+ outBuf[outPos++] = paddingChar_;
+ }
+
+ _GTMDevAssert(outPos == outLen, @"Underflowed output buffer");
+ [outData setLength:outPos];
+
+ return [[[NSString alloc] initWithData:outData
+ encoding:NSASCIIStringEncoding] autorelease];
+}
+
+- (NSString *)encodeString:(NSString *)inString {
+ return [self encode:[inString dataUsingEncoding:NSUTF8StringEncoding]];
+}
+
+- (NSData *)decode:(NSString *)inString {
+ char *inBuf = (char *)[inString cStringUsingEncoding:NSASCIIStringEncoding];
+ if (!inBuf) {
+ _GTMDevLog(@"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:
+ _GTMDevLog(@"Unexpected data in input pos %d", i);
+ return nil;
+ default:
+ if (expectPad) {
+ _GTMDevLog(@"Expected further padding characters");
+ return nil;
+ }
+ buffer <<= shift_;
+ buffer |= val & mask_;
+ bitsLeft += shift_;
+ if (bitsLeft >= 8) {
+ outBuf[outPos++] = buffer >> (bitsLeft - 8);
+ bitsLeft -= 8;
+ }
+ break;
+ }
+ }
+
+ if (bitsLeft && buffer & ((1 << bitsLeft) - 1)) {
+ _GTMDevLog(@"Incomplete trailing data");
+ return nil;
+ }
+
+ // Shorten buffer if needed due to padding chars
+ _GTMDevAssert(outPos <= outLen, @"Overflowed buffer");
+ [outData setLength:outPos];
+
+ return outData;
+}
+
+- (NSString *)stringByDecoding:(NSString *)inString {
+ NSData *ret = [self decode:inString];
+ return [[[NSString alloc] initWithData:ret
+ encoding:NSUTF8StringEncoding] autorelease];
+}
+
+@end
diff --git a/Foundation/GTMStringEncodingTest.m b/Foundation/GTMStringEncodingTest.m
new file mode 100644
index 0000000..82b7492
--- /dev/null
+++ b/Foundation/GTMStringEncodingTest.m
@@ -0,0 +1,309 @@
+//
+// GTMStringEncodingTest.m
+//
+// Copyright 2010 Google Inc.
+//
+// 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 "GTMSenTestCase.h"
+#import "GTMStringEncoding.h"
+#import "GTMUnitTestDevLog.h"
+
+@interface GTMStringEncodingTest : GTMTestCase
+@end
+
+@implementation GTMStringEncodingTest
+
+// Empty inputs should result in empty outputs.
+- (void)testEmptyInputs {
+ GTMStringEncoding *coder = [GTMStringEncoding stringEncodingWithString:@"01"];
+
+ [GTMUnitTestDevLogDebug expectString:@"Empty input"];
+ STAssertEqualStrings([coder encode:[NSData data]], @"", nil);
+ [GTMUnitTestDevLogDebug expectString:@"Empty input"];
+ STAssertEqualObjects([coder encodeString:@""], @"", nil);
+ STAssertEqualObjects([coder decode:@""], [NSData data], nil);
+ STAssertEqualStrings([coder stringByDecoding:@""], @"", nil);
+}
+
+// Invalid inputs should result in nil outputs.
+- (void)testInvalidInputs {
+ GTMStringEncoding *coder = [GTMStringEncoding stringEncodingWithString:@"01"];
+
+ [GTMUnitTestDevLogDebug expectString:@"unable to convert buffer to ASCII"];
+ STAssertNil([coder decode:nil], nil);
+ [GTMUnitTestDevLogDebug expectString:@"Unexpected data in input pos 0"];
+ STAssertNil([coder decode:@"banana"], nil);
+}
+
+// Ignored inputs should be silently ignored.
+- (void)testIgnoreChars {
+ GTMStringEncoding *coder = [GTMStringEncoding stringEncodingWithString:@"01"];
+ [coder ignoreCharacters:@" \n-"];
+
+ char aa = 0xaa;
+ NSData *aaData = [NSData dataWithBytes:&aa length:sizeof(aa)];
+ STAssertEqualObjects([coder decode:@"10101010"], aaData, nil);
+
+ // Inputs with ignored characters
+ STAssertEqualObjects([coder decode:@"1010 1010"], aaData, nil);
+ STAssertEqualObjects([coder decode:@"1010-1010"], aaData, nil);
+ STAssertEqualObjects([coder decode:@"1010\n1010"], aaData, nil);
+
+ // Invalid inputs
+ [GTMUnitTestDevLogDebug expectString:@"Unexpected data in input pos 4"];
+ STAssertNil([coder decode:@"1010+1010"], nil);
+}
+
+#define ASSERT_ENCODE_DECODE_STRING(coder, decoded, encoded) do { \
+ STAssertEqualStrings([coder encodeString:decoded], encoded, nil); \
+ STAssertEqualStrings([coder stringByDecoding:encoded], decoded, nil); \
+} while (0)
+
+- (void)testBinary {
+ GTMStringEncoding *coder = [GTMStringEncoding binaryStringEncoding];
+
+ [GTMUnitTestDevLogDebug expectString:@"Empty input"];
+ ASSERT_ENCODE_DECODE_STRING(coder, @"", @"");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"f", @"01100110");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"fo", @"0110011001101111");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foo", @"011001100110111101101111");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foob", @"011001100110111101101111"
+ "01100010");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"fooba", @"011001100110111101101111"
+ "0110001001100001");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foobar", @"011001100110111101101111"
+ "011000100110000101110010");
+
+ // All values, generated with:
+ // perl -le 'print unpack "B*", join("", map chr, 0..255)'
+ NSString *allValues = @""
+ "0000000000000001000000100000001100000100000001010000011000000111000010000000"
+ "1001000010100000101100001100000011010000111000001111000100000001000100010010"
+ "0001001100010100000101010001011000010111000110000001100100011010000110110001"
+ "1100000111010001111000011111001000000010000100100010001000110010010000100101"
+ "0010011000100111001010000010100100101010001010110010110000101101001011100010"
+ "1111001100000011000100110010001100110011010000110101001101100011011100111000"
+ "0011100100111010001110110011110000111101001111100011111101000000010000010100"
+ "0010010000110100010001000101010001100100011101001000010010010100101001001011"
+ "0100110001001101010011100100111101010000010100010101001001010011010101000101"
+ "0101010101100101011101011000010110010101101001011011010111000101110101011110"
+ "0101111101100000011000010110001001100011011001000110010101100110011001110110"
+ "1000011010010110101001101011011011000110110101101110011011110111000001110001"
+ "0111001001110011011101000111010101110110011101110111100001111001011110100111"
+ "1011011111000111110101111110011111111000000010000001100000101000001110000100"
+ "1000010110000110100001111000100010001001100010101000101110001100100011011000"
+ "1110100011111001000010010001100100101001001110010100100101011001011010010111"
+ "1001100010011001100110101001101110011100100111011001111010011111101000001010"
+ "0001101000101010001110100100101001011010011010100111101010001010100110101010"
+ "1010101110101100101011011010111010101111101100001011000110110010101100111011"
+ "0100101101011011011010110111101110001011100110111010101110111011110010111101"
+ "1011111010111111110000001100000111000010110000111100010011000101110001101100"
+ "0111110010001100100111001010110010111100110011001101110011101100111111010000"
+ "1101000111010010110100111101010011010101110101101101011111011000110110011101"
+ "1010110110111101110011011101110111101101111111100000111000011110001011100011"
+ "1110010011100101111001101110011111101000111010011110101011101011111011001110"
+ "1101111011101110111111110000111100011111001011110011111101001111010111110110"
+ "111101111111100011111001111110101111101111111100111111011111111011111111";
+ char allValuesBytes[256];
+ for (NSUInteger i = 0; i < sizeof(allValuesBytes); i++)
+ allValuesBytes[i] = i;
+ NSData *allValuesData = [NSData dataWithBytes:&allValuesBytes
+ length:sizeof(allValuesBytes)];
+
+ STAssertEqualObjects([coder decode:allValues], allValuesData, nil);
+ STAssertEqualStrings([coder encode:allValuesData], allValues, nil);
+}
+
+- (void)testBase64 {
+ // RFC4648 test vectors
+ GTMStringEncoding *coder = [GTMStringEncoding rfc4648Base64StringEncoding];
+
+ [GTMUnitTestDevLogDebug expectString:@"Empty input"];
+ ASSERT_ENCODE_DECODE_STRING(coder, @"", @"");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"f", @"Zg==");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"fo", @"Zm8=");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foo", @"Zm9v");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foob", @"Zm9vYg==");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"fooba", @"Zm9vYmE=");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foobar", @"Zm9vYmFy");
+
+ // All values, generated with:
+ // python -c 'import base64; print base64.b64encode("".join([chr(x) for x in range(0, 256)]))'
+ NSString *allValues = @""
+ "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4"
+ "OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3Bx"
+ "cnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmq"
+ "q6ytrq+wsbKztLW2t7i5uru8vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj"
+ "5OXm5+jp6uvs7e7v8PHy8/T19vf4+fr7/P3+/w==";
+ char allValuesBytes[256];
+ for (NSUInteger i = 0; i < sizeof(allValuesBytes); i++)
+ allValuesBytes[i] = i;
+ NSData *allValuesData = [NSData dataWithBytes:&allValuesBytes
+ length:sizeof(allValuesBytes)];
+
+ STAssertEqualObjects([coder decode:allValues], allValuesData, nil);
+ STAssertEqualStrings([coder encode:allValuesData], allValues, nil);
+}
+
+- (void)testBase64Websafe {
+ // RFC4648 test vectors
+ GTMStringEncoding *coder =
+ [GTMStringEncoding rfc4648Base64WebsafeStringEncoding];
+
+ [GTMUnitTestDevLogDebug expectString:@"Empty input"];
+ ASSERT_ENCODE_DECODE_STRING(coder, @"", @"");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"f", @"Zg==");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"fo", @"Zm8=");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foo", @"Zm9v");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foob", @"Zm9vYg==");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"fooba", @"Zm9vYmE=");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foobar", @"Zm9vYmFy");
+
+ // All values, generated with:
+ // python -c 'import base64; print base64.urlsafe_b64encode("".join([chr(x) for x in range(0, 256)]))'
+ NSString *allValues = @""
+ "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4"
+ "OTo7PD0-P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3Bx"
+ "cnN0dXZ3eHl6e3x9fn-AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmq"
+ "q6ytrq-wsbKztLW2t7i5uru8vb6_wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t_g4eLj"
+ "5OXm5-jp6uvs7e7v8PHy8_T19vf4-fr7_P3-_w==";
+ char allValuesBytes[256];
+ for (NSUInteger i = 0; i < sizeof(allValuesBytes); i++)
+ allValuesBytes[i] = i;
+ NSData *allValuesData = [NSData dataWithBytes:&allValuesBytes
+ length:sizeof(allValuesBytes)];
+
+ STAssertEqualObjects([coder decode:allValues], allValuesData, nil);
+ STAssertEqualStrings([coder encode:allValuesData], allValues, nil);
+}
+
+- (void)testBase32 {
+ // RFC4648 test vectors
+ GTMStringEncoding *coder = [GTMStringEncoding rfc4648Base32StringEncoding];
+
+ [GTMUnitTestDevLogDebug expectString:@"Empty input"];
+ ASSERT_ENCODE_DECODE_STRING(coder, @"", @"");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"f", @"MY======");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"fo", @"MZXQ====");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foo", @"MZXW6===");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foob", @"MZXW6YQ=");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"fooba", @"MZXW6YTB");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foobar", @"MZXW6YTBOI======");
+
+ // All values, generated with:
+ // python -c 'import base64; print base64.b32encode("".join([chr(x) for x in range(0, 256)]))'
+ NSString *allValues = @""
+ "AAAQEAYEAUDAOCAJBIFQYDIOB4IBCEQTCQKRMFYYDENBWHA5DYPSAIJCEMSCKJRHFAUSUKZMFUXC"
+ "6MBRGIZTINJWG44DSOR3HQ6T4P2AIFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLJNVYXK6"
+ "L5QGCYTDMRSWMZ3INFVGW3DNNZXXA4LSON2HK5TXPB4XU634PV7H7AEBQKBYJBMGQ6EITCULRSGY"
+ "5D4QSGJJHFEVS2LZRGM2TOOJ3HU7UCQ2FI5EUWTKPKFJVKV2ZLNOV6YLDMVTWS23NN5YXG5LXPF5"
+ "X274BQOCYPCMLRWHZDE4VS6MZXHM7UGR2LJ5JVOW27MNTWW33TO55X7A4HROHZHF43T6R2PK5PWO"
+ "33XP6DY7F47U6X3PP6HZ7L57Z7P674======";
+ char allValuesBytes[256];
+ for (NSUInteger i = 0; i < sizeof(allValuesBytes); i++)
+ allValuesBytes[i] = i;
+ NSData *allValuesData = [NSData dataWithBytes:&allValuesBytes
+ length:sizeof(allValuesBytes)];
+
+ STAssertEqualObjects([coder decode:allValues], allValuesData, nil);
+ STAssertEqualStrings([coder encode:allValuesData], allValues, nil);
+}
+
+- (void)testBase32Hex {
+ // RFC4648 test vectors
+ GTMStringEncoding *coder = [GTMStringEncoding rfc4648Base32HexStringEncoding];
+
+ [GTMUnitTestDevLogDebug expectString:@"Empty input"];
+ ASSERT_ENCODE_DECODE_STRING(coder, @"", @"");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"f", @"CO======");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"fo", @"CPNG====");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foo", @"CPNMU===");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foob", @"CPNMUOG=");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"fooba", @"CPNMUOJ1");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foobar", @"CPNMUOJ1E8======");
+
+ // All values, generated with:
+ // python -c 'import base64; print base64.b32encode("".join([chr(x) for x in range(0, 256)]))' | tr A-Z2-7 0-9A-V
+ NSString *allValues = @""
+ "000G40O40K30E209185GO38E1S8124GJ2GAHC5OO34D1M70T3OFI08924CI2A9H750KIKAPC5KN2"
+ "UC1H68PJ8D9M6SS3IEHR7GUJSFQ085146H258P3KGIAA9D64QJIFA18L4KQKALB5EM2PB9DLONAU"
+ "BTG62OJ3CHIMCPR8D5L6MR3DDPNN0SBIEDQ7ATJNF1SNKURSFLV7V041GA1O91C6GU48J2KBHI6O"
+ "T3SGI699754LIQBPH6CQJEE9R7KVK2GQ58T4KMJAFA59LALQPBDELUOB3CLJMIQRDDTON6TBNF5T"
+ "NQVS1GE2OF2CBHM7P34SLIUCPN7CVK6HQB9T9LEMQVCDJMMRRJETTNV0S7HE7P75SRJUHQFATFME"
+ "RRNFU3OV5SVKUNRFFU7PVBTVPVFUVS======";
+ char allValuesBytes[256];
+ for (NSUInteger i = 0; i < sizeof(allValuesBytes); i++)
+ allValuesBytes[i] = i;
+ NSData *allValuesData = [NSData dataWithBytes:&allValuesBytes
+ length:sizeof(allValuesBytes)];
+
+ STAssertEqualObjects([coder decode:allValues], allValuesData, nil);
+ STAssertEqualStrings([coder encode:allValuesData], allValues, nil);
+}
+
+- (void)testHex {
+ // RFC4648 test vectors
+ GTMStringEncoding *coder = [GTMStringEncoding hexStringEncoding];
+
+ [GTMUnitTestDevLogDebug expectString:@"Empty input"];
+ ASSERT_ENCODE_DECODE_STRING(coder, @"", @"");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"f", @"66");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"fo", @"666F");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foo", @"666F6F");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foob", @"666F6F62");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"fooba", @"666F6F6261");
+ ASSERT_ENCODE_DECODE_STRING(coder, @"foobar", @"666F6F626172");
+
+ // All Values, generated with:
+ // python -c 'import binascii; print binascii.b2a_hex("".join([chr(x) for x in range(0, 256)])).upper()'
+ NSString *allValues = @""
+ "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425"
+ "262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B"
+ "4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F7071"
+ "72737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F9091929394959697"
+ "98999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBD"
+ "BEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3"
+ "E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF";
+ char allValuesBytes[256];
+ for (NSUInteger i = 0; i < sizeof(allValuesBytes); i++)
+ allValuesBytes[i] = i;
+ NSData *allValuesData = [NSData dataWithBytes:&allValuesBytes
+ length:sizeof(allValuesBytes)];
+
+ STAssertEqualObjects([coder decode:allValues], allValuesData, nil);
+ STAssertEqualStrings([coder encode:allValuesData], allValues, nil);
+
+ // Lower case
+ STAssertEqualObjects([coder decode:[allValues lowercaseString]],
+ allValuesData, nil);
+
+ // Extra tests from GTMNSData+HexTest.m
+ NSString *testString = @"1C2F0032F40123456789ABCDEF";
+ char testBytes[] = { 0x1c, 0x2f, 0x00, 0x32, 0xf4, 0x01, 0x23,
+ 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
+ NSData *testData = [NSData dataWithBytes:testBytes length:sizeof(testBytes)];
+ STAssertEqualStrings([coder encode:testData], testString, nil);
+ STAssertEqualObjects([coder decode:testString], testData, nil);
+
+ // Invalid inputs
+ [GTMUnitTestDevLogDebug expectString:@"Incomplete trailing data"];
+ STAssertNil([coder decode:@"1c2f003"], nil);
+ [GTMUnitTestDevLogDebug expectString:@"Unexpected data in input pos 7"];
+ STAssertNil([coder decode:@"1c2f00ft"], nil);
+ [GTMUnitTestDevLogDebug expectString:@"Unexpected data in input pos 4"];
+ STAssertNil([coder decode:@"abcd<C3><A9>f"], nil);
+}
+
+@end
diff --git a/GTM.xcodeproj/project.pbxproj b/GTM.xcodeproj/project.pbxproj
index 2ff56ac..69a7233 100644
--- a/GTM.xcodeproj/project.pbxproj
+++ b/GTM.xcodeproj/project.pbxproj
@@ -37,6 +37,9 @@
/* End PBXAppleScriptBuildPhase section */
/* Begin PBXBuildFile section */
+ 0B1B9B8710FECD870084EE4B /* GTMStringEncoding.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B1B9B8410FECD870084EE4B /* GTMStringEncoding.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 0B1B9B8810FECD870084EE4B /* GTMStringEncoding.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B1B9B8510FECD870084EE4B /* GTMStringEncoding.m */; };
+ 0B1B9B8A10FECDA00084EE4B /* GTMStringEncodingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B1B9B8610FECD870084EE4B /* GTMStringEncodingTest.m */; };
0BFAD4C5104D06EF002BEB27 /* GTMNSData+Hex.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BFAD4BF104D06EF002BEB27 /* GTMNSData+Hex.h */; settings = {ATTRIBUTES = (Public, ); }; };
0BFAD4C6104D06EF002BEB27 /* GTMNSData+Hex.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BFAD4C0104D06EF002BEB27 /* GTMNSData+Hex.m */; };
0BFAD4C8104D06EF002BEB27 /* GTMNSDictionary+CaseInsensitive.h in Headers */ = {isa = PBXBuildFile; fileRef = 0BFAD4C2104D06EF002BEB27 /* GTMNSDictionary+CaseInsensitive.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -447,6 +450,9 @@
/* Begin PBXFileReference section */
0867D69BFE84028FC02AAC07 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
0867D6A5FE840307C02AAC07 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
+ 0B1B9B8410FECD870084EE4B /* GTMStringEncoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTMStringEncoding.h; path = Foundation/GTMStringEncoding.h; sourceTree = SOURCE_ROOT; };
+ 0B1B9B8510FECD870084EE4B /* GTMStringEncoding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTMStringEncoding.m; path = Foundation/GTMStringEncoding.m; sourceTree = SOURCE_ROOT; };
+ 0B1B9B8610FECD870084EE4B /* GTMStringEncodingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTMStringEncodingTest.m; path = Foundation/GTMStringEncodingTest.m; sourceTree = SOURCE_ROOT; };
0BFAD4BF104D06EF002BEB27 /* GTMNSData+Hex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSData+Hex.h"; sourceTree = "<group>"; };
0BFAD4C0104D06EF002BEB27 /* GTMNSData+Hex.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSData+Hex.m"; sourceTree = "<group>"; };
0BFAD4C1104D06EF002BEB27 /* GTMNSData+HexTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSData+HexTest.m"; sourceTree = "<group>"; };
@@ -1161,6 +1167,9 @@
F48FE2720D198CCE009257D2 /* Foundation */ = {
isa = PBXGroup;
children = (
+ 0B1B9B8410FECD870084EE4B /* GTMStringEncoding.h */,
+ 0B1B9B8510FECD870084EE4B /* GTMStringEncoding.m */,
+ 0B1B9B8610FECD870084EE4B /* GTMStringEncodingTest.m */,
0BFAD4BF104D06EF002BEB27 /* GTMNSData+Hex.h */,
0BFAD4C0104D06EF002BEB27 /* GTMNSData+Hex.m */,
0BFAD4C1104D06EF002BEB27 /* GTMNSData+HexTest.m */,
@@ -1379,6 +1388,7 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
+ 0B1B9B8710FECD870084EE4B /* GTMStringEncoding.h in Headers */,
0BFAD4C5104D06EF002BEB27 /* GTMNSData+Hex.h in Headers */,
0BFAD4C8104D06EF002BEB27 /* GTMNSDictionary+CaseInsensitive.h in Headers */,
F93207DE0F4B82DB005F37EA /* GTMSQLite.h in Headers */,
@@ -1934,6 +1944,7 @@
0BFAD4CB104D06FE002BEB27 /* GTMNSData+HexTest.m in Sources */,
0BFAD4CC104D06FE002BEB27 /* GTMNSDictionary+CaseInsensitiveTest.m in Sources */,
8B3080151056B917006C4C7A /* GTMNSNumber+64BitTest.m in Sources */,
+ 0B1B9B8A10FECDA00084EE4B /* GTMStringEncodingTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -2009,6 +2020,7 @@
8B307FF81056B773006C4C7A /* GTMNSNumber+64Bit.m in Sources */,
F4C6248C109753960069CADD /* GTMIBArray.m in Sources */,
8B158A9B10A8C31100C93125 /* GTMNSAnimation+Duration.m in Sources */,
+ 0B1B9B8810FECD870084EE4B /* GTMStringEncoding.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/GTMiPhone.xcodeproj/project.pbxproj b/GTMiPhone.xcodeproj/project.pbxproj
index a3929a7..8d5f191 100644
--- a/GTMiPhone.xcodeproj/project.pbxproj
+++ b/GTMiPhone.xcodeproj/project.pbxproj
@@ -25,6 +25,8 @@
0B859DA0104D08160064FE46 /* GTMNSData+HexTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B859D9B104D08050064FE46 /* GTMNSData+HexTest.m */; };
0B859DA1104D08160064FE46 /* GTMNSDictionary+CaseInsensitive.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B859D9D104D08050064FE46 /* GTMNSDictionary+CaseInsensitive.m */; };
0B859DA2104D08160064FE46 /* GTMNSDictionary+CaseInsensitiveTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B859D9E104D08050064FE46 /* GTMNSDictionary+CaseInsensitiveTest.m */; };
+ 0BBC768B10FEF62C0006FABE /* GTMStringEncoding.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BBC768910FEF61D0006FABE /* GTMStringEncoding.m */; };
+ 0BBC768C10FEF62C0006FABE /* GTMStringEncodingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BBC768A10FEF61D0006FABE /* GTMStringEncodingTest.m */; };
13C1ED4F104896C900907CD8 /* GTMUIView+SubtreeDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C1ED4C104896C900907CD8 /* GTMUIView+SubtreeDescription.m */; };
13C1ED50104896C900907CD8 /* GTMUIView+SubtreeDescriptionTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 13C1ED4D104896C900907CD8 /* GTMUIView+SubtreeDescriptionTest.m */; };
1D3623EC0D0F72F000981E51 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D3623EB0D0F72F000981E51 /* CoreGraphics.framework */; };
@@ -144,6 +146,9 @@
0B859D9C104D08050064FE46 /* GTMNSDictionary+CaseInsensitive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMNSDictionary+CaseInsensitive.h"; sourceTree = "<group>"; };
0B859D9D104D08050064FE46 /* GTMNSDictionary+CaseInsensitive.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+CaseInsensitive.m"; sourceTree = "<group>"; };
0B859D9E104D08050064FE46 /* GTMNSDictionary+CaseInsensitiveTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMNSDictionary+CaseInsensitiveTest.m"; sourceTree = "<group>"; };
+ 0BBC768810FEF61D0006FABE /* GTMStringEncoding.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = GTMStringEncoding.h; path = Foundation/GTMStringEncoding.h; sourceTree = SOURCE_ROOT; };
+ 0BBC768910FEF61D0006FABE /* GTMStringEncoding.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTMStringEncoding.m; path = Foundation/GTMStringEncoding.m; sourceTree = SOURCE_ROOT; };
+ 0BBC768A10FEF61D0006FABE /* GTMStringEncodingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = GTMStringEncodingTest.m; path = Foundation/GTMStringEncodingTest.m; sourceTree = SOURCE_ROOT; };
13C1ED4C104896C900907CD8 /* GTMUIView+SubtreeDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMUIView+SubtreeDescription.m"; sourceTree = "<group>"; };
13C1ED4D104896C900907CD8 /* GTMUIView+SubtreeDescriptionTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "GTMUIView+SubtreeDescriptionTest.m"; sourceTree = "<group>"; };
13C1ED4E104896C900907CD8 /* GTMUIView+SubtreeDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "GTMUIView+SubtreeDescription.h"; sourceTree = "<group>"; };
@@ -416,6 +421,9 @@
8BC047760DAE928A00C2D1CA /* Foundation */ = {
isa = PBXGroup;
children = (
+ 0BBC768810FEF61D0006FABE /* GTMStringEncoding.h */,
+ 0BBC768910FEF61D0006FABE /* GTMStringEncoding.m */,
+ 0BBC768A10FEF61D0006FABE /* GTMStringEncodingTest.m */,
0B859D99104D08050064FE46 /* GTMNSData+Hex.h */,
0B859D9A104D08050064FE46 /* GTMNSData+Hex.m */,
0B859D9B104D08050064FE46 /* GTMNSData+HexTest.m */,
@@ -798,6 +806,8 @@
0B859DA0104D08160064FE46 /* GTMNSData+HexTest.m in Sources */,
0B859DA1104D08160064FE46 /* GTMNSDictionary+CaseInsensitive.m in Sources */,
0B859DA2104D08160064FE46 /* GTMNSDictionary+CaseInsensitiveTest.m in Sources */,
+ 0BBC768B10FEF62C0006FABE /* GTMStringEncoding.m in Sources */,
+ 0BBC768C10FEF62C0006FABE /* GTMStringEncodingTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/ReleaseNotes.txt b/ReleaseNotes.txt
index a61862e..58aa502 100644
--- a/ReleaseNotes.txt
+++ b/ReleaseNotes.txt
@@ -385,6 +385,9 @@ Changes since 1.5.1
- Added GTMTypeCasting.h which gives you safer objective-c casts based on
C++ static_cast and dynamic_cast.
+- Added GTMStringEncoding which is a generic base 2-128 encoder/decoder with
+ support for custom character maps.
+
Release 1.5.1
Changes since 1.5.0