//
// MessagePack for Java
//
// 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.
//
package org.msgpack.core;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.math.BigInteger;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.MalformedInputException;
import org.msgpack.core.MessagePack.Code;
import org.msgpack.core.buffer.MessageBuffer;
import org.msgpack.core.buffer.MessageBufferInput;
import org.msgpack.value.Cursor;
import org.msgpack.value.ValueType;
import org.msgpack.value.holder.FloatHolder;
import org.msgpack.value.holder.IntegerHolder;
import org.msgpack.value.holder.ValueHolder;
import org.msgpack.value.impl.CursorImpl;
import static org.msgpack.core.Preconditions.*;
/**
* MessageUnpacker lets an application read message-packed values from a data stream.
* The application needs to call {@link #getNextFormat()} followed by an appropriate unpackXXX method according to the the returned format type.
*
* <pre>
* <code>
* MessageUnpacker unpacker = MessagePackFactory.DEFAULT.newUnpacker(...);
* while(unpacker.hasNext()) {
* MessageFormat f = unpacker.getNextFormat();
* switch(f) {
* case MessageFormat.POSFIXINT:
* case MessageFormat.INT8:
* case MessageFormat.UINT8: {
* int v = unpacker.unpackInt();
* break;
* }
* case MessageFormat.STRING: {
* String v = unpacker.unpackString();
* break;
* }
* // ...
* }
* }
*
* </code>
* </pre>
*/
public class MessageUnpacker implements Closeable {
private final static MessageBuffer EMPTY_BUFFER = MessageBuffer.wrap(new byte[0]);
private final MessagePack.Config config;
private MessageBufferInput in;
/**
* Points to the current buffer to read
*/
private MessageBuffer buffer = EMPTY_BUFFER;
/**
* Cursor position in the current buffer
*/
private int position;
/**
* Total read byte size
*/
private long totalReadBytes;
/**
* For preserving the next buffer to use
*/
private MessageBuffer secondaryBuffer = null;
/**
* Extra buffer for string data at the buffer boundary. Using 17-byte buffer (for FIXEXT16) is sufficient.
*/
private final MessageBuffer extraBuffer = MessageBuffer.wrap(new byte[24]);
/**
* True if no more data is available from the MessageBufferInput
*/
private boolean reachedEOF = false;
/**
* For decoding String in unpackString.
*/
private CharsetDecoder decoder;
/**
* Buffer for decoding strings
*/
private CharBuffer decodeBuffer;
/**
* Get a {@link org.msgpack.value.Cursor} for traversing message-packed values
* @return
*/
public Cursor getCursor() {
return new CursorImpl(this);
}
/**
* Create an MessageUnpacker that reads data from the given MessageBufferInput
*
* @param in
*/
public MessageUnpacker(MessageBufferInput in) {
this(in, MessagePack.DEFAULT_CONFIG);
}
/**
* Create an MessageUnpacker
* @param in
* @param config configuration
*/
public MessageUnpacker(MessageBufferInput in, MessagePack.Config config) {
// Root constructor. All of the constructors must call this constructor.
this.in = checkNotNull(in, "MessageBufferInput is null");
this.config = checkNotNull(config, "Config");
}
public void reset(MessageBufferInput in) throws IOException {
MessageBufferInput newIn = checkNotNull(in, "MessageBufferInput is null");
try {
if(in != newIn) {
close();
}
}
finally {
// Reset the internal states here for the exception safety
this.in = newIn;
this.buffer = EMPTY_BUFFER;
this.position = 0;
this.totalReadBytes = 0;
this.secondaryBuffer = null;
this.reachedEOF = false;
// No need to initialize the already allocated string decoder here since we can reuse it.
}
}
public long getTotalReadBytes() {
return totalReadBytes + position;
}
private void prepareDecoder() {
if(decoder == null) {
decodeBuffer = CharBuffer.allocate(config.getStringDecoderBufferSize());
decoder = MessagePack.UTF8.newDecoder()
.onMalformedInput(config.getActionOnMalFormedInput())
.onUnmappableCharacter(config.getActionOnUnmappableCharacter());
}
}
/**
* Relocate the cursor position so that it points to the real buffer
*
* @return true if it succeeds to move the cursor, or false if there is no more buffer to read
* @throws IOException when failed to retrieve next buffer
*/
private boolean ensureBuffer() throws IOException {
while(buffer != null && position >= buffer.size()) {
// Fetch the next buffer
int bufferSize = buffer.size();
position -= bufferSize;
totalReadBytes += bufferSize;
buffer = takeNextBuffer();
}
return buffer != null;
}
private MessageBuffer takeNextBuffer() throws IOException {
if(reachedEOF)
return null;
MessageBuffer nextBuffer = null;
if(secondaryBuffer == null) {
nextBuffer = in.next();
}
else {
nextBuffer = secondaryBuffer;
secondaryBuffer = null;
}
if(nextBuffer == null) {
reachedEOF = true;
}
return nextBuffer;
}
/**
* Ensure that the buffer has the data of at least the specified size.
*
* @param byteSizeToRead the data size to be read
* @return if the buffer can have the data of the specified size returns true, or if the input source reached an EOF, it returns false.
* @throws IOException
*/
private boolean ensure(int byteSizeToRead) throws IOException {
if(byteSizeToRead == 0)
return true;
if(!ensureBuffer())
return false;
// The buffer contains the data
if(position + byteSizeToRead <= buffer.size()) {
// OK
return true;
}
// When the data is at the boundary
/*
|---(byte size to read) ----|
-- current buffer --|
|--- extra buffer (slice) --|----|
|-------|---------- secondary buffer (slice) ----------------|
*/
// If the byte size to read fits within the extra buffer, use the extraBuffer
MessageBuffer newBuffer = byteSizeToRead <= extraBuffer.size() ? extraBuffer : MessageBuffer.newBuffer(byteSizeToRead);
// Copy the remaining buffer contents to the new buffer
int firstHalfSize = buffer.size() - position;
if(firstHalfSize > 0)
buffer.copyTo(position, newBuffer, 0, firstHalfSize);
// Read the last half contents from the next buffers
int cursor = firstHalfSize;
while(cursor < byteSizeToRead) {
secondaryBuffer = takeNextBuffer();
if(secondaryBuffer == null)
return false; // No more buffer to read
// Copy the contents from the secondary buffer to the new buffer
int copyLen = Math.min(byteSizeToRead - cursor, secondaryBuffer.size());
secondaryBuffer.copyTo(0, newBuffer, cursor, copyLen);
// Truncate the copied part from the secondaryBuffer
secondaryBuffer = copyLen == secondaryBuffer.size() ? null : secondaryBuffer.slice(copyLen, secondaryBuffer.size()-copyLen);
cursor += copyLen;
}
// Replace the current buffer with the new buffer
totalReadBytes += position;
buffer = byteSizeToRead == newBuffer.size() ? newBuffer : newBuffer.slice(0, byteSizeToRead);
position = 0;
return true;
}
/**
* Returns true true if this unpacker has more elements.
* When this returns true, subsequent call to {@link #getNextFormat()} returns an
* MessageFormat instance. If false, {@link #getNextFormat()} will throw an EOFException.
*
* @return true if this unpacker has more elements to read
*/
public boolean hasNext() throws IOException {
return ensure(1);
}
/**
* Returns the next MessageFormat type. This method should be called after {@link #hasNext()} returns true.
* If {@link #hasNext()} returns false, calling this method throws {@link java.io.EOFException}.
*
* This method does not proceed the internal cursor.
* @return the next MessageFormat
* @throws IOException when failed to read the input data.
* @throws EOFException when the end of file reached, i.e. {@link #hasNext()} == false.
*/
public MessageFormat getNextFormat() throws IOException {
byte b = lookAhead();
return MessageFormat.valueOf(b);
}
/**
* Look-ahead a byte value at the current cursor position.
* This method does not proceed the cursor.
*
* @return
* @throws IOException
*/
private byte lookAhead() throws IOException {
if(ensure(1))
return buffer.getByte(position);
else
throw new EOFException();
}
/**
* Get the head byte value and proceeds the cursor by 1
*/
private byte consume() throws IOException {
byte b = lookAhead();
position += 1;
return b;
}
/**
* Proceeds the cursor by the specified byte length
*/
private void consume(int numBytes) throws IOException {
assert(numBytes >= 0);
// If position + numBytes becomes negative, it indicates an overflow from Integer.MAX_VALUE.
if(position + numBytes < 0) {
ensureBuffer();
}
position += numBytes;
}
/**
* Read a byte value at the cursor and proceed the cursor.
*
* @return
* @throws IOException
*/
private byte readByte() throws IOException {
if(!ensure(1)) {
throw new EOFException("insufficient data length for reading byte value");
}
byte b = buffer.getByte(position);
consume(1);
return b;
}
private short readShort() throws IOException {
if(!ensure(2)) {
throw new EOFException("insufficient data length for reading short value");
}
short s = buffer.getShort(position);
consume(2);
return s;
}
private int readInt() throws IOException {
if(!ensure(4)) {
throw new EOFException("insufficient data length for reading int value");
}
int i = buffer.getInt(position);
consume(4);
return i;
}
private float readFloat() throws IOException {
if(!ensure(4)) {
throw new EOFException("insufficient data length for reading float value");
}
float f = buffer.getFloat(position);
consume(4);
return f;
}
private long readLong() throws IOException {
if(!ensure(8)) {
throw new EOFException("insufficient data length for reading long value");
}
long l = buffer.getLong(position);
consume(8);
return l;
}
private double readDouble() throws IOException {
if(!ensure(8)) {
throw new EOFException("insufficient data length for reading double value");
}
double d = buffer.getDouble(position);
consume(8);
return d;
}
/**
* Skip reading the specified number of bytes. Use this method only if you know skipping data is safe.
* For simply skipping the next value, use {@link #skipValue()}.
*
* @param numBytes
* @throws IOException
*/
public void skipBytes(int numBytes) throws IOException {
checkArgument(numBytes >= 0, "skip length must be >= 0: " + numBytes);
consume(numBytes);
}
/**
* Skip the next value, then move the cursor at the end of the value
*
* @throws IOException
*/
public void skipValue() throws IOException {
int remainingValues = 1;
while(remainingValues > 0) {
if(reachedEOF) {
throw new EOFException();
}
MessageFormat f = getNextFormat();
byte b = consume();
switch(f) {
case POSFIXINT:
case NEGFIXINT:
case BOOLEAN:
case NIL:
break;
case FIXMAP: {
int mapLen = b & 0x0f;
remainingValues += mapLen * 2;
break;
}
case FIXARRAY: {
int arrayLen = b & 0x0f;
remainingValues += arrayLen;
break;
}
case FIXSTR: {
int strLen = b & 0x1f;
consume(strLen);
break;
}
case INT8:
case UINT8:
consume(1);
break;
case INT16:
case UINT16:
consume(2);
break;
case INT32:
case UINT32:
case FLOAT32:
consume(4);
break;
case INT64:
case UINT64:
case FLOAT64:
consume(8);
break;
case BIN8:
case STR8:
consume(readNextLength8());
break;
case BIN16:
case STR16:
consume(readNextLength16());
break;
case BIN32:
case STR32:
consume(readNextLength32());
break;
case FIXEXT1:
consume(2);
break;
case FIXEXT2:
consume(3);
break;
case FIXEXT4:
consume(5);
break;
case FIXEXT8:
consume(9);
break;
case FIXEXT16:
consume(17);
break;
case EXT8:
consume(readNextLength8() + 1);
break;
case EXT16:
consume(readNextLength16() + 1);
break;
case EXT32:
consume(readNextLength32() + 1);
break;
case ARRAY16:
remainingValues += readNextLength16();
consume(2);
break;
case ARRAY32:
remainingValues += readNextLength32();
consume(4);
break;
case MAP16:
remainingValues += readNextLength16() * 2;
consume(2);
break;
case MAP32:
remainingValues += readNextLength32() * 2; // TODO check int overflow
consume(2);
break;
case NEVER_USED:
throw new MessageFormatException(String.format("unknown code: %02x is found", b));
}
remainingValues--;
}
}
/**
* Create an exception for the case when an unexpected byte value is read
*
* @param expected
* @param b
* @return
* @throws MessageFormatException
*/
private static MessageTypeException unexpected(String expected, byte b)
throws MessageTypeException {
ValueType type = ValueType.valueOf(b);
return new MessageTypeException(String.format("Expected %s, but got %s (%02x)", expected, type.toTypeName(), b));
}
public MessageFormat unpackValue(ValueHolder holder) throws IOException {
MessageFormat mf = getNextFormat();
switch(mf.getValueType()) {
case NIL:
unpackNil();
holder.setNil();
break;
case BOOLEAN:
holder.setBoolean(unpackBoolean());
break;
case INTEGER: {
unpackInteger(holder.getIntegerHolder());
holder.setToInteger();
break;
}
case FLOAT: {
unpackFloat(holder.getFloatHolder());
holder.setToFloat();
break;
}
case STRING:
int strLen = unpackRawStringHeader();
holder.setString(readPayloadAsReference(strLen));
break;
case BINARY: {
int binaryLen = unpackBinaryHeader();
holder.setBinary(readPayloadAsReference(binaryLen));
break;
}
case ARRAY:
holder.prepareArrayCursor(this);
break;
case MAP:
holder.prepareMapCursor(this);
break;
case EXTENDED:
ExtendedTypeHeader extHeader = unpackExtendedTypeHeader();
holder.setExt(extHeader.getType(), readPayloadAsReference(extHeader.getLength()));
break;
}
return mf;
}
public Object unpackNil() throws IOException {
byte b = consume();
if(b == Code.NIL) {
return null;
}
throw unexpected("Nil", b);
}
public boolean unpackBoolean() throws IOException {
byte b = consume();
if(b == Code.FALSE) {
return false;
} else if(b == Code.TRUE) {
return true;
}
throw unexpected("boolean", b);
}
public byte unpackByte() throws IOException {
byte b = consume();
if(Code.isFixInt(b)) {
return b;
}
switch(b) {
case Code.UINT8: // unsigned int 8
byte u8 = readByte();
if(u8 < (byte) 0) {
throw overflowU8(u8);
}
return u8;
case Code.UINT16: // unsigned int 16
short u16 = readShort();
if(u16 < 0 || u16 > Byte.MAX_VALUE) {
throw overflowU16(u16);
}
return (byte) u16;
case Code.UINT32: // unsigned int 32
int u32 = readInt();
if(u32 < 0 || u32 > Byte.MAX_VALUE) {
throw overflowU32(u32);
}
return (byte) u32;
case Code.UINT64: // unsigned int 64
long u64 = readLong();
if(u64 < 0L || u64 > Byte.MAX_VALUE) {
throw overflowU64(u64);
}
return (byte) u64;
case Code.INT8: // signed int 8
byte i8 = readByte();
return i8;
case Code.INT16: // signed int 16
short i16 = readShort();
if(i16 < Byte.MIN_VALUE || i16 > Byte.MAX_VALUE) {
throw overflowI16(i16);
}
return (byte) i16;
case Code.INT32: // signed int 32
int i32 = readInt();
if(i32 < Byte.MIN_VALUE || i32 > Byte.MAX_VALUE) {
throw overflowI32(i32);
}
return (byte) i32;
case Code.INT64: // signed int 64
long i64 = readLong();
if(i64 < Byte.MIN_VALUE || i64 > Byte.MAX_VALUE) {
throw overflowI64(i64);
}
return (byte) i64;
}
throw unexpected("Integer", b);
}
public short unpackShort() throws IOException {
byte b = consume();
if(Code.isFixInt(b)) {
return (short) b;
}
switch(b) {
case Code.UINT8: // unsigned int 8
byte u8 = readByte();
return (short) (u8 & 0xff);
case Code.UINT16: // unsigned int 16
short u16 = readShort();
if(u16 < (short) 0) {
throw overflowU16(u16);
}
return u16;
case Code.UINT32: // unsigned int 32
int u32 = readInt();
if(u32 < 0 || u32 > Short.MAX_VALUE) {
throw overflowU32(u32);
}
return (short) u32;
case Code.UINT64: // unsigned int 64
long u64 = readLong();
if(u64 < 0L || u64 > Short.MAX_VALUE) {
throw overflowU64(u64);
}
return (short) u64;
case Code.INT8: // signed int 8
byte i8 = readByte();
return (short) i8;
case Code.INT16: // signed int 16
short i16 = readShort();
return i16;
case Code.INT32: // signed int 32
int i32 = readInt();
if(i32 < Short.MIN_VALUE || i32 > Short.MAX_VALUE) {
throw overflowI32(i32);
}
return (short) i32;
case Code.INT64: // signed int 64
long i64 = readLong();
if(i64 < Short.MIN_VALUE || i64 > Short.MAX_VALUE) {
throw overflowI64(i64);
}
return (short) i64;
}
throw unexpected("Integer", b);
}
public int unpackInt() throws IOException {
byte b = consume();
if(Code.isFixInt(b)) {
return (int) b;
}
switch(b) {
case Code.UINT8: // unsigned int 8
byte u8 = readByte();
return u8 & 0xff;
case Code.UINT16: // unsigned int 16
short u16 = readShort();
return u16 & 0xffff;
case Code.UINT32: // unsigned int 32
int u32 = readInt();
if(u32 < 0) {
throw overflowU32(u32);
}
return u32;
case Code.UINT64: // unsigned int 64
long u64 = readLong();
if(u64 < 0L || u64 > (long) Integer.MAX_VALUE) {
throw overflowU64(u64);
}
return (int) u64;
case Code.INT8: // signed int 8
byte i8 = readByte();
return i8;
case Code.INT16: // signed int 16
short i16 = readShort();
return i16;
case Code.INT32: // signed int 32
int i32 = readInt();
return i32;
case Code.INT64: // signed int 64
long i64 = readLong();
if(i64 < (long) Integer.MIN_VALUE || i64 > (long) Integer.MAX_VALUE) {
throw overflowI64(i64);
}
return (int) i64;
}
throw unexpected("Integer", b);
}
public long unpackLong() throws IOException {
byte b = consume();
if(Code.isFixInt(b)) {
return (long) b;
}
switch(b) {
case Code.UINT8: // unsigned int 8
byte u8 = readByte();
return (long) (u8 & 0xff);
case Code.UINT16: // unsigned int 16
short u16 = readShort();
return (long) (u16 & 0xffff);
case Code.UINT32: // unsigned int 32
int u32 = readInt();
if(u32 < 0) {
return (long) (u32 & 0x7fffffff) + 0x80000000L;
} else {
return (long) u32;
}
case Code.UINT64: // unsigned int 64
long u64 = readLong();
if(u64 < 0L) {
throw overflowU64(u64);
}
return u64;
case Code.INT8: // signed int 8
byte i8 = readByte();
return (long) i8;
case Code.INT16: // signed int 16
short i16 = readShort();
return (long) i16;
case Code.INT32: // signed int 32
int i32 = readInt();
return (long) i32;
case Code.INT64: // signed int 64
long i64 = readLong();
return i64;
}
throw unexpected("Integer", b);
}
public BigInteger unpackBigInteger() throws IOException {
byte b = consume();
if(Code.isFixInt(b)) {
return BigInteger.valueOf((long) b);
}
switch(b) {
case Code.UINT8: // unsigned int 8
byte u8 = readByte();
return BigInteger.valueOf((long) (u8 & 0xff));
case Code.UINT16: // unsigned int 16
short u16 = readShort();
return BigInteger.valueOf((long) (u16 & 0xffff));
case Code.UINT32: // unsigned int 32
int u32 = readInt();
if(u32 < 0) {
return BigInteger.valueOf((long) (u32 & 0x7fffffff) + 0x80000000L);
} else {
return BigInteger.valueOf((long) u32);
}
case Code.UINT64: // unsigned int 64
long u64 = readLong();
if(u64 < 0L) {
BigInteger bi = BigInteger.valueOf(u64 + Long.MAX_VALUE + 1L).setBit(63);
return bi;
} else {
return BigInteger.valueOf(u64);
}
case Code.INT8: // signed int 8
byte i8 = readByte();
return BigInteger.valueOf((long) i8);
case Code.INT16: // signed int 16
short i16 = readShort();
return BigInteger.valueOf((long) i16);
case Code.INT32: // signed int 32
int i32 = readInt();
return BigInteger.valueOf((long) i32);
case Code.INT64: // signed int 64
long i64 = readLong();
return BigInteger.valueOf(i64);
}
throw unexpected("Integer", b);
}
/**
* Unpack an integer, then store the read value to the given holder
* @param holder an integer holder to which the unpacked integer will be set.
* @throws IOException
*/
public void unpackInteger(IntegerHolder holder) throws IOException {
byte b = consume();
if(Code.isFixInt(b)) {
holder.setByte(b);
return;
}
switch(b) {
case Code.INT8: // signed int 8
holder.setByte(readByte());
break;
case Code.INT16:
holder.setShort(readShort());
break;
case Code.INT32:
holder.setInt(readInt());
break;
case Code.INT64: // signed int 64
holder.setLong(readLong());
break;
case Code.UINT8: // unsigned int 8
byte u8 = readByte();
if(u8 < 0) {
holder.setShort((short) (u8 & 0xFF));
}
else {
holder.setByte(u8);
}
break;
case Code.UINT16: // unsigned int 16
short u16 = readShort();
if(u16 < 0) {
holder.setInt(u16 & 0xFFFF);
}
else {
holder.setShort(u16);
}
break;
case Code.UINT32: // unsigned int 32
int u32 = readInt();
if(u32 < 0) {
holder.setLong((long) (u32 & 0x7fffffff) + 0x80000000L);
} else {
holder.setInt(u32);
}
break;
case Code.UINT64: // unsigned int 64
long u64 = readLong();
if(u64 < 0L) {
holder.setBigInteger(BigInteger.valueOf(u64 + Long.MAX_VALUE + 1L).setBit(63));
} else {
holder.setLong(u64);
}
break;
default:
throw unexpected("Integer", b);
}
}
public float unpackFloat() throws IOException {
byte b = consume();
switch(b) {
case Code.FLOAT32: // float
float fv = readFloat();
return fv;
case Code.FLOAT64: // double
double dv = readDouble();
return (float) dv;
}
throw unexpected("Float", b);
}
public double unpackDouble() throws IOException {
byte b = consume();
switch(b) {
case Code.FLOAT32: // float
float fv = readFloat();
return (double) fv;
case Code.FLOAT64: // double
double dv = readDouble();
return dv;
}
throw unexpected("Float", b);
}
public void unpackFloat(ValueHolder holder) throws IOException {
unpackFloat(holder.getFloatHolder());
}
public void unpackFloat(FloatHolder holder) throws IOException {
byte b = consume();
switch(b) {
case Code.FLOAT32: // float
float fv = readFloat();
holder.setFloat(fv);
break;
case Code.FLOAT64: // double
double dv = readDouble();
holder.setDouble(dv);
break;
default:
throw unexpected("Float", b);
}
}
private final static String EMPTY_STRING = "";
public String unpackString() throws IOException {
int strLen = unpackRawStringHeader();
if(strLen > 0) {
if(strLen > config.getMaxUnpackStringSize()) {
throw new MessageSizeException(String.format("cannot unpack a String of size larger than %,d: %,d", config.getMaxUnpackStringSize(), strLen), strLen);
}
prepareDecoder();
assert(decoder != null);
decoder.reset();
try {
int cursor = 0;
decodeBuffer.clear();
StringBuilder sb = new StringBuilder();
while(cursor < strLen) {
if (!ensure(strLen))
throw new EOFException();
int readLen = Math.min(buffer.size() - position, strLen-cursor);
ByteBuffer bb = buffer.toByteBuffer(position, readLen);
while(bb.hasRemaining()) {
boolean endOfInput = (cursor + readLen) >= strLen;
CoderResult cr = decoder.decode(bb, decodeBuffer, endOfInput);
if(endOfInput && cr.isUnderflow())
cr = decoder.flush(decodeBuffer);
if(cr.isOverflow()) {
// The output CharBuffer has insufficient space
readLen = bb.limit() - bb.remaining();
decoder.reset();
}
if(cr.isUnderflow() && bb.hasRemaining()) {
// input buffer doesn't have enough bytes for multi bytes characters
if(config.getActionOnMalFormedInput() == CodingErrorAction.REPORT) {
throw new MalformedInputException(strLen);
}
// trash truncated bytes
while (bb.hasRemaining())
bb.get();
}
if(cr.isError()) {
if((cr.isMalformed() && config.getActionOnMalFormedInput() == CodingErrorAction.REPORT) ||
(cr.isUnmappable() && config.getActionOnUnmappableCharacter() == CodingErrorAction.REPORT)) {
cr.throwException();
}
}
decodeBuffer.flip();
sb.append(decodeBuffer);
decodeBuffer.clear();
cursor += readLen;
consume(readLen);
}
}
return sb.toString();
}
catch(CharacterCodingException e) {
throw new MessageStringCodingException(e);
}
} else
return EMPTY_STRING;
}
public int unpackArrayHeader() throws IOException {
byte b = consume();
if(Code.isFixedArray(b)) { // fixarray
return b & 0x0f;
}
switch(b) {
case Code.ARRAY16: // array 16
return readNextLength16();
case Code.ARRAY32: // array 32
return readNextLength32();
}
throw unexpected("Array", b);
}
public int unpackMapHeader() throws IOException {
byte b = consume();
if(Code.isFixedMap(b)) { // fixmap
return b & 0x0f;
}
switch(b) {
case Code.MAP16: // map 16
return readNextLength16();
case Code.MAP32: // map 32
return readNextLength32();
}
throw unexpected("Map", b);
}
public ExtendedTypeHeader unpackExtendedTypeHeader() throws IOException {
byte b = consume();
switch(b) {
case Code.FIXEXT1:
return new ExtendedTypeHeader(1, readByte());
case Code.FIXEXT2:
return new ExtendedTypeHeader(2, readByte());
case Code.FIXEXT4:
return new ExtendedTypeHeader(4, readByte());
case Code.FIXEXT8:
return new ExtendedTypeHeader(8, readByte());
case Code.FIXEXT16:
return new ExtendedTypeHeader(16, readByte());
case Code.EXT8: {
int len = readNextLength8();
int t = readByte();
return new ExtendedTypeHeader(len, t);
}
case Code.EXT16: {
int len = readNextLength16();
int t = readByte();
return new ExtendedTypeHeader(len, t);
}
case Code.EXT32: {
int len = readNextLength32();
int t = readByte();
return new ExtendedTypeHeader(len, t);
}
}
throw unexpected("Ext", b);
}
private int readStringHeader(byte b) throws IOException {
switch(b) {
case Code.STR8: // str 8
return readNextLength8();
case Code.STR16: // str 16
return readNextLength16();
case Code.STR32: // str 32
return readNextLength32();
default:
return -1;
}
}
private int readBinaryHeader(byte b) throws IOException {
switch(b) {
case Code.BIN8: // bin 8
return readNextLength8();
case Code.BIN16: // bin 16
return readNextLength16();
case Code.BIN32: // bin 32
return readNextLength32();
default:
return -1;
}
}
public int unpackRawStringHeader() throws IOException {
byte b = consume();
if(Code.isFixedRaw(b)) { // FixRaw
return b & 0x1f;
}
int len = readStringHeader(b);
if(len >= 0)
return len;
if(config.isReadBinaryAsString()){
len = readBinaryHeader(b);
if(len >= 0)
return len;
}
throw unexpected("String", b);
}
public int unpackBinaryHeader() throws IOException {
byte b = consume();
if(Code.isFixedRaw(b)) { // FixRaw
return b & 0x1f;
}
int len = readBinaryHeader(b);
if(len >= 0)
return len;
if(config.isReadStringAsBinary()) {
len = readStringHeader(b);
if(len >= 0)
return len;
}
throw unexpected("Binary", b);
}
// TODO returns a buffer reference to the payload (zero-copy)
public void readPayload(ByteBuffer dst) throws IOException {
while(dst.remaining() > 0) {
if(!ensureBuffer())
throw new EOFException();
int l = Math.min(buffer.size() - position, dst.remaining());
buffer.getBytes(position, l, dst);
consume(l);
}
}
public void readPayload(byte[] dst) throws IOException {
readPayload(dst, 0, dst.length);
}
/**
* Read up to len bytes of data into the destination array
*
* @param dst the buffer into which the data is read
* @param off the offset in the dst array
* @param len the number of bytes to read
* @throws IOException
*/
public void readPayload(byte[] dst, int off, int len) throws IOException {
int writtenLen = 0;
while(writtenLen < len) {
if(!ensureBuffer())
throw new EOFException();
int l = Math.min(buffer.size() - position, len - writtenLen);
buffer.getBytes(position, dst, off + writtenLen, l);
consume(l);
writtenLen += l;
}
}
public MessageBuffer readPayloadAsReference(int length) throws IOException {
checkArgument(length >= 0);
if(!ensure(length))
throw new EOFException();
MessageBuffer ref = buffer.slice(position, length);
position += length;
return ref;
}
private int readNextLength8() throws IOException {
byte u8 = readByte();
return u8 & 0xff;
}
private int readNextLength16() throws IOException {
short u16 = readShort();
return u16 & 0xffff;
}
private int readNextLength32() throws IOException {
int u32 = readInt();
if(u32 < 0) {
throw overflowU32Size(u32);
}
return u32;
}
@Override
public void close() throws IOException {
in.close();
}
private static MessageIntegerOverflowException overflowU8(byte u8) {
BigInteger bi = BigInteger.valueOf((long) (u8 & 0xff));
return new MessageIntegerOverflowException(bi);
}
private static MessageIntegerOverflowException overflowU16(short u16) {
BigInteger bi = BigInteger.valueOf((long) (u16 & 0xffff));
return new MessageIntegerOverflowException(bi);
}
private static MessageIntegerOverflowException overflowU32(int u32) {
BigInteger bi = BigInteger.valueOf((long) (u32 & 0x7fffffff) + 0x80000000L);
return new MessageIntegerOverflowException(bi);
}
private static MessageIntegerOverflowException overflowU64(long u64) {
BigInteger bi = BigInteger.valueOf(u64 + Long.MAX_VALUE + 1L).setBit(63);
return new MessageIntegerOverflowException(bi);
}
private static MessageIntegerOverflowException overflowI16(short i16) {
BigInteger bi = BigInteger.valueOf((long) i16);
return new MessageIntegerOverflowException(bi);
}
private static MessageIntegerOverflowException overflowI32(int i32) {
BigInteger bi = BigInteger.valueOf((long) i32);
return new MessageIntegerOverflowException(bi);
}
private static MessageIntegerOverflowException overflowI64(long i64) {
BigInteger bi = BigInteger.valueOf(i64);
return new MessageIntegerOverflowException(bi);
}
private static MessageSizeException overflowU32Size(int u32) {
long lv = (long) (u32 & 0x7fffffff) + 0x80000000L;
return new MessageSizeException(lv);
}
}