// Copyright (c) 2007-Present Pivotal Software, Inc. All rights reserved. // // This software, the RabbitMQ Java client library, is triple-licensed under the // Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2 // ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see // LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, // please see LICENSE-APACHE2. // // This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, // either express or implied. See the LICENSE file for specific language governing // rights and limitations of this software. // // If you have any questions regarding licensing, please contact us at // info@rabbitmq.com. package com.rabbitmq.client.impl; import java.io.DataInputStream; import java.io.IOException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.List; import java.util.ArrayList; import com.rabbitmq.client.LongString; import com.rabbitmq.client.MalformedFrameException; /** * Helper class to read AMQP wire-protocol encoded values. */ public class ValueReader { private static final long INT_MASK = 0xffffffffL; /** * Protected API - Cast an int to a long without extending the * sign bit of the int out into the high half of the long. */ private static long unsignedExtend(int value) { long extended = value; return extended & INT_MASK; } /** The stream we are reading from. */ private final DataInputStream in; /** * Construct a MethodArgumentReader streaming over the given DataInputStream. */ public ValueReader(DataInputStream in) { this.in = in; } /** Convenience method - reads a short string from a DataInput * Stream. */ private static String readShortstr(DataInputStream in) throws IOException { byte [] b = new byte[in.readUnsignedByte()]; in.readFully(b); return new String(b, "utf-8"); } /** Public API - reads a short string. */ public final String readShortstr() throws IOException { return readShortstr(this.in); } /** Convenience method - reads a 32-bit-length-prefix * byte vector from a DataInputStream. */ private static byte[] readBytes(final DataInputStream in) throws IOException { final long contentLength = unsignedExtend(in.readInt()); if(contentLength < Integer.MAX_VALUE) { final byte [] buffer = new byte[(int)contentLength]; in.readFully(buffer); return buffer; } else { throw new UnsupportedOperationException ("Very long byte vectors and strings not currently supported"); } } /** Convenience method - reads a long string argument * from a DataInputStream. */ private static LongString readLongstr(final DataInputStream in) throws IOException { return LongStringHelper.asLongString(readBytes(in)); } /** Public API - reads a long string. */ public final LongString readLongstr() throws IOException { return readLongstr(this.in); } /** Public API - reads a short integer. */ public final int readShort() throws IOException { return in.readUnsignedShort(); } /** Public API - reads an integer. */ public final int readLong() throws IOException { return in.readInt(); } /** Public API - reads a long integer. */ public final long readLonglong() throws IOException { return in.readLong(); } /** * Reads a table argument from a given stream. Also * called by {@link ContentHeaderPropertyReader}. */ private static Map<String, Object> readTable(DataInputStream in) throws IOException { long tableLength = unsignedExtend(in.readInt()); if (tableLength == 0) return Collections.emptyMap(); Map<String, Object> table = new HashMap<String, Object>(); DataInputStream tableIn = new DataInputStream (new TruncatedInputStream(in, tableLength)); while(tableIn.available() > 0) { String name = readShortstr(tableIn); Object value = readFieldValue(tableIn); if(!table.containsKey(name)) table.put(name, value); } return table; } private static Object readFieldValue(DataInputStream in) throws IOException { Object value = null; switch(in.readUnsignedByte()) { case 'S': value = readLongstr(in); break; case 'I': value = in.readInt(); break; case 'D': int scale = in.readUnsignedByte(); byte [] unscaled = new byte[4]; in.readFully(unscaled); value = new BigDecimal(new BigInteger(unscaled), scale); break; case 'T': value = readTimestamp(in); break; case 'F': value = readTable(in); break; case 'A': value = readArray(in); break; case 'b': value = in.readByte(); break; case 'd': value = in.readDouble(); break; case 'f': value = in.readFloat(); break; case 'l': value = in.readLong(); break; case 's': value = in.readShort(); break; case 't': value = in.readBoolean(); break; case 'x': value = readBytes(in); break; case 'V': value = null; break; default: throw new MalformedFrameException ("Unrecognised type in table"); } return value; } /** Read a field-array */ private static List<Object> readArray(DataInputStream in) throws IOException { long length = unsignedExtend(in.readInt()); DataInputStream arrayIn = new DataInputStream (new TruncatedInputStream(in, length)); List<Object> array = new ArrayList<Object>(); while(arrayIn.available() > 0) { Object value = readFieldValue(arrayIn); array.add(value); } return array; } /** Public API - reads a table. */ public final Map<String, Object> readTable() throws IOException { return readTable(this.in); } /** Public API - reads an octet. */ public final int readOctet() throws IOException { return in.readUnsignedByte(); } /** Convenience method - reads a timestamp argument from the DataInputStream. */ private static Date readTimestamp(DataInputStream in) throws IOException { return new Date(in.readLong()*1000); } /** Public API - reads an timestamp. */ public final Date readTimestamp() throws IOException { return readTimestamp(this.in); } }