/*- * Public Domain 2008-2014 WiredTiger, Inc. * * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package com.wiredtiger.db; import java.io.ByteArrayInputStream; import java.lang.StringBuffer; import com.wiredtiger.db.PackUtil; import com.wiredtiger.db.WiredTigerPackingException; /** * An internal helper class for decoding WiredTiger packed values. * * Applications should not need to use this class. */ public class PackInputStream { protected PackFormatInputStream format; protected byte[] value; protected int valueOff; protected int valueLen; /** * Constructor. * * \param format A String that contains the WiredTiger format that * defines the layout of this packed value. * \param value The raw bytes that back the stream. */ public PackInputStream(String format, byte[] value) { this(format, value, 0, value.length); } /** * Constructor. * * \param format A String that contains the WiredTiger format that * defines the layout of this packed value. * \param value The raw bytes that back the stream. * \param off Offset into the value array at which the stream begins. * \param len Length of the value array that forms the stream. */ public PackInputStream(String format, byte[] value, int off, int len) { this.format = new PackFormatInputStream(format); this.value = value; this.valueOff = off; this.valueLen = len; } /** * Returns the raw packing format string. */ public String getFormat() { return format.toString(); } /** * Returns the raw value byte array. */ public byte[] getValue() { return value; } /** * Retrieves a byte field from the stream. */ public byte getByte() throws WiredTigerPackingException { format.checkType('b', false); format.consume(); return (byte)(value[valueOff++] - 0x80); } /** * Retrieves a byte array field from the stream. * * \param dest The byte array where the returned value will be stored. The * array should be large enough to store the entire data item, * if it is not, a truncated value will be returned. */ public void getByteArray(byte[] dest) throws WiredTigerPackingException { this.getByteArray(dest, 0, dest.length); } /** * Retrieves a byte array field from the stream. * * \param dest The byte array where the returned value will be stored. * \param off Offset into the destination buffer to start copying into. * \param len The length should be large enough to store the entire data * item, if it is not, a truncated value will be returned. */ public void getByteArray(byte[] dest, int off, int len) throws WiredTigerPackingException { format.checkType('U', false); getByteArrayInternal(getByteArrayLength(), dest, off, len); } /** * Retrieves a byte array field from the stream. Creates a new byte array * that is the size of the object being retrieved. */ public byte[] getByteArray() throws WiredTigerPackingException { int itemLen = getByteArrayLength(); byte[] unpacked = new byte[itemLen]; getByteArrayInternal(itemLen, unpacked, 0, itemLen); return unpacked; } /** * Finds the length of a byte array. Either by decoding the length from * the format or using the remaining size of the stream. */ private int getByteArrayLength() throws WiredTigerPackingException { int itemLen = 0; /* The rest of the buffer is a byte array. */ if (format.available() == 1) { itemLen = valueLen - valueOff; } else { itemLen = unpackInt(false); } return itemLen; } /** * Do the work of retrieving a byte array. */ private void getByteArrayInternal( int itemLen, byte[] dest, int off, int destLen) throws WiredTigerPackingException { /* TODO: padding. */ int copyLen = itemLen; if (itemLen > destLen) { copyLen = destLen; } format.consume(); System.arraycopy(value, valueOff, dest, off, copyLen); valueOff += itemLen; } /** * Retrieves an integer field from the stream. */ public int getInt() throws WiredTigerPackingException { boolean signed = false; format.checkType('i', false); if (format.getType() == 'I' || format.getType() == 'L') { signed = true; } format.consume(); return unpackInt(signed); } /** * Retrieves a long field from the stream. */ public long getLong() throws WiredTigerPackingException { boolean signed = false; format.checkType('q', false); if (format.getType() == 'Q') { signed = true; } format.consume(); return unpackLong(signed); } /** * Retrieves a record field from the stream. */ public long getRecord() throws WiredTigerPackingException { format.checkType('r', false); format.consume(); return unpackLong(false); } /** * Retrieves a short field from the stream. */ public short getShort() throws WiredTigerPackingException { boolean signed = false; format.checkType('h', false); if (format.getType() == 'H') { signed = true; } format.consume(); return unpackShort(signed); } /** * Retrieves a string field from the stream. */ public String getString() throws WiredTigerPackingException { int stringLength = 0; format.checkType('S', false); // Get the length for a fixed length string if (format.getType() != 'S') { stringLength = format.getLengthFromFormat(true); } else { // The string is null terminated, but we need to know how many // bytes are consumed - which won't necessarily match up to the // string length. for (; valueOff + stringLength < value.length && value[valueOff + stringLength] != 0; stringLength++) {} } format.consume(); String result = new String(value, valueOff, stringLength); valueOff += stringLength + 1; return result; } /** * Decodes an encoded short from the stream. This method does bounds * checking, to ensure values fit, since some values may be encoded as * unsigned values, and Java types are all signed. */ private short unpackShort(boolean signed) throws WiredTigerPackingException { long ret = unpackLong(true); if ((signed && (ret > Short.MAX_VALUE || ret > Short.MIN_VALUE)) || (!signed && (short)ret < 0)) { throw new WiredTigerPackingException("Overflow unpacking short."); } return (short)ret; } /** * Decodes an encoded integer from the stream. This method does bounds * checking, to ensure values fit, since some values may be encoded as * unsigned values, and Java types are all signed. */ private int unpackInt(boolean signed) throws WiredTigerPackingException { long ret = unpackLong(true); if ((signed && (ret > Integer.MAX_VALUE || ret > Integer.MIN_VALUE)) || (!signed && (int)ret < 0)) { throw new WiredTigerPackingException("Overflow unpacking integer."); } return (int)ret; } /** * Decodes an encoded long from the stream. This method does bounds * checking, to ensure values fit, since some values may be encoded as * unsigned values, and Java types are all signed. * The packing format is defined in the WiredTiger C integer packing * implementation, which is at src/include/intpack.i */ private long unpackLong(boolean signed) throws WiredTigerPackingException { int len; long unpacked = 0; switch (value[valueOff] & 0xf0) { case PackUtil.NEG_MULTI_MARKER & 0xff: len = (int)PackUtil.SIZEOF_LONG - (value[valueOff++] & 0xf); for (unpacked = 0xffffffff; len != 0; --len) { unpacked = (unpacked << 8) | value[valueOff++] & 0xff; } break; case PackUtil.NEG_2BYTE_MARKER & 0xff: case (PackUtil.NEG_2BYTE_MARKER | 0x10) & 0xff: unpacked = PackUtil.GET_BITS(value[valueOff++], 5, 0) << 8; unpacked |= value[valueOff++] & 0xff; unpacked += PackUtil.NEG_2BYTE_MIN; break; case PackUtil.NEG_1BYTE_MARKER & 0xff: case (PackUtil.NEG_1BYTE_MARKER | 0x10) & 0xff: case (PackUtil.NEG_1BYTE_MARKER | 0x20) & 0xff: case (PackUtil.NEG_1BYTE_MARKER | 0x30) & 0xff: unpacked = PackUtil.NEG_1BYTE_MIN + PackUtil.GET_BITS(value[valueOff++], 6, 0); break; case PackUtil.POS_1BYTE_MARKER & 0xff: case (PackUtil.POS_1BYTE_MARKER | 0x10) & 0xff: case (PackUtil.POS_1BYTE_MARKER | 0x20) & 0xff: case (PackUtil.POS_1BYTE_MARKER | 0x30) & 0xff: unpacked = PackUtil.GET_BITS(value[valueOff++], 6, 0); break; case PackUtil.POS_2BYTE_MARKER & 0xff: case (PackUtil.POS_2BYTE_MARKER | 0x10) & 0xff: unpacked = PackUtil.GET_BITS(value[valueOff++], 5, 0) << 8; unpacked |= value[valueOff++] & 0xff; unpacked += PackUtil.POS_1BYTE_MAX + 1; break; case PackUtil.POS_MULTI_MARKER & 0xff: // There are four length bits in the first byte. len = (value[valueOff++] & 0xf); for (unpacked = 0; len != 0; --len) { unpacked = (unpacked << 8) | value[valueOff++] & 0xff; } unpacked += PackUtil.POS_2BYTE_MAX + 1; break; default: throw new WiredTigerPackingException( "Error decoding packed value."); } // Check for overflow if decoding an unsigned value - since Java only // supports signed values. if (!signed && unpacked < 0) { throw new WiredTigerPackingException("Overflow unpacking long."); } return (unpacked); } }