/* * RED5 Open Source Flash Server - http://code.google.com/p/red5/ * * Copyright 2006-2012 by respective authors (see below). All rights reserved. * * 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.red5.io.amf3; import java.io.IOException; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.Vector; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.BeanUtilsBean; import org.apache.commons.beanutils.ConvertUtilsBean; import org.apache.mina.core.buffer.IoBuffer; import org.red5.io.amf.AMF; import org.red5.io.object.DataTypes; import org.red5.io.object.Deserializer; import org.red5.io.utils.ArrayUtils; import org.red5.io.utils.ObjectMap; import org.red5.io.utils.XMLUtils; import org.red5.server.util.ConversionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; /** * Input for Red5 data (AMF3) types * * @author The Red5 Project (red5@osflash.org) * @author Luke Hubbard, Codegent Ltd (luke@codegent.com) * @author Joachim Bauch (jojo@struktur.de) */ public class Input extends org.red5.io.amf.Input implements org.red5.io.object.Input { private static ConvertUtilsBean convertUtilsBean = BeanUtilsBean.getInstance().getConvertUtils(); /** * Holds informations about already deserialized classes. */ protected static class ClassReference { /** Name of the deserialized class. */ protected String className; /** Type of the class. */ protected int type; /** Names of the attributes of the class. */ protected List<String> attributeNames; /** * Create new information about a class. * * @param className class name * @param type type * @param attributeNames attributes */ public ClassReference(String className, int type, List<String> attributeNames) { log.debug("Pending property - className: {} type: {} attributeNames: {}", new Object[] { className, type, attributeNames }); this.className = className; this.type = type; this.attributeNames = attributeNames; } } /** * Dummy class that is stored as reference for objects currently * being deserialized that reference themselves. */ protected static class PendingObject { public PendingObject() { log.debug("PendingObject"); } static final class PendingProperty { Object obj; Class<?> klass; String name; PendingProperty(Object obj, Class<?> klass, String name) { log.debug("Pending property - obj: {} class: {} name: {}", new Object[] { obj, klass, name }); this.obj = obj; this.klass = klass; this.name = name; } } private List<PendingProperty> properties; public void addPendingProperty(Object obj, Class<?> klass, String name) { if (properties == null) { properties = new ArrayList<PendingProperty>(); } properties.add(new PendingProperty(obj, klass, name)); } public void resolveProperties(Object result) { if (properties != null) { for (PendingProperty prop : properties) { try { prop.klass.getField(prop.name).set(prop.obj, result); } catch (Exception e) { try { BeanUtils.setProperty(prop.obj, prop.name, result); } catch (Exception ex) { log.error("Error mapping property: {} ({})", prop.name, result); } } } properties.clear(); } else { // No pending properties return; } } } /** * Class used to collect AMF3 references. * In AMF3 references should be collected through the whole "body" (across several Input objects). */ public static class RefStorage { // informations about previously deserialized classes private List<ClassReference> classReferences = new ArrayList<ClassReference>(); // list of string values found in the input stream private List<String> stringReferences = new ArrayList<String>(); private Map<Integer, Object> refMap = new HashMap<Integer, Object>(4); } /** * Logger */ protected static Logger log = LoggerFactory.getLogger(Input.class); /** * Set to a value above <tt>0</tt> to enforce AMF3 decoding mode. */ private int amf3_mode; /** * Stores references declared in this input of previous ones in the same message body */ private RefStorage refStorage; /** * Creates Input object for AMF3 from byte buffer * * @param buf Byte buffer */ public Input(IoBuffer buf) { super(buf); amf3_mode = 0; refStorage = new RefStorage(); } /** * Creates Input object for AMF3 from byte buffer and initializes references * from passed RefStorage * @param buf buffer * @param refStorage ref storage */ public Input(IoBuffer buf, RefStorage refStorage) { super(buf); this.refStorage = refStorage; this.refMap = refStorage.refMap; amf3_mode = 0; } /** * Force using AMF3 everywhere */ public void enforceAMF3() { amf3_mode++; } /** * Provide access to raw data. * * @return IoBuffer */ protected IoBuffer getBuffer() { return buf; } /** * Reads the data type * * @return byte Data type */ @Override public byte readDataType() { log.trace("readDataType"); byte coreType = AMF3.TYPE_UNDEFINED; if (buf != null) { currentDataType = buf.get(); log.debug("Current data type: {}", currentDataType); if (currentDataType == AMF.TYPE_AMF3_OBJECT) { currentDataType = buf.get(); } else if (amf3_mode == 0) { // AMF0 object return readDataType(currentDataType); } log.debug("Current data type (after amf checks): {}", currentDataType); switch (currentDataType) { case AMF3.TYPE_UNDEFINED: case AMF3.TYPE_NULL: coreType = DataTypes.CORE_NULL; break; case AMF3.TYPE_INTEGER: case AMF3.TYPE_NUMBER: coreType = DataTypes.CORE_NUMBER; break; case AMF3.TYPE_BOOLEAN_TRUE: case AMF3.TYPE_BOOLEAN_FALSE: coreType = DataTypes.CORE_BOOLEAN; break; case AMF3.TYPE_STRING: coreType = DataTypes.CORE_STRING; break; // TODO check XML_SPECIAL case AMF3.TYPE_XML: case AMF3.TYPE_XML_DOCUMENT: coreType = DataTypes.CORE_XML; break; case AMF3.TYPE_OBJECT: coreType = DataTypes.CORE_OBJECT; break; case AMF3.TYPE_ARRAY: // should we map this to list or array? coreType = DataTypes.CORE_ARRAY; break; case AMF3.TYPE_DATE: coreType = DataTypes.CORE_DATE; break; case AMF3.TYPE_BYTEARRAY: coreType = DataTypes.CORE_BYTEARRAY; break; case AMF3.TYPE_VECTOR_INT: coreType = DataTypes.CORE_VECTOR_INT; break; case AMF3.TYPE_VECTOR_UINT: coreType = DataTypes.CORE_VECTOR_UINT; break; case AMF3.TYPE_VECTOR_NUMBER: coreType = DataTypes.CORE_VECTOR_NUMBER; break; case AMF3.TYPE_VECTOR_OBJECT: coreType = DataTypes.CORE_VECTOR_OBJECT; break; default: log.info("Unknown datatype: {}", currentDataType); // End of object, and anything else lets just skip coreType = DataTypes.CORE_SKIP; break; } log.debug("Core type: {}", coreType); } else { log.error("Why is buf null?"); } return coreType; } /** * Reads a null (value) * * @return Object null */ @Override public Object readNull(Type target) { return null; } /** * Reads a boolean * * @return boolean Boolean value */ @Override public Boolean readBoolean(Type target) { return (currentDataType == AMF3.TYPE_BOOLEAN_TRUE) ? Boolean.TRUE : Boolean.FALSE; } /** * Reads a Number * * @return Number Number */ @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public Number readNumber(Type target) { log.debug("readNumber - target: {}", target); Number v; if (currentDataType == AMF3.TYPE_NUMBER) { v = buf.getDouble(); } else { // we are decoding an int v = readAMF3Integer(); } log.debug("readNumber - value: {}", v); if (target instanceof Class && Number.class.isAssignableFrom((Class<?>) target)) { Class cls = (Class) target; if (!cls.isAssignableFrom(v.getClass())) { String value = v.toString(); log.debug("readNumber - value class: {} str: {}", v.getClass(), value); if (value.indexOf(".") > 0) { if (Float.class == v.getClass()) { v = (Number) convertUtilsBean.convert(value, Float.class); } else { v = (Number) convertUtilsBean.convert(value, Double.class); } } else { v = (Number) convertUtilsBean.convert(value, cls); } } } return v; } /** * Reads a string * * @return String String */ @Override public String readString(Type target) { int len = readAMF3Integer(); log.debug("readString - length: {}", len); // get the length of the string (0x03 = 1, 0x05 = 2, 0x07 = 3 etc..) // 0x01 is special and it means "empty" if (len == 1) { // Empty string return ""; } if ((len & 1) == 0) { //if the refs are empty an IndexOutOfBoundsEx will be thrown if (refStorage.stringReferences.isEmpty()) { log.debug("String reference list is empty"); } // Reference return refStorage.stringReferences.get(len >> 1); } len >>= 1; log.debug("readString - new length: {}", len); int limit = buf.limit(); log.debug("readString - limit: {}", limit); final ByteBuffer strBuf = buf.buf(); strBuf.limit(strBuf.position() + len); final String string = AMF.CHARSET.decode(strBuf).toString(); log.debug("String: {}", string); buf.limit(limit); // reset the limit refStorage.stringReferences.add(string); return string; } /** * Reads a string of a set length. This does not use * the string reference table. * * @param length the length of the string * @return String */ public String readString(int length) { log.debug("readString - length: {}", length); int limit = buf.limit(); final ByteBuffer strBuf = buf.buf(); strBuf.limit(strBuf.position() + length); final String string = AMF.CHARSET.decode(strBuf).toString(); log.debug("String: {}", string); buf.limit(limit); //check for null termination byte b = buf.get(); if (b != 0) { buf.position(buf.position() - 1); } return string; } public RefStorage getRefStorage() { return refStorage; } public String getString() { return readString(String.class); } /** * Returns a date * * @return Date Date object */ @Override public Date readDate(Type target) { int ref = readAMF3Integer(); if ((ref & 1) == 0) { // Reference to previously found date return (Date) getReference(ref >> 1); } long ms = (long) buf.getDouble(); Date date = new Date(ms); storeReference(date); return date; } // Array /** * Returns an array * * @return int Length of array */ @SuppressWarnings({ "unchecked", "rawtypes" }) public Object readArray(Deserializer deserializer, Type target) { int count = readAMF3Integer(); log.debug("Count: {} and {} ref {}", new Object[] { count, (count & 1), (count >> 1) }); if ((count & 1) == 0) { //Reference Object ref = getReference(count >> 1); if (ref != null) { return ref; } } count = (count >> 1); String key = readString(String.class); amf3_mode += 1; Object result; if (key.equals("")) { Class<?> nested = Object.class; Class<?> collection = Collection.class; Collection resultCollection; if (target instanceof ParameterizedType) { ParameterizedType t = (ParameterizedType) target; Type[] actualTypeArguments = t.getActualTypeArguments(); if (actualTypeArguments.length == 1) { nested = (Class<?>) actualTypeArguments[0]; } target = t.getRawType(); } if (target instanceof Class) { collection = (Class) target; } if (collection.isArray()) { nested = ArrayUtils.getGenericType(collection.getComponentType()); result = Array.newInstance(nested, count); storeReference(result); for (int i = 0; i < count; i++) { final Object value = deserializer.deserialize(this, nested); Array.set(result, i, value); } } else { if (SortedSet.class.isAssignableFrom(collection)) { resultCollection = new TreeSet(); } else if (Set.class.isAssignableFrom(collection)) { resultCollection = new HashSet(count); } else { resultCollection = new ArrayList(count); } result = resultCollection; storeReference(result); for (int i = 0; i < count; i++) { final Object value = deserializer.deserialize(this, nested); resultCollection.add(value); } } } else { Class<?> k = Object.class; Class<?> v = Object.class; Class<?> collection = Collection.class; if (target instanceof ParameterizedType) { ParameterizedType t = (ParameterizedType) target; Type[] actualTypeArguments = t.getActualTypeArguments(); if (actualTypeArguments.length == 2) { k = (Class<?>) actualTypeArguments[0]; v = (Class<?>) actualTypeArguments[1]; } target = t.getRawType(); } if (target instanceof Class) { collection = (Class) target; } if (SortedMap.class.isAssignableFrom(collection)) { collection = TreeMap.class; } else { collection = HashMap.class; } Map resultMap; try { resultMap = (Map) collection.newInstance(); } catch (Exception e) { resultMap = new HashMap(count); } // associative array storeReference(resultMap); while (!key.equals("")) { final Object value = deserializer.deserialize(this, v); resultMap.put(key, value); key = readString(k); } for (int i = 0; i < count; i++) { final Object value = deserializer.deserialize(this, v); resultMap.put(i, value); } result = resultMap; } amf3_mode -= 1; return result; } public Object readMap(Deserializer deserializer, Type target) { throw new RuntimeException("AMF3 doesn't support maps."); } @SuppressWarnings({ "unchecked", "rawtypes", "serial" }) public Object readObject(Deserializer deserializer, Type target) { int type = readAMF3Integer(); log.debug("Type: {} and {} ref {}", new Object[] { type, (type & 1), (type >> 1) }); if ((type & 1) == 0) { //Reference Object ref = getReference(type >> 1); if (ref != null) { return ref; } byte b = buf.get(); if (b == 7) { log.debug("BEL: {}", b); //7 } else { log.debug("Non-BEL byte: {}", b); log.debug("Extra byte: {}", buf.get()); } } type >>= 1; List<String> attributes = null; String className; Object result = null; boolean inlineClass = (type & 1) == 1; log.debug("Class is in-line? {}", inlineClass); if (!inlineClass) { ClassReference info = refStorage.classReferences.get(type >> 1); className = info.className; attributes = info.attributeNames; type = info.type; if (attributes != null) { type |= attributes.size() << 2; } } else { type >>= 1; className = readString(String.class); log.debug("Type: {} classname: {}", type, className); //check for flex class alias since these wont be detected as externalizable if (classAliases.containsKey(className)) { //make sure type is externalizable type = 1; } else if (className.startsWith("flex")) { //set the attributes for messaging classes if (className.endsWith("CommandMessage")) { attributes = new LinkedList<String>() { { add("timestamp"); add("headers"); add("operation"); add("body"); add("correlationId"); add("messageId"); add("timeToLive"); add("clientId"); add("destination"); } }; } else { log.debug("Attributes for {} were not set", className); } } } amf3_mode += 1; Object instance = newInstance(className); Map<String, Object> properties = null; PendingObject pending = new PendingObject(); int tempRefId = storeReference(pending); log.debug("Object type: {}", (type & 0x03)); switch (type & 0x03) { case AMF3.TYPE_OBJECT_PROPERTY: log.debug("Detected: Object property type"); // Load object properties into map int count = type >> 2; log.debug("Count: {}", count); if (attributes == null) { attributes = new ArrayList<String>(count); for (int i = 0; i < count; i++) { attributes.add(readString(String.class)); } refStorage.classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_PROPERTY, attributes)); } properties = new ObjectMap<String, Object>(); for (int i = 0; i < count; i++) { String name = attributes.get(i); properties.put(name, deserializer.deserialize(this, getPropertyType(instance, name))); } break; case AMF3.TYPE_OBJECT_EXTERNALIZABLE: log.debug("Detected: Externalizable type"); // Use custom class to deserialize the object if ("".equals(className)) { throw new RuntimeException("Classname is required to load an Externalizable object"); } log.debug("Externalizable class: {}", className); if (className.length() == 3) { //check for special DS class aliases className = classAliases.get(className); } result = newInstance(className); if (result == null) { throw new RuntimeException(String.format("Could not instantiate class: %s", className)); } if (!(result instanceof IExternalizable)) { throw new RuntimeException(String.format("Class must implement the IExternalizable interface: %s", className)); } refStorage.classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_EXTERNALIZABLE, null)); storeReference(tempRefId, result); ((IExternalizable) result).readExternal(new DataInput(this, deserializer)); break; case AMF3.TYPE_OBJECT_VALUE: log.debug("Detected: Object value type"); // First, we should read typed (non-dynamic) properties ("sealed traits" according to AMF3 specification). // Property names are stored in the beginning, then values are stored. count = type >> 2; log.debug("Count: {}", count); if (attributes == null) { attributes = new ArrayList<String>(count); for (int i = 0; i < count; i++) { attributes.add(readString(String.class)); } refStorage.classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_VALUE, attributes)); } //use the size of the attributes if we have no count if (count == 0 && attributes != null) { count = attributes.size(); log.debug("Using class attribute size for property count: {}", count); //read the attributes from the stream and log if count doesnt match List<String> tmpAttributes = new ArrayList<String>(count); for (int i = 0; i < count; i++) { tmpAttributes.add(readString(String.class)); } if (count != tmpAttributes.size()) { log.debug("Count and attributes length does not match!"); } refStorage.classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_VALUE, attributes)); } properties = new ObjectMap<String, Object>(); for (String key : attributes) { log.debug("Looking for property: {}", key); Object value = deserializer.deserialize(this, getPropertyType(instance, key)); log.debug("Key: {} Value: {}", key, value); properties.put(key, value); } log.trace("Buffer - position: {} limit: {}", buf.position(), buf.limit()); //no more items to read if we are at the end of the buffer if (buf.position() < buf.limit()) { // Now we should read dynamic properties which are stored as name-value pairs. // Dynamic properties are NOT remembered in 'classReferences'. String key = readString(String.class); while (!"".equals(key)) { Object value = deserializer.deserialize(this, getPropertyType(instance, key)); properties.put(key, value); key = readString(String.class); } } break; default: case AMF3.TYPE_OBJECT_PROXY: log.debug("Detected: Object proxy type"); if ("".equals(className)) { throw new RuntimeException("Classname is required to load an Externalizable object"); } log.debug("Externalizable class: {}", className); result = newInstance(className); if (result == null) { throw new RuntimeException(String.format("Could not instantiate class: %s", className)); } if (!(result instanceof IExternalizable)) { throw new RuntimeException(String.format("Class must implement the IExternalizable interface: %s", className)); } refStorage.classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_PROXY, null)); storeReference(tempRefId, result); ((IExternalizable) result).readExternal(new DataInput(this, deserializer)); } amf3_mode -= 1; if (result == null) { // Create result object based on classname if ("".equals(className)) { // "anonymous" object, load as Map // Resolve circular references for (Map.Entry<String, Object> entry : properties.entrySet()) { if (entry.getValue() == pending) { entry.setValue(properties); } } storeReference(tempRefId, properties); result = properties; } else if ("RecordSet".equals(className)) { // TODO: how are RecordSet objects encoded? throw new RuntimeException("Objects of type RecordSet not supported yet."); } else if ("RecordSetPage".equals(className)) { // TODO: how are RecordSetPage objects encoded? throw new RuntimeException("Objects of type RecordSetPage not supported yet."); } else { // Apply properties to object result = newInstance(className); if (result != null) { storeReference(tempRefId, result); Class resultClass = result.getClass(); pending.resolveProperties(result); for (Map.Entry<String, Object> entry : properties.entrySet()) { // Resolve circular references final String key = entry.getKey(); Object value = entry.getValue(); if (value == pending) { value = result; } if (value instanceof PendingObject) { // Defer setting of value until real object is created ((PendingObject) value).addPendingProperty(result, resultClass, key); continue; } if (value != null) { try { final Field field = resultClass.getField(key); final Class fieldType = field.getType(); if (!fieldType.isAssignableFrom(value.getClass())) { value = ConversionUtils.convert(value, fieldType); } else if (value instanceof Enum) { value = Enum.valueOf(fieldType, value.toString()); } field.set(result, value); } catch (Exception e) { try { BeanUtils.setProperty(result, key, value); } catch (IllegalAccessException ex) { log.warn("Error mapping key: {} value: {}", key, value); } catch (InvocationTargetException ex) { log.warn("Error mapping key: {} value: {}", key, value); } } } else { log.debug("Skipping null property: {}", key); } } } // else fall through } } return result; } /** * Read ByteArray object. * * @return ByteArray object */ public ByteArray readByteArray(Type target) { int type = readAMF3Integer(); if ((type & 1) == 0) { return (ByteArray) getReference(type >> 1); } type >>= 1; ByteArray result = new ByteArray(buf, type); storeReference(result); return result; } /** * Read Vector<Integer> object. * * @return Vector<Integer> object */ @SuppressWarnings("unchecked") public Vector<Integer> readVectorInt() { log.debug("readVectorInt"); int type = readAMF3Integer(); if ((type & 1) == 0) { return (Vector<Integer>) getReference(type >> 1); } int len = type >> 1; Vector<Integer> array = new Vector<Integer>(len); storeReference(array); @SuppressWarnings("unused") int ref2 = readAMF3Integer(); for (int j = 0; j < len; ++j) { array.add(buf.getInt()); } return array; } /** * Read Vector<uint> object. * * @return Vector<Long> object */ @SuppressWarnings("unchecked") public Vector<Long> readVectorUInt() { log.debug("readVectorUInt"); int type = readAMF3Integer(); if ((type & 1) == 0) { return (Vector<Long>) getReference(type >> 1); } int len = type >> 1; Vector<Long> array = new Vector<Long>(len); storeReference(array); @SuppressWarnings("unused") int ref2 = readAMF3Integer(); for (int j = 0; j < len; ++j) { long value = (buf.get() & 0xff) << 24L; value += (buf.get() & 0xff) << 16L; value += (buf.get() & 0xff) << 8L; value += (buf.get() & 0xff); array.add(value); } return array; } /** * Read Vector<Number> object. * * @return Vector<Double> object */ @SuppressWarnings("unchecked") public Vector<Double> readVectorNumber() { log.debug("readVectorNumber"); int type = readAMF3Integer(); log.debug("Type: {}", type); if ((type & 1) == 0) { return (Vector<Double>) getReference(type >> 1); } int len = type >> 1; log.debug("Length: {}", len); Vector<Double> array = new Vector<Double>(len); storeReference(array); int ref2 = readAMF3Integer(); log.debug("Ref2: {}", ref2); for (int j = 0; j < len; ++j) { Double d = buf.getDouble(); log.debug("Double: {}", d); array.add(d); } return array; } /** * Read Vector<Object> object. * * @return Vector<Object> object */ @SuppressWarnings("unchecked") public Vector<Object> readVectorObject() { log.debug("readVectorObject"); int type = readAMF3Integer(); log.debug("Type: {}", type); if ((type & 1) == 0) { return (Vector<Object>) getReference(type >> 1); } int len = type >> 1; log.debug("Length: {}", len); Vector<Object> array = new Vector<Object>(len); storeReference(array); int ref2 = readAMF3Integer(); log.debug("Ref2: {}", ref2); buf.skip(1); Deserializer deserializer = new Deserializer(); Object object = null; for (int j = 0; j < len; ++j) { byte objectType = buf.get(); log.debug("Object type: {}", objectType); switch (objectType) { case AMF3.TYPE_UNDEFINED: case AMF3.TYPE_NULL: object = null; break; case AMF3.TYPE_STRING: object = readString(null); break; case AMF3.TYPE_NUMBER: case AMF3.TYPE_INTEGER: object = readNumber(null); break; case AMF3.TYPE_BYTEARRAY: object = readByteArray(null); break; case AMF3.TYPE_VECTOR_INT: object = readVectorInt(); break; case AMF3.TYPE_VECTOR_UINT: object = readVectorUInt(); break; case AMF3.TYPE_VECTOR_NUMBER: object = readVectorNumber(); break; case AMF3.TYPE_VECTOR_OBJECT: object = readVectorObject(); break; default: object = readObject(deserializer, null); } array.add(object); } log.debug("Vector: {}", array); return array; } /** * Reads Custom * * @return Object Custom type object */ @Override public Object readCustom(Type target) { // Return null for now return null; } /** {@inheritDoc} */ public Object readReference(Type target) { throw new RuntimeException("AMF3 doesn't support direct references."); } /** * Parser of AMF3 "compressed" integer data type * * @return a converted integer value * @see <a href="http://osflash.org/amf3/parsing_integers">parsing AMF3 integers (external)</a> */ private int readAMF3Integer() { int n = 0; int b = buf.get(); int result = 0; while ((b & 0x80) != 0 && n < 3) { result <<= 7; result |= (b & 0x7f); b = buf.get(); n++; } if (n < 3) { result <<= 7; result |= b; } else { // use all 8 bits from the 4th byte result <<= 8; result |= b & 0x0ff; // check if the integer should be negative if ((result & 0x10000000) != 0) { // extend the sign bit result |= 0xe0000000; } } return result; } //Read UInt29 style @SuppressWarnings("unused") private int readAMF3IntegerNew() { int b = buf.get() & 0xFF; if (b < 128) { return b; } int value = (b & 0x7F) << 7; b = buf.get() & 0xFF; if (b < 128) { return (value | b); } value = (value | b & 0x7F) << 7; b = buf.get() & 0xFF; if (b < 128) { return (value | b); } value = (value | b & 0x7F) << 8; b = buf.get() & 0xFF; return (value | b); } /** {@inheritDoc} */ public Document readXML(Type target) { int len = readAMF3Integer(); if (len == 1) { // Empty string, should not happen return null; } if ((len & 1) == 0) { // Reference return (Document) getReference(len >> 1); } len >>= 1; int limit = buf.limit(); final ByteBuffer strBuf = buf.buf(); strBuf.limit(strBuf.position() + len); final String xmlString = AMF.CHARSET.decode(strBuf).toString(); buf.limit(limit); // Reset the limit Document doc = null; try { doc = XMLUtils.stringToDoc(xmlString); } catch (IOException ioex) { log.error("IOException converting xml to dom", ioex); } storeReference(doc); return doc; } /** * Resets map */ @Override public void reset() { super.reset(); // input must keep the String references for all parameters //refStorage.stringReferences.clear(); } }