session-ios/Signal/src/util/DataUtil.m

192 lines
6.9 KiB
Objective-C

#import "DataUtil.h"
#import "Constraints.h"
@implementation NSData (Util)
-(const void*) bytesNotNull {
// note: this storage location is static, not auto, so its lifetime does not end
// (also, by virtue of being const, there are no threading/entrancy issues)
static const int SafeNonNullPointerToStaticStorageLocation[1];
if (self.length == 0) {
return SafeNonNullPointerToStaticStorageLocation;
} else {
require([self bytes] != nil);
return [self bytes];
}
}
+(NSData*) dataWithLength:(NSUInteger)length {
return [NSMutableData dataWithLength:length];
}
+(NSData*) dataWithSingleByte:(uint8_t)value{
return [NSData dataWithBytes:&value length:sizeof(value)];
}
-(NSNumber*) tryFindIndexOf:(NSData*)subData {
require(subData != nil);
if (subData.length > self.length) return nil;
NSUInteger subDataLength = subData.length;
NSUInteger excessLength = self.length - subDataLength;
const uint8_t* selfBytes = [self bytes];
const uint8_t* subDataBytes = [subData bytes];
for (NSUInteger i = 0; i <= excessLength; i++) {
if (memcmp(selfBytes+i, subDataBytes, subDataLength) == 0) {
return @(i);
}
}
return nil;
}
-(NSString*) encodedAsHexString {
if (![self bytes]) return @"";
NSMutableString* result = [NSMutableString string];
for (NSUInteger i = 0; i < self.length; ++i)
[result appendString:[NSString stringWithFormat:@"%02x", [self uint8At:i]]];
return result;
}
-(NSString*) decodedAsUtf8 {
// workaround for empty data having nil bytes
if (self.length == 0) return @"";
[NSString stringWithUTF8String:[self bytes]];
NSString* result = [[NSString alloc] initWithData:self encoding:NSUTF8StringEncoding];
checkOperationDescribe(result != nil, @"Invalid UTF8 data.");
return result;
}
-(NSString*) decodedAsAscii {
// workaround for empty data having nil bytes
if (self.length == 0) return @"";
// workaround for initWithData not enforcing the fact that NSASCIIStringEncoding means strict 7-bit
for (NSUInteger i = 0; i < self.length; i++) {
checkOperationDescribe(([self uint8At:i] & 0x80) == 0, @"Invalid ascii data.");
}
NSString* result = [[NSString alloc] initWithData:self encoding:NSASCIIStringEncoding];
checkOperationDescribe(result != nil, @"Invalid ascii data.");
return result;
}
-(NSString*) decodedAsAsciiReplacingErrorsWithDots {
const int MinPrintableChar = ' ';
const int MaxPrintableChar = '~';
NSMutableData* d = [NSMutableData dataWithLength:self.length];
for (NSUInteger i = 0; i < self.length; i++) {
uint8_t v = [self uint8At:i];
if (v < MinPrintableChar || v > MaxPrintableChar) v = '.';
[d setUint8At:i to:v];
}
return [d decodedAsAscii];
}
-(NSData*) skip:(NSUInteger)offset {
require(offset <= self.length);
return [self subdataWithRange:NSMakeRange(offset, self.length - offset)];
}
-(NSData*) take:(NSUInteger)takeCount {
require(takeCount <= self.length);
return [self subdataWithRange:NSMakeRange(0, takeCount)];
}
-(NSData*) skipLast:(NSUInteger)skipLastCount {
require(skipLastCount <= self.length);
return [self subdataWithRange:NSMakeRange(0, self.length - skipLastCount)];
}
-(NSData*) takeLast:(NSUInteger)takeLastCount {
require(takeLastCount <= self.length);
return [self subdataWithRange:NSMakeRange(self.length - takeLastCount, takeLastCount)];
}
-(NSData*) subdataVolatileWithRange:(NSRange)range {
NSUInteger length = self.length;
require(range.location <= length);
require(range.length <= length);
require(range.location + range.length <= length);
return [NSData dataWithBytesNoCopy:(uint8_t*)[self bytes] + range.location length:range.length freeWhenDone:NO];
}
-(NSData*) skipVolatile:(NSUInteger)offset {
require(offset <= self.length);
return [self subdataVolatileWithRange:NSMakeRange(offset, self.length - offset)];
}
-(NSData*) takeVolatile:(NSUInteger)takeCount {
require(takeCount <= self.length);
return [self subdataVolatileWithRange:NSMakeRange(0, takeCount)];
}
-(NSData*) skipLastVolatile:(NSUInteger)skipLastCount {
require(skipLastCount <= self.length);
return [self subdataVolatileWithRange:NSMakeRange(0, self.length - skipLastCount)];
}
-(NSData*) takeLastVolatile:(NSUInteger)takeLastCount {
require(takeLastCount <= self.length);
return [self subdataVolatileWithRange:NSMakeRange(self.length - takeLastCount, takeLastCount)];
}
-(uint8_t) highUint4AtByteOffset:(NSUInteger)offset {
return [self uint8At:offset] >> 4;
}
-(uint8_t) lowUint4AtByteOffset:(NSUInteger)offset {
return [self uint8At:offset] & 0xF;
}
-(uint8_t) uint8At:(NSUInteger)offset {
require(offset < self.length);
return ((const uint8_t*)[self bytes])[offset];
}
-(const uint8_t*) constPtrToUint8At:(NSUInteger)offset {
return ((uint8_t*)[self bytes]) + offset;
}
-(NSString*) encodedAsBase64 {
const NSUInteger BitsPerBase64Word = 6;
const NSUInteger BitsPerByte = 8;
const uint8_t Base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
NSUInteger byteCount = self.length;
NSUInteger bitCount = byteCount*BitsPerByte;
NSUInteger base64WordCount = bitCount / BitsPerBase64Word;
if (base64WordCount * BitsPerBase64Word < bitCount) base64WordCount += 1;
// base 256 to to base 2
bool bits[bitCount];
for (NSUInteger i = 0; i < byteCount; i++) {
for (NSUInteger j = 0; j < BitsPerByte; j++) {
bits[i*BitsPerByte + BitsPerByte - 1 - j] = (([self uint8At:i] >> j) & 1) != 0;
}
}
// base 2 to base 64
uint8_t base64Words[base64WordCount];
for (NSUInteger i = 0; i < base64WordCount; i++) {
base64Words[i] = 0;
for (NSUInteger j = 0; j < BitsPerBase64Word; j++) {
NSUInteger offset = i*BitsPerBase64Word + BitsPerBase64Word - 1 - j;
if (offset >= bitCount) continue; // default to 0
if (bits[offset]) base64Words[i] |= 1 << j;
}
}
// base 64 to ASCII data
NSUInteger paddingCount = bitCount % 3;
NSMutableData* asciiData = [NSMutableData dataWithLength:base64WordCount+paddingCount];
for (NSUInteger i = 0; i < base64WordCount; i++) {
[asciiData setUint8At:i to:Base64Chars[base64Words[i]]];
}
for (NSUInteger i = 0; i < paddingCount; i++) {
[asciiData setUint8At:i+base64WordCount to:'='];
}
return [asciiData decodedAsAscii];
}
@end
@implementation NSMutableData (Util)
-(void) setUint8At:(NSUInteger)offset to:(uint8_t)newValue {
require(offset < self.length);
((uint8_t*)[self mutableBytes])[offset] = newValue;
}
-(void) replaceBytesStartingAt:(NSUInteger)offset withData:(NSData*)data {
require(data != nil);
require(offset + data.length <= self.length);
[self replaceBytesInRange:NSMakeRange(offset, data.length) withBytes:[data bytes]];
}
@end