
org.red5.io.amf3.Input Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of android-rmtp-client Show documentation
Show all versions of android-rmtp-client Show documentation
A standalone RTMP client library ported from the Red5 project
The newest version!
package org.red5.io.amf3;
/*
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
*
* Copyright (c) 2006-2010 by respective authors (see below). All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any later
* version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with this library; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
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 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.service.ConversionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
/**
* Input for Red5 data (AMF3) types
*
* @author The Red5 Project ([email protected])
* @author Luke Hubbard, Codegent Ltd ([email protected])
* @author Joachim Bauch ([email protected])
*/
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 attributeNames;
/** Create new informations about a class.
* @param className class name
* @param type type
* @param attributeNames attributes
*/
public ClassReference(String className, int type, List 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 {
static final class PendingProperty {
Object obj;
Class> klass;
String name;
PendingProperty(Object obj, Class> klass, String name) {
this.obj = obj;
this.klass = klass;
this.name = name;
}
}
private List properties;
public void addPendingProperty(Object obj, Class> klass, String name) {
if (properties == null) {
properties = new ArrayList();
}
properties.add(new PendingProperty(obj, klass, name));
}
public void resolveProperties(Object result) {
if (properties == null)
// No pending properties
return;
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();
}
}
/**
* Class used to collect AMF3 references.
* In AMF3 references should be collected through the whole "body" (across several Input objects).
*/
public static class RefStorage {
private List classReferences = new ArrayList();
private List stringReferences = new ArrayList();
private Map refMap = new HashMap();
}
/**
* Logger
*/
protected static Logger log = LoggerFactory.getLogger(Input.class);
/**
* Set to a value above 0 to enforce AMF3 decoding mode.
*/
private int amf3_mode;
/**
* List of string values found in the input stream.
*/
private List stringReferences;
/**
* Informations about already deserialized classes.
*/
private List classReferences;
/**
* Creates Input object for AMF3 from byte buffer
*
* @param buf Byte buffer
*/
public Input(IoBuffer buf) {
super(buf);
amf3_mode = 0;
stringReferences = new ArrayList();
classReferences = new ArrayList();
}
/**
* 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.stringReferences = refStorage.stringReferences;
this.classReferences = refStorage.classReferences;
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() {
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;
}
// Basic
/**
* 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) {
Number v;
if (currentDataType == AMF3.TYPE_NUMBER) {
v = buf.getDouble();
} else {
// we are decoding an int
v = readAMF3Integer();
}
if (target instanceof Class && Number.class.isAssignableFrom((Class>) target)) {
Class cls = (Class) target;
if (!cls.isAssignableFrom(v.getClass())) {
// v = (Number) convertUtilsBean.convert(v.toString(), cls);
}
}
return v;
}
/**
* Reads a string
*
* @return String String
*/
@Override
public String readString(Type target) {
int len = readAMF3Integer();
log.debug("readString - length: {}", len);
if (len == 1) {
// Empty string
return "";
}
if ((len & 1) == 0) {
//if the refs are empty an IndexOutOfBoundsEx will be thrown
if (stringReferences.isEmpty()) {
log.debug("String reference list is empty");
}
// Reference
return 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 = AMF3.CHARSET.decode(strBuf).toString();
log.debug("String: {}", string);
buf.limit(limit); // Reset the limit
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 = AMF3.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 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.");
}
// Object
@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 attributes = null;
String className;
Object result = null;
boolean inlineClass = (type & 1) == 1;
log.debug("Class is in-line? {}", inlineClass);
if (!inlineClass) {
ClassReference info = 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() {
{
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 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(count);
for (int i = 0; i < count; i++) {
attributes.add(readString(String.class));
}
classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_PROPERTY, attributes));
}
properties = new ObjectMap();
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));
}
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(count);
for (int i = 0; i < count; i++) {
attributes.add(readString(String.class));
}
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 tmpAttributes = new ArrayList(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!");
}
classReferences.add(new ClassReference(className, AMF3.TYPE_OBJECT_VALUE, attributes));
}
properties = new ObjectMap();
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));
}
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 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 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;
}
public ByteArray readByteArray(Type target) {
int type = readAMF3Integer();
if ((type & 1) == 0) {
// Reference
return (ByteArray) getReference(type >> 1);
}
type >>= 1;
ByteArray result = new ByteArray(buf, type);
storeReference(result);
return result;
}
@SuppressWarnings("unchecked")
public List readVectorInt() {
int type = readAMF3Integer();
if ((type & 1) == 0) {
return (List) getReference(type >> 1);
}
int len = type >> 1;
List array = new ArrayList(len);
storeReference(array);
@SuppressWarnings("unused")
int ref2 = readAMF3Integer();
for (int j = 0; j < len; ++j) {
array.add(buf.getInt());
}
return array;
}
@SuppressWarnings("unchecked")
public List readVectorUInt() {
int type = readAMF3Integer();
if ((type & 1) == 0) {
return (List) getReference(type >> 1);
}
int len = type >> 1;
List array = new ArrayList(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;
}
@SuppressWarnings("unchecked")
public List readVectorNumber() {
int type = readAMF3Integer();
if ((type & 1) == 0) {
return (List) getReference(type >> 1);
}
int len = type >> 1;
List array = new ArrayList(len);
storeReference(array);
@SuppressWarnings("unused")
int ref2 = readAMF3Integer();
for (int j = 0; j < len; ++j) {
array.add(buf.getDouble());
}
return array;
}
@SuppressWarnings("unchecked")
public List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy