mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
791 lines
23 KiB
Objective-C
791 lines
23 KiB
Objective-C
// Copyright 2008 Cyrus Najmabadi
|
|
//
|
|
// 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 "CodedInputStream.h"
|
|
|
|
#import "Message_Builder.h"
|
|
#import "Utilities.h"
|
|
#import "WireFormat.h"
|
|
#import "UnknownFieldSet_Builder.h"
|
|
|
|
@interface PBCodedInputStream ()
|
|
@property (retain) NSMutableData* buffer;
|
|
@property (retain) NSInputStream* input;
|
|
@end
|
|
|
|
|
|
@implementation PBCodedInputStream
|
|
|
|
const int32_t DEFAULT_RECURSION_LIMIT = 64;
|
|
const int32_t DEFAULT_SIZE_LIMIT = 64 << 20; // 64MB
|
|
const int32_t BUFFER_SIZE = 4096;
|
|
|
|
@synthesize buffer;
|
|
@synthesize input;
|
|
|
|
- (void) dealloc {
|
|
[input close];
|
|
self.buffer = nil;
|
|
self.input = nil;
|
|
}
|
|
|
|
|
|
- (void) commonInit {
|
|
currentLimit = INT_MAX;
|
|
recursionLimit = DEFAULT_RECURSION_LIMIT;
|
|
sizeLimit = DEFAULT_SIZE_LIMIT;
|
|
}
|
|
|
|
|
|
- (id) initWithData:(NSData*) data {
|
|
if ((self = [super init])) {
|
|
self.buffer = [NSMutableData dataWithData:data];
|
|
bufferSize = (int32_t)buffer.length;
|
|
self.input = nil;
|
|
[self commonInit];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
|
|
- (id) initWithInputStream:(NSInputStream*) input_ {
|
|
if ((self = [super init])) {
|
|
self.buffer = [NSMutableData dataWithLength:BUFFER_SIZE];
|
|
bufferSize = 0;
|
|
self.input = input_;
|
|
[input open];
|
|
[self commonInit];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
|
|
+ (PBCodedInputStream*) streamWithData:(NSData*) data {
|
|
return [[PBCodedInputStream alloc] initWithData:data];
|
|
}
|
|
|
|
|
|
+ (PBCodedInputStream*) streamWithInputStream:(NSInputStream*) input {
|
|
return [[PBCodedInputStream alloc] initWithInputStream:input];
|
|
}
|
|
|
|
|
|
/**
|
|
* Attempt to read a field tag, returning zero if we have reached EOF.
|
|
* Protocol message parsers use this to read tags, since a protocol message
|
|
* may legally end wherever a tag occurs, and zero is not a valid tag number.
|
|
*/
|
|
- (int32_t) readTag {
|
|
if (self.isAtEnd) {
|
|
lastTag = 0;
|
|
return 0;
|
|
}
|
|
|
|
lastTag = [self readRawVarint32];
|
|
if (lastTag == 0) {
|
|
// If we actually read zero, that's not a valid tag.
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"Invalid Tag" userInfo:nil];
|
|
}
|
|
return lastTag;
|
|
}
|
|
|
|
/**
|
|
* Verifies that the last call to readTag() returned the given tag value.
|
|
* This is used to verify that a nested group ended with the correct
|
|
* end tag.
|
|
*
|
|
* @throws InvalidProtocolBufferException {@code value} does not match the
|
|
* last tag.
|
|
*/
|
|
- (void) checkLastTagWas:(int32_t) value {
|
|
if (lastTag != value) {
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"Invalid End Tag" userInfo:nil];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Reads and discards a single field, given its tag value.
|
|
*
|
|
* @return {@code NO} if the tag is an endgroup tag, in which case
|
|
* nothing is skipped. Otherwise, returns {@code YES}.
|
|
*/
|
|
- (BOOL) skipField:(int32_t) tag {
|
|
switch (PBWireFormatGetTagWireType(tag)) {
|
|
case PBWireFormatVarint:
|
|
[self readInt32];
|
|
return YES;
|
|
case PBWireFormatFixed64:
|
|
[self readRawLittleEndian64];
|
|
return YES;
|
|
case PBWireFormatLengthDelimited:
|
|
[self skipRawData:[self readRawVarint32]];
|
|
return YES;
|
|
case PBWireFormatStartGroup:
|
|
[self skipMessage];
|
|
[self checkLastTagWas:
|
|
PBWireFormatMakeTag(PBWireFormatGetTagFieldNumber(tag),
|
|
PBWireFormatEndGroup)];
|
|
return YES;
|
|
case PBWireFormatEndGroup:
|
|
return NO;
|
|
case PBWireFormatFixed32:
|
|
[self readRawLittleEndian32];
|
|
return YES;
|
|
default:
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"Invalid Wire Type" userInfo:nil];
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Reads and discards an entire message. This will read either until EOF
|
|
* or until an endgroup tag, whichever comes first.
|
|
*/
|
|
- (void) skipMessage {
|
|
while (YES) {
|
|
int32_t tag = [self readTag];
|
|
if (tag == 0 || ![self skipField:tag]) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/** Read a {@code double} field value from the stream. */
|
|
- (Float64) readDouble {
|
|
return convertInt64ToFloat64([self readRawLittleEndian64]);
|
|
}
|
|
|
|
|
|
/** Read a {@code float} field value from the stream. */
|
|
- (Float32) readFloat {
|
|
return convertInt32ToFloat32([self readRawLittleEndian32]);
|
|
}
|
|
|
|
|
|
/** Read a {@code uint64} field value from the stream. */
|
|
- (int64_t) readUInt64 {
|
|
return [self readRawVarint64];
|
|
}
|
|
|
|
|
|
/** Read an {@code int64} field value from the stream. */
|
|
- (int64_t) readInt64 {
|
|
return [self readRawVarint64];
|
|
}
|
|
|
|
|
|
/** Read an {@code int32} field value from the stream. */
|
|
- (int32_t) readInt32 {
|
|
return [self readRawVarint32];
|
|
}
|
|
|
|
|
|
/** Read a {@code fixed64} field value from the stream. */
|
|
- (int64_t) readFixed64 {
|
|
return [self readRawLittleEndian64];
|
|
}
|
|
|
|
|
|
/** Read a {@code fixed32} field value from the stream. */
|
|
- (int32_t) readFixed32 {
|
|
return [self readRawLittleEndian32];
|
|
}
|
|
|
|
|
|
/** Read a {@code bool} field value from the stream. */
|
|
- (BOOL) readBool {
|
|
return [self readRawVarint32] != 0;
|
|
}
|
|
|
|
|
|
/** Read a {@code string} field value from the stream. */
|
|
- (NSString*) readString {
|
|
int32_t size = [self readRawVarint32];
|
|
if (size <= (bufferSize - bufferPos) && size > 0) {
|
|
// Fast path: We already have the bytes in a contiguous buffer, so
|
|
// just copy directly from it.
|
|
// new String(buffer, bufferPos, size, "UTF-8");
|
|
NSString* result = [[NSString alloc] initWithBytes:(((uint8_t*) buffer.bytes) + bufferPos)
|
|
length:(NSUInteger)size
|
|
encoding:NSUTF8StringEncoding];
|
|
bufferPos += size;
|
|
return result;
|
|
} else {
|
|
// Slow path: Build a byte array first then copy it.
|
|
NSData* data = [self readRawData:size];
|
|
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
}
|
|
}
|
|
|
|
|
|
/** Read a {@code group} field value from the stream. */
|
|
- (void) readGroup:(int32_t) fieldNumber
|
|
builder:(id<PBMessage_Builder>) builder
|
|
extensionRegistry:(PBExtensionRegistry*) extensionRegistry {
|
|
if (recursionDepth >= recursionLimit) {
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"Recursion Limit Exceeded" userInfo:nil];
|
|
}
|
|
++recursionDepth;
|
|
[builder mergeFromCodedInputStream:self extensionRegistry:extensionRegistry];
|
|
[self checkLastTagWas:PBWireFormatMakeTag(fieldNumber, PBWireFormatEndGroup)];
|
|
--recursionDepth;
|
|
}
|
|
|
|
|
|
/**
|
|
* Reads a {@code group} field value from the stream and merges it into the
|
|
* given {@link PBUnknownFieldSet}.
|
|
*/
|
|
- (void) readUnknownGroup:(int32_t) fieldNumber
|
|
builder:(PBUnknownFieldSet_Builder*) builder {
|
|
if (recursionDepth >= recursionLimit) {
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"Recursion Limit Exceeded" userInfo:nil];
|
|
}
|
|
++recursionDepth;
|
|
[builder mergeFromCodedInputStream:self];
|
|
[self checkLastTagWas:PBWireFormatMakeTag(fieldNumber, PBWireFormatEndGroup)];
|
|
--recursionDepth;
|
|
}
|
|
|
|
|
|
/** Read an embedded message field value from the stream. */
|
|
- (void) readMessage:(id<PBMessage_Builder>) builder
|
|
extensionRegistry:(PBExtensionRegistry*) extensionRegistry {
|
|
int32_t length = [self readRawVarint32];
|
|
if (recursionDepth >= recursionLimit) {
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"Recursion Limit Exceeded" userInfo:nil];
|
|
}
|
|
int32_t oldLimit = [self pushLimit:length];
|
|
++recursionDepth;
|
|
[builder mergeFromCodedInputStream:self extensionRegistry:extensionRegistry];
|
|
[self checkLastTagWas:0];
|
|
--recursionDepth;
|
|
[self popLimit:oldLimit];
|
|
}
|
|
|
|
|
|
/** Read a {@code bytes} field value from the stream. */
|
|
- (NSData*) readData {
|
|
int32_t size = [self readRawVarint32];
|
|
if (size < bufferSize - bufferPos && size > 0) {
|
|
// Fast path: We already have the bytes in a contiguous buffer, so
|
|
// just copy directly from it.
|
|
NSData* result = [NSData dataWithBytes:(((uint8_t*) buffer.bytes) + bufferPos) length:(NSUInteger)size];
|
|
bufferPos += size;
|
|
return result;
|
|
} else {
|
|
// Slow path: Build a byte array first then copy it.
|
|
return [self readRawData:size];
|
|
}
|
|
}
|
|
|
|
|
|
/** Read a {@code uint32} field value from the stream. */
|
|
- (int32_t) readUInt32 {
|
|
return [self readRawVarint32];
|
|
}
|
|
|
|
|
|
/**
|
|
* Read an enum field value from the stream. Caller is responsible
|
|
* for converting the numeric value to an actual enum.
|
|
*/
|
|
- (int32_t) readEnum {
|
|
return [self readRawVarint32];
|
|
}
|
|
|
|
|
|
/** Read an {@code sfixed32} field value from the stream. */
|
|
- (int32_t) readSFixed32 {
|
|
return [self readRawLittleEndian32];
|
|
}
|
|
|
|
|
|
/** Read an {@code sfixed64} field value from the stream. */
|
|
- (int64_t) readSFixed64 {
|
|
return [self readRawLittleEndian64];
|
|
}
|
|
|
|
|
|
/** Read an {@code sint32} field value from the stream. */
|
|
- (int32_t) readSInt32 {
|
|
return decodeZigZag32([self readRawVarint32]);
|
|
}
|
|
|
|
|
|
/** Read an {@code sint64} field value from the stream. */
|
|
- (int64_t) readSInt64 {
|
|
return decodeZigZag64([self readRawVarint64]);
|
|
}
|
|
|
|
|
|
// =================================================================
|
|
|
|
/**
|
|
* Read a raw Varint from the stream. If larger than 32 bits, discard the
|
|
* upper bits.
|
|
*/
|
|
- (int32_t) readRawVarint32 {
|
|
int8_t tmp = [self readRawByte];
|
|
if (tmp >= 0) {
|
|
return tmp;
|
|
}
|
|
int32_t result = tmp & 0x7f;
|
|
if ((tmp = [self readRawByte]) >= 0) {
|
|
result |= tmp << 7;
|
|
} else {
|
|
result |= (tmp & 0x7f) << 7;
|
|
if ((tmp = [self readRawByte]) >= 0) {
|
|
result |= tmp << 14;
|
|
} else {
|
|
result |= (tmp & 0x7f) << 14;
|
|
if ((tmp = [self readRawByte]) >= 0) {
|
|
result |= tmp << 21;
|
|
} else {
|
|
result |= (tmp & 0x7f) << 21;
|
|
result |= (tmp = [self readRawByte]) << 28;
|
|
if (tmp < 0) {
|
|
// Discard upper 32 bits.
|
|
for (int i = 0; i < 5; i++) {
|
|
if ([self readRawByte] >= 0) {
|
|
return result;
|
|
}
|
|
}
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"malformedVarint" userInfo:nil];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/** Read a raw Varint from the stream. */
|
|
- (int64_t) readRawVarint64 {
|
|
int32_t shift = 0;
|
|
int64_t result = 0;
|
|
while (shift < 64) {
|
|
int8_t b = [self readRawByte];
|
|
result |= (int64_t)(b & 0x7F) << shift;
|
|
if ((b & 0x80) == 0) {
|
|
return result;
|
|
}
|
|
shift += 7;
|
|
}
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"malformedVarint" userInfo:nil];
|
|
}
|
|
|
|
|
|
/** Read a 32-bit little-endian integer from the stream. */
|
|
- (int32_t) readRawLittleEndian32 {
|
|
int8_t b1 = [self readRawByte];
|
|
int8_t b2 = [self readRawByte];
|
|
int8_t b3 = [self readRawByte];
|
|
int8_t b4 = [self readRawByte];
|
|
return
|
|
(((int32_t)b1 & 0xff) ) |
|
|
(((int32_t)b2 & 0xff) << 8) |
|
|
(((int32_t)b3 & 0xff) << 16) |
|
|
(((int32_t)b4 & 0xff) << 24);
|
|
}
|
|
|
|
|
|
/** Read a 64-bit little-endian integer from the stream. */
|
|
- (int64_t) readRawLittleEndian64 {
|
|
int8_t b1 = [self readRawByte];
|
|
int8_t b2 = [self readRawByte];
|
|
int8_t b3 = [self readRawByte];
|
|
int8_t b4 = [self readRawByte];
|
|
int8_t b5 = [self readRawByte];
|
|
int8_t b6 = [self readRawByte];
|
|
int8_t b7 = [self readRawByte];
|
|
int8_t b8 = [self readRawByte];
|
|
return
|
|
(((int64_t)b1 & 0xff) ) |
|
|
(((int64_t)b2 & 0xff) << 8) |
|
|
(((int64_t)b3 & 0xff) << 16) |
|
|
(((int64_t)b4 & 0xff) << 24) |
|
|
(((int64_t)b5 & 0xff) << 32) |
|
|
(((int64_t)b6 & 0xff) << 40) |
|
|
(((int64_t)b7 & 0xff) << 48) |
|
|
(((int64_t)b8 & 0xff) << 56);
|
|
}
|
|
|
|
|
|
/**
|
|
* Decode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers
|
|
* into values that can be efficiently encoded with varint. (Otherwise,
|
|
* negative values must be sign-extended to 64 bits to be varint encoded,
|
|
* thus always taking 10 bytes on the wire.)
|
|
*
|
|
* @param n An unsigned 32-bit integer, stored in a signed int
|
|
* @return A signed 32-bit integer.
|
|
*/
|
|
int32_t decodeZigZag32(int32_t n) {
|
|
return logicalRightShift32(n, 1) ^ -(n & 1);
|
|
}
|
|
|
|
|
|
/**
|
|
* Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers
|
|
* into values that can be efficiently encoded with varint. (Otherwise,
|
|
* negative values must be sign-extended to 64 bits to be varint encoded,
|
|
* thus always taking 10 bytes on the wire.)
|
|
*
|
|
* @param n An unsigned 64-bit integer, stored in a signed int
|
|
* @return A signed 64-bit integer.
|
|
*/
|
|
int64_t decodeZigZag64(int64_t n) {
|
|
return logicalRightShift64(n, 1) ^ -(n & 1);
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the maximum message recursion depth. In order to prevent malicious
|
|
* messages from causing stack overflows, {@code PBCodedInputStream} limits
|
|
* how deeply messages may be nested. The default limit is 64.
|
|
*
|
|
* @return the old limit.
|
|
*/
|
|
- (int32_t) setRecursionLimit:(int32_t) limit {
|
|
if (limit < 0) {
|
|
@throw [NSException exceptionWithName:@"IllegalArgument" reason:@"Recursion limit cannot be negative" userInfo:nil];
|
|
}
|
|
int32_t oldLimit = recursionLimit;
|
|
recursionLimit = limit;
|
|
return oldLimit;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the maximum message size. In order to prevent malicious
|
|
* messages from exhausting memory or causing integer overflows,
|
|
* {@code PBCodedInputStream} limits how large a message may be.
|
|
* The default limit is 64MB. You should set this limit as small
|
|
* as you can without harming your app's functionality. Note that
|
|
* size limits only apply when reading from an {@code InputStream}, not
|
|
* when constructed around a raw byte array.
|
|
*
|
|
* @return the old limit.
|
|
*/
|
|
- (int32_t) setSizeLimit:(int32_t) limit {
|
|
if (limit < 0) {
|
|
@throw [NSException exceptionWithName:@"IllegalArgument" reason:@"Size limit cannot be negative:" userInfo:nil];
|
|
}
|
|
int32_t oldLimit = sizeLimit;
|
|
sizeLimit = limit;
|
|
return oldLimit;
|
|
}
|
|
|
|
|
|
/**
|
|
* Resets the current size counter to zero (see {@link #setSizeLimit(int)}).
|
|
*/
|
|
- (void) resetSizeCounter {
|
|
totalBytesRetired = 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Sets {@code currentLimit} to (current position) + {@code byteLimit}. This
|
|
* is called when descending into a length-delimited embedded message.
|
|
*
|
|
* @return the old limit.
|
|
*/
|
|
- (int32_t) pushLimit:(int32_t) byteLimit {
|
|
if (byteLimit < 0) {
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"negativeSize" userInfo:nil];
|
|
}
|
|
byteLimit += totalBytesRetired + bufferPos;
|
|
int32_t oldLimit = currentLimit;
|
|
if (byteLimit > oldLimit) {
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil];
|
|
}
|
|
currentLimit = byteLimit;
|
|
|
|
[self recomputeBufferSizeAfterLimit];
|
|
|
|
return oldLimit;
|
|
}
|
|
|
|
|
|
- (void) recomputeBufferSizeAfterLimit {
|
|
bufferSize += bufferSizeAfterLimit;
|
|
int32_t bufferEnd = totalBytesRetired + bufferSize;
|
|
if (bufferEnd > currentLimit) {
|
|
// Limit is in current buffer.
|
|
bufferSizeAfterLimit = bufferEnd - currentLimit;
|
|
bufferSize -= bufferSizeAfterLimit;
|
|
} else {
|
|
bufferSizeAfterLimit = 0;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Discards the current limit, returning to the previous limit.
|
|
*
|
|
* @param oldLimit The old limit, as returned by {@code pushLimit}.
|
|
*/
|
|
- (void) popLimit:(int32_t) oldLimit {
|
|
currentLimit = oldLimit;
|
|
[self recomputeBufferSizeAfterLimit];
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the number of bytes to be read before the current limit.
|
|
* If no limit is set, returns -1.
|
|
*/
|
|
- (int32_t) bytesUntilLimit {
|
|
if (currentLimit == INT_MAX) {
|
|
return -1;
|
|
}
|
|
|
|
int32_t currentAbsolutePosition = totalBytesRetired + bufferPos;
|
|
return currentLimit - currentAbsolutePosition;
|
|
}
|
|
|
|
/**
|
|
* Returns true if the stream has reached the end of the input. This is the
|
|
* case if either the end of the underlying input source has been reached or
|
|
* if the stream has reached a limit created using {@link #pushLimit(int)}.
|
|
*/
|
|
- (BOOL) isAtEnd {
|
|
return bufferPos == bufferSize && ![self refillBuffer:NO];
|
|
}
|
|
|
|
|
|
/**
|
|
* Called with {@code this.buffer} is empty to read more bytes from the
|
|
* input. If {@code mustSucceed} is YES, refillBuffer() gurantees that
|
|
* either there will be at least one byte in the buffer when it returns
|
|
* or it will throw an exception. If {@code mustSucceed} is NO,
|
|
* refillBuffer() returns NO if no more bytes were available.
|
|
*/
|
|
- (BOOL) refillBuffer:(BOOL) mustSucceed {
|
|
if (bufferPos < bufferSize) {
|
|
@throw [NSException exceptionWithName:@"IllegalState" reason:@"refillBuffer called when buffer wasn't empty." userInfo:nil];
|
|
}
|
|
|
|
if (totalBytesRetired + bufferSize == currentLimit) {
|
|
// Oops, we hit a limit.
|
|
if (mustSucceed) {
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil];
|
|
} else {
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
totalBytesRetired += bufferSize;
|
|
|
|
// TODO(cyrusn): does NSInputStream behave the same as java.io.InputStream
|
|
// when there is no more data?
|
|
bufferPos = 0;
|
|
bufferSize = 0;
|
|
if (input != nil) {
|
|
bufferSize = (int32_t)[input read:buffer.mutableBytes maxLength:buffer.length];
|
|
}
|
|
|
|
if (bufferSize <= 0) {
|
|
bufferSize = 0;
|
|
if (mustSucceed) {
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil];
|
|
} else {
|
|
return NO;
|
|
}
|
|
} else {
|
|
[self recomputeBufferSizeAfterLimit];
|
|
NSInteger totalBytesRead = totalBytesRetired + bufferSize + bufferSizeAfterLimit;
|
|
if (totalBytesRead > sizeLimit || totalBytesRead < 0) {
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"sizeLimitExceeded" userInfo:nil];
|
|
}
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Read one byte from the input.
|
|
*
|
|
* @throws InvalidProtocolBufferException The end of the stream or the current
|
|
* limit was reached.
|
|
*/
|
|
- (int8_t) readRawByte {
|
|
if (bufferPos == bufferSize) {
|
|
[self refillBuffer:YES];
|
|
}
|
|
int8_t* bytes = (int8_t*)buffer.bytes;
|
|
return bytes[bufferPos++];
|
|
}
|
|
|
|
|
|
/**
|
|
* Read a fixed size of bytes from the input.
|
|
*
|
|
* @throws InvalidProtocolBufferException The end of the stream or the current
|
|
* limit was reached.
|
|
*/
|
|
- (NSData*) readRawData:(int32_t) size {
|
|
if (size < 0) {
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"negativeSize" userInfo:nil];
|
|
}
|
|
|
|
if (totalBytesRetired + bufferPos + size > currentLimit) {
|
|
// Read to the end of the stream anyway.
|
|
[self skipRawData:currentLimit - totalBytesRetired - bufferPos];
|
|
// Then fail.
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil];
|
|
}
|
|
|
|
if (size <= bufferSize - bufferPos) {
|
|
// We have all the bytes we need already.
|
|
NSData* data = [NSData dataWithBytes:(((int8_t*) buffer.bytes) + bufferPos) length:(NSUInteger)size];
|
|
bufferPos += size;
|
|
return data;
|
|
} else if (size < BUFFER_SIZE) {
|
|
// Reading more bytes than are in the buffer, but not an excessive number
|
|
// of bytes. We can safely allocate the resulting array ahead of time.
|
|
|
|
// First copy what we have.
|
|
NSMutableData* bytes = [NSMutableData dataWithLength:(NSUInteger)size];
|
|
int32_t pos = bufferSize - bufferPos;
|
|
memcpy(bytes.mutableBytes, ((int8_t*)buffer.bytes) + bufferPos, pos);
|
|
bufferPos = bufferSize;
|
|
|
|
// We want to use refillBuffer() and then copy from the buffer into our
|
|
// byte array rather than reading directly into our byte array because
|
|
// the input may be unbuffered.
|
|
[self refillBuffer:YES];
|
|
|
|
while (size - pos > bufferSize) {
|
|
memcpy(((int8_t*)bytes.mutableBytes) + pos, buffer.bytes, bufferSize);
|
|
pos += bufferSize;
|
|
bufferPos = bufferSize;
|
|
[self refillBuffer:YES];
|
|
}
|
|
|
|
memcpy(((int8_t*)bytes.mutableBytes) + pos, buffer.bytes, size - pos);
|
|
bufferPos = size - pos;
|
|
|
|
return bytes;
|
|
} else {
|
|
// The size is very large. For security reasons, we can't allocate the
|
|
// entire byte array yet. The size comes directly from the input, so a
|
|
// maliciously-crafted message could provide a bogus very large size in
|
|
// order to trick the app into allocating a lot of memory. We avoid this
|
|
// by allocating and reading only a small chunk at a time, so that the
|
|
// malicious message must actuall* e* extremely large to cause
|
|
// problems. Meanwhile, we limit the allowed size of a message elsewhere.
|
|
|
|
// Remember the buffer markers since we'll have to copy the bytes out of
|
|
// it later.
|
|
int32_t originalBufferPos = bufferPos;
|
|
int32_t originalBufferSize = bufferSize;
|
|
|
|
// Mark the current buffer consumed.
|
|
totalBytesRetired += bufferSize;
|
|
bufferPos = 0;
|
|
bufferSize = 0;
|
|
|
|
// Read all the rest of the bytes we need.
|
|
int32_t sizeLeft = size - (originalBufferSize - originalBufferPos);
|
|
NSMutableArray* chunks = [NSMutableArray array];
|
|
|
|
while (sizeLeft > 0) {
|
|
NSMutableData* chunk = [NSMutableData dataWithLength:(NSUInteger)MIN(sizeLeft, BUFFER_SIZE)];
|
|
|
|
int32_t pos = 0;
|
|
while (pos < (int32_t)chunk.length) {
|
|
int32_t n = 0;
|
|
if (input != nil) {
|
|
n = (int32_t)[input read:(((uint8_t*) chunk.mutableBytes) + pos) maxLength:chunk.length - (NSUInteger)pos];
|
|
}
|
|
if (n <= 0) {
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil];
|
|
}
|
|
totalBytesRetired += n;
|
|
pos += n;
|
|
}
|
|
sizeLeft -= chunk.length;
|
|
[chunks addObject:chunk];
|
|
}
|
|
|
|
// OK, got everything. Now concatenate it all into one buffer.
|
|
NSMutableData* bytes = [NSMutableData dataWithLength:(NSUInteger)size];
|
|
|
|
// Start by copying the leftover bytes from this.buffer.
|
|
int32_t pos = originalBufferSize - originalBufferPos;
|
|
memcpy(bytes.mutableBytes, ((int8_t*)buffer.bytes) + originalBufferPos, pos);
|
|
|
|
// And now all the chunks.
|
|
for (NSData* chunk in chunks) {
|
|
memcpy(((int8_t*)bytes.mutableBytes) + pos, chunk.bytes, chunk.length);
|
|
pos += chunk.length;
|
|
}
|
|
|
|
// Done.
|
|
return bytes;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Reads and discards {@code size} bytes.
|
|
*
|
|
* @throws InvalidProtocolBufferException The end of the stream or the current
|
|
* limit was reached.
|
|
*/
|
|
- (void) skipRawData:(int32_t) size {
|
|
if (size < 0) {
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"negativeSize" userInfo:nil];
|
|
}
|
|
|
|
if (totalBytesRetired + bufferPos + size > currentLimit) {
|
|
// Read to the end of the stream anyway.
|
|
[self skipRawData:currentLimit - totalBytesRetired - bufferPos];
|
|
// Then fail.
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil];
|
|
}
|
|
|
|
if (size <= (bufferSize - bufferPos)) {
|
|
// We have all the bytes we need already.
|
|
bufferPos += size;
|
|
} else {
|
|
// Skipping more bytes than are in the buffer. First skip what we have.
|
|
int32_t pos = bufferSize - bufferPos;
|
|
totalBytesRetired += pos;
|
|
bufferPos = 0;
|
|
bufferSize = 0;
|
|
|
|
// Then skip directly from the InputStream for the rest.
|
|
while (pos < size) {
|
|
NSMutableData* data = [NSMutableData dataWithLength:(NSUInteger)(size - pos)];
|
|
int32_t n = (input == nil) ? -1 : (int32_t)[input read:data.mutableBytes maxLength:(NSUInteger)(size - pos)];
|
|
if (n <= 0) {
|
|
@throw [NSException exceptionWithName:@"InvalidProtocolBuffer" reason:@"truncatedMessage" userInfo:nil];
|
|
}
|
|
pos += n;
|
|
totalBytesRetired += n;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@end
|