/* * JACOB - CBOR implementation in Java. * * (C) Copyright - 2013 - J.W. Janssen */ package jacob; import static jacob.CborConstants.*; import static jacob.CborType.*; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; /** * Provides a decoder capable of handling CBOR encoded data from a {@link InputStream}. */ public class CborDecoder { protected final PushbackInputStream m_is; protected final int total_avail; static int getAvailableBytes(PushbackInputStream is) { try { return is.available(); } catch (IOException e) { return -1; } } /** * Creates a new {@link CborDecoder} instance. * * @param is the actual input stream to read the CBOR-encoded data from, cannot be null. */ public CborDecoder(InputStream is) { if (is == null) { throw new IllegalArgumentException("InputStream cannot be null!"); } m_is = (is instanceof PushbackInputStream) ? (PushbackInputStream) is : new PushbackInputStream(is); total_avail = getAvailableBytes(m_is); } private static void fail(String msg, Object... args) throws IOException { throw new IOException(String.format(msg, args)); } private static String lengthToString(int len) { return (len < 0) ? "no payload" : (len == ONE_BYTE) ? "one byte" : (len == TWO_BYTES) ? "two bytes" : (len == FOUR_BYTES) ? "four bytes" : (len == EIGHT_BYTES) ? "eight bytes" : "(unknown)"; } public long getPos() { try { return total_avail-m_is.available(); } catch (IOException e) { return -1; } } /** * Peeks in the input stream for the upcoming type. * * @return the upcoming type in the stream, or null in case of an end-of-stream. * @throws IOException in case of I/O problems reading the CBOR-type from the underlying input stream. */ public CborType peekType() throws IOException { int p = m_is.read(); if (p < 0) { // EOF, nothing to peek at... return null; } m_is.unread(p); return valueOf(p); } /** * Prolog to reading an array value in CBOR format. * * @return the number of elements in the array to read, or -1 in case of infinite-length arrays. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public long readArrayLength() throws IOException { return readMajorTypeWithSize(TYPE_ARRAY); } /** * Reads a boolean value in CBOR format. * * @return the read boolean. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public boolean readBoolean() throws IOException { int b = readMajorType(TYPE_FLOAT_SIMPLE); if (b != FALSE && b != TRUE) { fail("Unexpected boolean value: %d!", b); } return b == TRUE; } /** * Reads a "break"/stop value in CBOR format. * * @return always null. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public Object readBreak() throws IOException { readMajorTypeExact(TYPE_FLOAT_SIMPLE, BREAK); return null; } /** * Reads a byte string value in CBOR format. * * @return the read byte string, never null. In case the encoded string has a length of 0, an empty string is returned. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public byte[] readByteString() throws IOException { long len = readMajorTypeWithSize(TYPE_BYTE_STRING); if (len < 0) { fail("Infinite-length byte strings not supported!"); } if (len > Integer.MAX_VALUE) { fail("String length too long!"); } return readFully(new byte[(int) len]); } /** * Prolog to reading a byte string value in CBOR format. * * @return the number of bytes in the string to read, or -1 in case of infinite-length strings. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public long readByteStringLength() throws IOException { return readMajorTypeWithSize(TYPE_BYTE_STRING); } /** * Reads a double-precision float value in CBOR format. * * @return the read double value, values from {@link Float#MIN_VALUE} to {@link Float#MAX_VALUE} are supported. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public double readDouble() throws IOException { readMajorTypeExact(TYPE_FLOAT_SIMPLE, DOUBLE_PRECISION_FLOAT); return Double.longBitsToDouble(readUInt64()); } /** * Reads a single-precision float value in CBOR format. * * @return the read float value, values from {@link Float#MIN_VALUE} to {@link Float#MAX_VALUE} are supported. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public float readFloat() throws IOException { readMajorTypeExact(TYPE_FLOAT_SIMPLE, SINGLE_PRECISION_FLOAT); return Float.intBitsToFloat((int) readUInt32()); } /** * Reads a half-precision float value in CBOR format. * * @return the read half-precision float value, values from {@link Float#MIN_VALUE} to {@link Float#MAX_VALUE} are supported. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public double readHalfPrecisionFloat() throws IOException { readMajorTypeExact(TYPE_FLOAT_SIMPLE, HALF_PRECISION_FLOAT); int half = readUInt16(); int exp = (half >> 10) & 0x1f; int mant = half & 0x3ff; double val; if (exp == 0) { val = mant * Math.pow(2, -24); } else if (exp != 31) { val = (mant + 1024) * Math.pow(2, exp - 25); } else if (mant != 0) { val = Double.NaN; } else { val = Double.POSITIVE_INFINITY; } return ((half & 0x8000) == 0) ? val : -val; } /** * Reads a signed or unsigned integer value in CBOR format. * * @return the read integer value, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public long readInt() throws IOException { int ib = m_is.read(); // in case of negative integers, extends the sign to all bits; otherwise zero... long ui = expectIntegerType(ib); // in case of negative integers does a ones complement return ui ^ readUInt(ib & 0x1f, false /* breakAllowed */); } /** * Reads a signed or unsigned 16-bit integer value in CBOR format. * * @read the small integer value, values from [-65536..65535] are supported. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying output stream. */ public int readInt16() throws IOException { int ib = m_is.read(); // in case of negative integers, extends the sign to all bits; otherwise zero... long ui = expectIntegerType(ib); // in case of negative integers does a ones complement return (int) (ui ^ readUIntExact(TWO_BYTES, ib & 0x1f)); } /** * Reads a signed or unsigned 32-bit integer value in CBOR format. * * @read the small integer value, values in the range [-4294967296..4294967295] are supported. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying output stream. */ public long readInt32() throws IOException { int ib = m_is.read(); // in case of negative integers, extends the sign to all bits; otherwise zero... long ui = expectIntegerType(ib); // in case of negative integers does a ones complement return ui ^ readUIntExact(FOUR_BYTES, ib & 0x1f); } /** * Reads a signed or unsigned 64-bit integer value in CBOR format. * * @read the small integer value, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying output stream. */ public long readInt64() throws IOException { int ib = m_is.read(); // in case of negative integers, extends the sign to all bits; otherwise zero... long ui = expectIntegerType(ib); // in case of negative integers does a ones complement return ui ^ readUIntExact(EIGHT_BYTES, ib & 0x1f); } /** * Reads a signed or unsigned 8-bit integer value in CBOR format. * * @read the small integer value, values in the range [-256..255] are supported. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying output stream. */ public int readInt8() throws IOException { int ib = m_is.read(); // in case of negative integers, extends the sign to all bits; otherwise zero... long ui = expectIntegerType(ib); // in case of negative integers does a ones complement return (int) (ui ^ readUIntExact(ONE_BYTE, ib & 0x1f)); } /** * Prolog to reading a map of key-value pairs in CBOR format. * * @return the number of entries in the map, >= 0. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public long readMapLength() throws IOException { return readMajorTypeWithSize(TYPE_MAP); } /** * Reads a null-value in CBOR format. * * @return always null. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public Object readNull() throws IOException { readMajorTypeExact(TYPE_FLOAT_SIMPLE, NULL); return null; } /** * Reads a single byte value in CBOR format. * * @return the read byte value. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public byte readSimpleValue() throws IOException { readMajorTypeExact(TYPE_FLOAT_SIMPLE, ONE_BYTE); return (byte) readUInt8(); } /** * Reads a signed or unsigned small (<= 23) integer value in CBOR format. * * @read the small integer value, values in the range [-24..23] are supported. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying output stream. */ public int readSmallInt() throws IOException { int ib = m_is.read(); // in case of negative integers, extends the sign to all bits; otherwise zero... long ui = expectIntegerType(ib); // in case of negative integers does a ones complement return (int) (ui ^ readUIntExact(-1, ib & 0x1f)); } /** * Reads a semantic tag value in CBOR format. * * @return the read tag value. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public long readTag() throws IOException { return readUInt(readMajorType(TYPE_TAG), false /* breakAllowed */); } /** * Reads an UTF-8 encoded string value in CBOR format. * * @return the read UTF-8 encoded string, never null. In case the encoded string has a length of 0, an empty string is returned. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public String readTextString() throws IOException { long len = readMajorTypeWithSize(TYPE_TEXT_STRING); if (len < 0) { fail("Infinite-length text strings not supported!"); } if (len > Integer.MAX_VALUE) { fail("String length too long!"); } return new String(readFully(new byte[(int) len]), "UTF-8"); } /** * Prolog to reading an UTF-8 encoded string value in CBOR format. * * @return the length of the string to read, or -1 in case of infinite-length strings. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public long readTextStringLength() throws IOException { return readMajorTypeWithSize(TYPE_TEXT_STRING); } /** * Reads an undefined value in CBOR format. * * @return always null. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ public Object readUndefined() throws IOException { readMajorTypeExact(TYPE_FLOAT_SIMPLE, UNDEFINED); return null; } /** * Reads the next major type from the underlying input stream, and verifies whether it matches the given expectation. * * @param majorType the expected major type, cannot be null (unchecked). * @return either -1 if the major type was an signed integer, or 0 otherwise. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ protected long expectIntegerType(int ib) throws IOException { int majorType = ((ib & 0xFF) >>> 5); if ((majorType != TYPE_UNSIGNED_INTEGER) && (majorType != TYPE_NEGATIVE_INTEGER)) { fail("Unexpected type: %s, expected type %s or %s!", getName(majorType), getName(TYPE_UNSIGNED_INTEGER), getName(TYPE_NEGATIVE_INTEGER)); } return -majorType; } /** * Reads the next major type from the underlying input stream, and verifies whether it matches the given expectation. * * @param majorType the expected major type, cannot be null (unchecked). * @return the read subtype, or payload, of the read major type. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ protected int readMajorType(int majorType) throws IOException { int ib = m_is.read(); if (majorType != ((ib >>> 5) & 0x07)) { fail("Unexpected type: %s, expected: %s!", getName(ib), getName(majorType)); } return ib & 0x1F; } /** * Reads the next major type from the underlying input stream, and verifies whether it matches the given expectations. * * @param majorType the expected major type, cannot be null (unchecked); * @param subtype the expected subtype. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ protected void readMajorTypeExact(int majorType, int subtype) throws IOException { int st = readMajorType(majorType); if ((st ^ subtype) != 0) { fail("Unexpected subtype: %d, expected: %d!", st, subtype); } } /** * Reads the next major type from the underlying input stream, verifies whether it matches the given expectation, and decodes the payload into a size. * * @param majorType the expected major type, cannot be null (unchecked). * @return the number of succeeding bytes, >= 0, or -1 if an infinite-length type is read. * @throws IOException in case of I/O problems reading the CBOR-encoded value from the underlying input stream. */ protected long readMajorTypeWithSize(int majorType) throws IOException { return readUInt(readMajorType(majorType), true /* breakAllowed */); } /** * Reads an unsigned integer with a given length-indicator. * * @param length the length indicator to use; * @return the read unsigned integer, as long value. * @throws IOException in case of I/O problems reading the unsigned integer from the underlying input stream. */ protected long readUInt(int length, boolean breakAllowed) throws IOException { long result = -1; if (length < ONE_BYTE) { result = length; } else if (length == ONE_BYTE) { result = readUInt8(); } else if (length == TWO_BYTES) { result = readUInt16(); } else if (length == FOUR_BYTES) { result = readUInt32(); } else if (length == EIGHT_BYTES) { result = readUInt64(); } else if (breakAllowed && length == BREAK) { return -1; } if (result < 0) { fail("Not well-formed CBOR integer found, invalid length: %d!", result); } return result; } /** * Reads an unsigned 16-bit integer value * * @return value the read value, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. */ protected int readUInt16() throws IOException { byte[] buf = readFully(new byte[2]); return (buf[0] & 0xFF) << 8 | (buf[1] & 0xFF); } /** * Reads an unsigned 32-bit integer value * * @return value the read value, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. */ protected long readUInt32() throws IOException { byte[] buf = readFully(new byte[4]); return ((buf[0] & 0xFF) << 24 | (buf[1] & 0xFF) << 16 | (buf[2] & 0xFF) << 8 | (buf[3] & 0xFF)) & 0xffffffffL; } /** * Reads an unsigned 64-bit integer value * * @return value the read value, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. */ protected long readUInt64() throws IOException { byte[] buf = readFully(new byte[8]); return (buf[0] & 0xFFL) << 56 | (buf[1] & 0xFFL) << 48 | (buf[2] & 0xFFL) << 40 | (buf[3] & 0xFFL) << 32 | // (buf[4] & 0xFFL) << 24 | (buf[5] & 0xFFL) << 16 | (buf[6] & 0xFFL) << 8 | (buf[7] & 0xFFL); } /** * Reads an unsigned 8-bit integer value * * @return value the read value, values from {@link Long#MIN_VALUE} to {@link Long#MAX_VALUE} are supported. * @throws IOException in case of I/O problems writing the CBOR-encoded value to the underlying output stream. */ protected int readUInt8() throws IOException { return m_is.read() & 0xff; } /** * Reads an unsigned integer with a given length-indicator. * * @param length the length indicator to use; * @return the read unsigned integer, as long value. * @throws IOException in case of I/O problems reading the unsigned integer from the underlying input stream. */ protected long readUIntExact(int expectedLength, int length) throws IOException { if (((expectedLength == -1) && (length >= ONE_BYTE)) || ((expectedLength >= 0) && (length != expectedLength))) { fail("Unexpected payload/length! Expected %s, but got %s.", lengthToString(expectedLength), lengthToString(length)); } return readUInt(length, false /* breakAllowed */); } private byte[] readFully(byte[] buf) throws IOException { int len = buf.length; int n = 0, off = 0; while (n < len) { int count = m_is.read(buf, off + n, len - n); if (count < 0) { throw new EOFException(); } n += count; } return buf; } }