org.red5.io.amf.Input Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ant-media-server Show documentation
Show all versions of ant-media-server Show documentation
Ant Media Server supports RTMP, RTSP, MP4, HLS, WebRTC, Adaptive Streaming, etc.
/*
* RED5 Open Source Media Server - https://github.com/Red5/ Copyright 2006-2016 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.amf;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.mina.core.buffer.IoBuffer;
import org.red5.io.amf3.ByteArray;
import org.red5.io.object.BaseInput;
import org.red5.io.object.DataTypes;
import org.red5.io.object.Deserializer;
import org.red5.io.object.RecordSet;
import org.red5.io.object.RecordSetPage;
import org.red5.io.utils.ArrayUtils;
import org.red5.io.utils.ConversionUtils;
import org.red5.io.utils.ObjectMap;
import org.red5.io.utils.XMLUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
/**
* Input for Red5 data types
*
* @author The Red5 Project
* @author Luke Hubbard, Codegent Ltd ([email protected])
* @author Paul Gregoire ([email protected])
*/
@SuppressWarnings("serial")
public class Input extends BaseInput implements org.red5.io.object.Input {
protected Logger log = LoggerFactory.getLogger(this.getClass());
protected static Map classAliases = new HashMap(3);
static{
classAliases.put("DSA", "org.red5.compatibility.flex.messaging.messages.AsyncMessageExt");
classAliases.put("DSC", "org.red5.compatibility.flex.messaging.messages.CommandMessageExt");
classAliases.put("DSK", "org.red5.compatibility.flex.messaging.messages.AcknowledgeMessageExt");
};
protected IoBuffer buf;
protected byte currentDataType;
/**
* Creates Input object from byte buffer
*
* @param buf
* Byte buffer
*/
public Input(IoBuffer buf) {
super();
this.buf = buf;
if (log.isTraceEnabled()) {
log.trace("Input: {}", Hex.encodeHexString(Arrays.copyOfRange(buf.array(), buf.position(), buf.limit())));
}
}
/**
* Reads the data type.
*
* @return One of AMF class constants with type
* @see org.red5.io.amf.AMF
*/
@Override
public byte readDataType() {
// prevent the handling of an empty Object
if (buf.hasRemaining()) {
do {
// get the data type
currentDataType = buf.get();
log.trace("Data type: {}", currentDataType);
switch (currentDataType) {
case AMF.TYPE_NULL:
case AMF.TYPE_UNDEFINED:
return DataTypes.CORE_NULL;
case AMF.TYPE_NUMBER:
return DataTypes.CORE_NUMBER;
case AMF.TYPE_BOOLEAN:
return DataTypes.CORE_BOOLEAN;
case AMF.TYPE_STRING:
case AMF.TYPE_LONG_STRING:
return DataTypes.CORE_STRING;
case AMF.TYPE_CLASS_OBJECT:
case AMF.TYPE_OBJECT:
return DataTypes.CORE_OBJECT;
case AMF.TYPE_MIXED_ARRAY:
return DataTypes.CORE_MAP;
case AMF.TYPE_ARRAY:
return DataTypes.CORE_ARRAY;
case AMF.TYPE_DATE:
return DataTypes.CORE_DATE;
case AMF.TYPE_XML:
return DataTypes.CORE_XML;
case AMF.TYPE_REFERENCE:
return DataTypes.OPT_REFERENCE;
case AMF.TYPE_UNSUPPORTED:
case AMF.TYPE_MOVIECLIP:
case AMF.TYPE_RECORDSET:
// These types are not handled by core datatypes
// So add the amf mask to them, this way the deserializer
// will call back to readCustom, we can then handle or
// return null
return (byte) (currentDataType + DataTypes.CUSTOM_AMF_MASK);
case AMF.TYPE_AMF3_OBJECT:
log.debug("Switch to AMF3");
return DataTypes.CORE_SWITCH;
}
} while (hasMoreProperties());
log.trace("No more data types available");
return DataTypes.CORE_END_OBJECT;
}
// empty object, may as well be null
return DataTypes.CORE_NULL;
}
/**
* Reads a null.
*
* @return Object
*/
@Override
public Object readNull() {
return null;
}
/**
* Reads a boolean.
*
* @return boolean
*/
@Override
public Boolean readBoolean() {
return (buf.get() == AMF.VALUE_TRUE) ? Boolean.TRUE : Boolean.FALSE;
}
/**
* Reads a Number. In ActionScript 1 and 2 Number type represents all numbers, both floats and integers.
*
* @return Number
*/
@Override
public Number readNumber() {
int remaining = buf.remaining();
log.debug("readNumber from {} bytes", remaining);
// look to see if big enough for double
if (remaining >= 8) {
double d = buf.getDouble();
log.debug("Number: {}", d);
return d;
}
if (log.isDebugEnabled()) {
log.debug("Remaining not big enough for number - offset: {} limit: {} {}", buf.position(), buf.limit(), Hex.encodeHexString(buf.array()));
}
return 0;
}
/**
* Reads string from buffer
*
* @return String
*/
@Override
public String getString() {
log.trace("getString - currentDataType: {}", currentDataType);
byte lastDataType = currentDataType;
// temporarily set to string for reading
if (currentDataType != AMF.TYPE_STRING) {
currentDataType = AMF.TYPE_STRING;
}
String result = readString();
// set data type back to what it was
currentDataType = lastDataType;
return result;
}
/**
* Reads a string
*
* @return String
*/
@Override
public String readString() {
int limit = buf.limit();
int len = 0;
switch (currentDataType) {
case AMF.TYPE_LONG_STRING:
log.trace("Long string type");
len = buf.getInt();
if (len > limit) {
len = limit;
}
break;
case AMF.TYPE_STRING:
log.trace("Std string type");
len = buf.getUnsignedShort();
break;
default:
log.debug("Unknown AMF type: {}", currentDataType);
}
log.debug("Length: {} limit: {}", len, limit);
byte[] str = new byte[len];
buf.get(str);
String string = bufferToString(str);
return string;
}
/**
* Converts the bytes into a string.
*
* @param str
* string bytes
* @return decoded String
*/
private final String bufferToString(byte[] str) {
String string = null;
if (str != null) {
string = AMF.CHARSET.decode(ByteBuffer.wrap(str)).toString();
log.debug("String: {}", string);
} else {
log.warn("ByteBuffer was null attempting to read String");
}
return string;
}
/**
* Returns a date
*
* @return Date Decoded string object
*/
@Override
public Date readDate() {
/*
* Date: 0x0B T7 T6 .. T0 Z1 Z2 T7 to T0 form a 64 bit Big Endian number that specifies the number of nanoseconds that have passed since 1/1/1970 0:00 to the specified time. This
* format is UTC 1970. Z1 an Z0 for a 16 bit Big Endian number indicating the indicated time's timezone in minutes.
*/
long ms = (long) buf.getDouble();
// The timezone can be ignored as the date always is encoded in UTC
@SuppressWarnings("unused")
short timeZoneMins = buf.getShort();
Date date = new Date(ms);
storeReference(date);
return date;
}
@Override
public Object readArray(Type target) {
log.debug("readArray - target: {}", target);
Object result = null;
int count = buf.getInt();
log.debug("Count: {}", count);
// To conform to the Input API, we should convert the output into an Array if the Type asks us to.
Class> collection = Collection.class;
if (target instanceof Class>) {
collection = (Class>) target;
}
List