AMF3 tests, exceptions

This commit is contained in:
Jindra Petřík
2016-07-17 15:48:56 +02:00
parent ec5ca5c3f1
commit f7b3d502cb
39 changed files with 2639 additions and 1545 deletions

View File

@@ -12,8 +12,10 @@ import com.jpexs.decompiler.flash.amf.amf3.types.VectorDoubleType;
import com.jpexs.decompiler.flash.amf.amf3.types.DictionaryType;
import com.jpexs.decompiler.flash.amf.amf3.types.VectorUIntType;
import com.jpexs.decompiler.flash.amf.amf3.types.BasicType;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.NoSuchFileException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -103,7 +105,7 @@ public class AMF3InputStream extends InputStream {
byte buf[] = new byte[(int) byteLength]; //how about long strings(?), will the int length be enough?
int cnt = is.read(buf);
if (cnt < buf.length) {
throw new IOException("EOF");
throw new PrematureEndOfTheStreamException();
}
String retString = new String(buf, "UTF-8");
return retString;
@@ -131,7 +133,7 @@ public class AMF3InputStream extends InputStream {
private int safeRead() throws IOException {
int ret = read();
if (ret == -1) {
throw new IOException("EOF");
throw new PrematureEndOfTheStreamException();
}
return ret;
}
@@ -141,11 +143,11 @@ public class AMF3InputStream extends InputStream {
return is.read();
}
public Object readValue() throws IOException {
public Object readValue() throws IOException, NoSerializerExistsException {
return readValue(new HashMap<>());
}
public Object readValue(Map<String, ObjectTypeSerializeHandler> serializers) throws IOException {
public Object readValue(Map<String, ObjectTypeSerializeHandler> serializers) throws IOException, NoSerializerExistsException {
return readValue(serializers, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
}
@@ -153,7 +155,7 @@ public class AMF3InputStream extends InputStream {
List<Object> objectTable,
List<Traits> traitsTable,
List<String> stringTable
) throws IOException {
) throws IOException, NoSerializerExistsException {
int marker = readU8();
switch (marker) {
@@ -197,7 +199,7 @@ public class AMF3InputStream extends InputStream {
byte buf[] = new byte[(int) byteLength]; //TODO: long strings, int is not enough for them
int cnt = is.read(buf);
if (cnt < buf.length) {
throw new IOException("EOF");
throw new PrematureEndOfTheStreamException();
}
xval = new String(buf, "UTF-8");
}
@@ -243,14 +245,27 @@ public class AMF3InputStream extends InputStream {
if (key.isEmpty()) {
break;
} else {
Object val = readValue(serializers, objectTable, traitsTable, stringTable);
assocPart.add(new Pair<>(key, val));
try {
Object val = readValue(serializers, objectTable, traitsTable, stringTable);
assocPart.add(new Pair<>(key, val));
} catch (NoSerializerExistsException nse) {
assocPart.add(new Pair<>(key, nse.getIncompleteData()));
throw new NoSerializerExistsException(nse.getClassName(), retArray, nse);
}
}
}
LOGGER.log(Level.FINEST, "Array value: assocSize={0}", new Object[]{assocPart.size()});
for (int i = 0; i < denseCount; i++) {
densePart.add(readValue(serializers, objectTable, traitsTable, stringTable));
try {
densePart.add(readValue(serializers, objectTable, traitsTable, stringTable));
} catch (NoSerializerExistsException nse) {
densePart.add(nse.getIncompleteData());
for (int j = i + 1; j < denseCount; j++) {
densePart.add(BasicType.UNKNOWN);
}
throw new NoSerializerExistsException(nse.getClassName(), retArray, nse);
}
}
LOGGER.log(Level.FINER, "Array value: dense_size={0},assocSize={1}", new Object[]{densePart.size(), assocPart.size()});
return retArray;
@@ -274,13 +289,17 @@ public class AMF3InputStream extends InputStream {
if (objectTraitsExtFlag == 1) {
String className = readUtf8Vr(stringTable);
if (!serializers.containsKey(className)) {
throw new IOException("No (de)serializer for class " + className + " found");
throw new NoSerializerExistsException(className, new ObjectType(className, null, new ArrayList<>()), null);
}
retObjectType = serializers.get(className).readObject(className, is);
MonitoredInputStream mis = new MonitoredInputStream(is);
List<Pair<String, Object>> serMembers = serializers.get(className).readObject(className, mis);
byte serData[] = mis.getReadData();
retObjectType = new ObjectType(className, serData, serMembers);
LOGGER.log(Level.FINER, "Object/Traits value: customSerialized");
traits = new Traits(className, true, new ArrayList<>());// FIXME???
traitsTable.add(traits);
//TODO: verify this
objectTable.add(retObjectType);
return retObjectType;
} else {
int dynamicFlag = (int) ((objectU29 >> 3) & 1);
int numSealed = (int) (objectU29 >> 4);
@@ -310,8 +329,19 @@ public class AMF3InputStream extends InputStream {
Object retObjectType = new ObjectType(traits.isDynamic(), sealedMembers, dynamicMembers, traits.getClassName());
objectTable.add(retObjectType); //add it before any subvalue can reference it
List<Object> sealedMemberValues = new ArrayList<>();
NoSerializerExistsException error = null;
for (int i = 0; i < traits.getSealedMemberNames().size(); i++) {
sealedMemberValues.add(readValue(serializers, objectTable, traitsTable, stringTable));
try {
sealedMemberValues.add(readValue(serializers, objectTable, traitsTable, stringTable));
} catch (NoSerializerExistsException nse) {
sealedMemberValues.add(nse.getIncompleteData());
for (int j = i + 1; j < traits.getSealedMemberNames().size(); j++) {
sealedMemberValues.add(BasicType.UNKNOWN);
}
error = nse;
break;
}
}
for (int i = 0; i < traits.getSealedMemberNames().size(); i++) {
@@ -320,8 +350,13 @@ public class AMF3InputStream extends InputStream {
if (traits.isDynamic()) {
String dynamicMemberName;
while (!(dynamicMemberName = readUtf8Vr(stringTable)).isEmpty()) {
Object dynamicMemberValue = readValue(serializers, objectTable, traitsTable, stringTable);
dynamicMembers.add(new Pair<>(dynamicMemberName, dynamicMemberValue));
try {
Object dynamicMemberValue = readValue(serializers, objectTable, traitsTable, stringTable);
dynamicMembers.add(new Pair<>(dynamicMemberName, dynamicMemberValue));
} catch (NoSerializerExistsException nse) {
dynamicMembers.add(new Pair<>(dynamicMemberName, nse.getIncompleteData()));
throw new NoSerializerExistsException(nse.getClassName(), retObjectType, nse);
}
}
}
@@ -356,7 +391,7 @@ public class AMF3InputStream extends InputStream {
int byteArrayLength = (int) (byteArrayU29 >> 1);
byte byteArrayBuf[] = new byte[byteArrayLength];
if (is.read(byteArrayBuf) != byteArrayLength) {
throw new IOException("EOF");
throw new PrematureEndOfTheStreamException();
}
LOGGER.log(Level.FINER, "ByteArray value: bytes[{0}]", byteArrayLength);
@@ -437,12 +472,25 @@ public class AMF3InputStream extends InputStream {
int fixed = readU8();
String objectTypeName = readUtf8Vr(stringTable); //uses "*" for any type
List<Object> vals = new ArrayList<>();
NoSerializerExistsException error = null;
for (int i = 0; i < vectorObjectCountItems; i++) {
vals.add(readValue(serializers, objectTable, traitsTable, stringTable));
try {
vals.add(readValue(serializers, objectTable, traitsTable, stringTable));
} catch (NoSerializerExistsException nse) {
vals.add(nse.getIncompleteData());
for (int j = i + 1; j < vectorObjectCountItems; j++) {
vals.add(BasicType.UNKNOWN);
}
error = nse;
break;
}
}
VectorObjectType retVectorObject = new VectorObjectType(fixed == 1, objectTypeName, vals);
LOGGER.log(Level.FINER, "Vector<Object> value: fixed={0}, size={1}, typeName:{2}]", new Object[]{fixed, vectorObjectCountItems, objectTypeName});
objectTable.add(retVectorObject);
if (error != null) {
throw new NoSerializerExistsException(error.getClassName(), retVectorObject, error);
}
return retVectorObject;
} else {
int refIndexVectorObject = (int) (vectorObjectU29 >> 1);
@@ -456,13 +504,37 @@ public class AMF3InputStream extends InputStream {
int numEntries = (int) (dictionaryObjectU29 >> 1);
int weakKeys = readU8();
List<Pair<Object, Object>> data = new ArrayList<>();
NoSerializerExistsException error = null;
for (int i = 0; i < numEntries; i++) {
Object key = readValue(serializers, objectTable, traitsTable, stringTable);
Object val = readValue(serializers, objectTable, traitsTable, stringTable);
Object key;
Object val;
try {
key = readValue(serializers, objectTable, traitsTable, stringTable);
try {
val = readValue(serializers, objectTable, traitsTable, stringTable);
} catch (NoSerializerExistsException nse) {
error = nse;
val = BasicType.UNKNOWN;
}
} catch (NoSerializerExistsException nse) {
error = nse;
key = BasicType.UNKNOWN;
val = BasicType.UNKNOWN;
}
data.add(new Pair<>(key, val));
if (error != null) {
for (int j = i + 1; j < numEntries; j++) {
data.add(new Pair<>(BasicType.UNKNOWN, BasicType.UNKNOWN));
}
break;
}
}
DictionaryType retDictionary = new DictionaryType(weakKeys == 1, data);
objectTable.add(retDictionary);
if (error != null) {
throw new NoSerializerExistsException(error.getClassName(), retDictionary, error);
}
return retDictionary;
} else {
int refIndexDictionary = (int) (dictionaryObjectU29 >> 1);
@@ -470,8 +542,31 @@ public class AMF3InputStream extends InputStream {
return objectTable.get(refIndexDictionary);
}
default:
return null;
throw new UnsupportedValueType(marker);
}
}
private class MonitoredInputStream extends InputStream {
private final InputStream is;
private ByteArrayOutputStream baos;
public MonitoredInputStream(InputStream is) {
this.is = is;
this.baos = new ByteArrayOutputStream();
}
@Override
public int read() throws IOException {
int ret = is.read();
if (ret > -1) {
baos.write(ret);
}
return ret;
}
public byte[] getReadData() {
return baos.toByteArray();
}
}
}

View File

@@ -46,6 +46,9 @@ public class AMF3Tools {
if (((List<? extends Class>) Arrays.asList(String.class, Long.class, Double.class, BasicType.class, Boolean.class)).contains(object.getClass())) {
return;
}
if (object instanceof BasicType) {
return;
}
int prevRef = 0;
if (referenceCount.containsKey(object)) {
prevRef = referenceCount.get(object);
@@ -85,7 +88,11 @@ public class AMF3Tools {
if (object instanceof String) {
return "\"" + Helper.escapeActionScriptString((String) object) + "\"";
}
if (((List<? extends Class>) Arrays.asList(Long.class, Double.class, BasicType.class, Boolean.class)).contains(object.getClass())) {
if (((List<? extends Class>) Arrays.asList(Long.class, Double.class, Boolean.class)).contains(object.getClass())) {
return object.toString();
}
if (object instanceof BasicType) {
return object.toString();
}
@@ -122,35 +129,58 @@ public class AMF3Tools {
ret.append(indent(level + 1)).append("\"type\": \"Object\",\r\n");
ret.append(addId);
ret.append(indent(level + 1)).append("\"className\": ").append(amfToString(processedObjects, level, ot.getClassName(), referenceCount, objectAlias)).append(",\r\n");
ret.append(indent(level + 1)).append("\"dynamic\": ").append(ot.isDynamic()).append(",\r\n");
if (!ot.getSealedMembers().isEmpty()) {
ret.append(indent(level + 1)).append("\"sealedMembers\": {\r\n");
for (int i = 0; i < ot.getSealedMembers().size(); i++) {
Pair<String, Object> member = ot.getSealedMembers().get(i);
ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)).append(":").append(amfToString(processedObjects, level + 1, member.getSecond(), referenceCount, objectAlias));
if (i < ot.getSealedMembers().size() - 1) {
ret.append(",\r\n");
} else {
if (ot.isSerialized()) {
byte[] serData = ot.getSerializedData();
if (serData == null) {
ret.append(indent(level + 1)).append("\"serialized\": unknown\r\n");
} else {
ret.append(indent(level + 1)).append("\"serialized\": 0x").append(javax.xml.bind.DatatypeConverter.printHexBinary(serData)).append(",\r\n");
if (!ot.getSerializedMembers().isEmpty()) {
ret.append(indent(level + 1)).append("\"unserializedMembers\": {\r\n");
for (int i = 0; i < ot.getSerializedMembers().size(); i++) {
Pair<String, Object> member = ot.getSerializedMembers().get(i);
ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)).append(":").append(amfToString(processedObjects, level + 1, member.getSecond(), referenceCount, objectAlias));
if (i < ot.getSerializedMembers().size() - 1) {
ret.append(",\r\n");
} else {
ret.append("\r\n");
}
}
ret.append(indent(level + 1)).append("}");
ret.append("\r\n");
}
}
ret.append(indent(level + 1)).append("}");
if (!ot.getDynamicMembers().isEmpty()) {
ret.append(",");
}
ret.append("\r\n");
}
if (!ot.getDynamicMembers().isEmpty()) {
ret.append(indent(level + 1)).append("\"dynamicMembers\": {\r\n");
for (int i = 0; i < ot.getDynamicMembers().size(); i++) {
Pair<String, Object> member = ot.getDynamicMembers().get(i);
ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)).append(":").append(amfToString(processedObjects, level + 2, member.getSecond(), referenceCount, objectAlias));
if (i < ot.getDynamicMembers().size() - 1) {
} else {
ret.append(indent(level + 1)).append("\"dynamic\": ").append(ot.isDynamic()).append(",\r\n");
if (!ot.getSealedMembers().isEmpty()) {
ret.append(indent(level + 1)).append("\"sealedMembers\": {\r\n");
for (int i = 0; i < ot.getSealedMembers().size(); i++) {
Pair<String, Object> member = ot.getSealedMembers().get(i);
ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)).append(":").append(amfToString(processedObjects, level + 1, member.getSecond(), referenceCount, objectAlias));
if (i < ot.getSealedMembers().size() - 1) {
ret.append(",\r\n");
} else {
ret.append("\r\n");
}
}
ret.append(indent(level + 1)).append("}");
if (!ot.getDynamicMembers().isEmpty()) {
ret.append(",");
}
ret.append("\r\n");
}
ret.append(indent(level + 1)).append("}\r\n");
if (!ot.getDynamicMembers().isEmpty()) {
ret.append(indent(level + 1)).append("\"dynamicMembers\": {\r\n");
for (int i = 0; i < ot.getDynamicMembers().size(); i++) {
Pair<String, Object> member = ot.getDynamicMembers().get(i);
ret.append(indent(level + 2)).append(amfToString(processedObjects, level + 2, member.getFirst(), referenceCount, objectAlias)).append(":").append(amfToString(processedObjects, level + 2, member.getSecond(), referenceCount, objectAlias));
if (i < ot.getDynamicMembers().size() - 1) {
ret.append(",");
}
ret.append("\r\n");
}
ret.append(indent(level + 1)).append("}\r\n");
}
}
ret.append(indent(level)).append("}");
} else if (object instanceof ArrayType) {
@@ -198,7 +228,7 @@ public class AMF3Tools {
ret.append("\r\n");
}
ret.append(indent(level + 1)).append("}\r\n");
ret.append(indent(level)).append("}");
ret.append(indent(level) + "}");
} else if (object instanceof ByteArrayType) {
ByteArrayType ba = (ByteArrayType) object;
byte data[] = ba.getData();
@@ -228,10 +258,6 @@ public class AMF3Tools {
+ addId
+ indent(level + 1) + "\"value\": " + amfToString(processedObjects, level, ((XmlType) object).getData(), referenceCount, objectAlias) + "\r\n"
+ indent(level) + "}";
} else if (object == BasicType.NULL) {
return "null";
} else if (object == BasicType.UNDEFINED) {
return "undefined";
} else {
throw new IllegalArgumentException("Unsupported type: " + object.getClass());
}

View File

@@ -0,0 +1,25 @@
package com.jpexs.decompiler.flash.amf.amf3;
public class NoSerializerExistsException extends Exception {
private final String className;
private final Object incompleteData;
/*public NoSerializerExistsException(String className, Object incompleteData) {
this(className, incompleteData, null);
}*/
public NoSerializerExistsException(String className, Object incompleteData, Throwable cause) {
super("Cannot read AMF - no deserializer defined for class \"" + className + "\".", cause);
this.className = className;
this.incompleteData = incompleteData;
}
public String getClassName() {
return className;
}
public Object getIncompleteData() {
return incompleteData;
}
}

View File

@@ -1,12 +1,13 @@
package com.jpexs.decompiler.flash.amf.amf3;
import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
public interface ObjectTypeSerializeHandler {
public ObjectType readObject(String className, InputStream is);
public List<Pair<String, Object>> readObject(String className, InputStream is) throws IOException;
public void writeObject(ObjectType val, OutputStream os);
public void writeObject(List<Pair<String, Object>> members, OutputStream os) throws IOException;
}

View File

@@ -0,0 +1,11 @@
package com.jpexs.decompiler.flash.amf.amf3;
import java.io.IOException;
public class PrematureEndOfTheStreamException extends IOException {
public PrematureEndOfTheStreamException() {
super("Premature end of the stream reached");
}
}

View File

@@ -0,0 +1,16 @@
package com.jpexs.decompiler.flash.amf.amf3;
public class UnsupportedValueType extends RuntimeException {
private int marker;
public UnsupportedValueType(int marker) {
super("Unsupported type of value - marker: 0x" + Integer.toHexString(marker));
this.marker = marker;
}
public int getMarker() {
return marker;
}
}

View File

@@ -0,0 +1,40 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.amf.amf3.AMF3Tools;
import java.util.ArrayList;
import java.util.List;
import com.jpexs.decompiler.flash.amf.amf3.WithSubValues;
public abstract class AbstractVectorType<T> implements WithSubValues {
private boolean fixed;
private List<T> values;
public boolean isFixed() {
return fixed;
}
public AbstractVectorType(boolean fixed, List<T> values) {
this.values = values;
this.fixed = fixed;
}
public List<T> getValues() {
return values;
}
@Override
public List<Object> getSubValues() {
List<Object> ret = new ArrayList<>();
ret.addAll(values);
return ret;
}
public abstract String getTypeName();
@Override
public String toString() {
return AMF3Tools.amfToString(this);
}
}

View File

@@ -0,0 +1,46 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.amf.amf3.AMF3Tools;
import com.jpexs.decompiler.flash.amf.amf3.Pair;
import com.jpexs.helpers.Helper;
import java.util.ArrayList;
import java.util.List;
import com.jpexs.decompiler.flash.amf.amf3.WithSubValues;
public class ArrayType implements WithSubValues {
private List<Object> denseValues;
private List<Pair<String, Object>> associativeValues;
public ArrayType(List<Object> denseValues, List<Pair<String, Object>> associativeValues) {
this.denseValues = denseValues;
this.associativeValues = associativeValues;
}
public List<Object> getDenseValues() {
return denseValues;
}
public List<Pair<String, Object>> getAssociativeValues() {
return associativeValues;
}
@Override
public String toString() {
return AMF3Tools.amfToString(this);
}
@Override
public List<Object> getSubValues() {
List<Object> ret = new ArrayList<>();
for (Pair<String, Object> p : associativeValues) {
ret.add(p.getFirst());
ret.add(p.getSecond());
}
for (Object v : denseValues) {
ret.add(v);
}
return ret;
}
}

View File

@@ -0,0 +1,26 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
public enum BasicType {
NULL {
@Override
public String toString() {
return "null";
}
},
UNDEFINED {
@Override
public String toString() {
return "undefined";
}
},
//Special types for errors while reading
UNKNOWN {
@Override
public String toString() {
return "unknown";
}
}
}

View File

@@ -0,0 +1,22 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.amf.amf3.AMF3Tools;
public class ByteArrayType {
private byte[] data;
public ByteArrayType(byte[] data) {
this.data = data;
}
public byte[] getData() {
return data;
}
@Override
public String toString() {
return AMF3Tools.amfToString(this);
}
}

View File

@@ -0,0 +1,30 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.amf.amf3.AMF3Tools;
import java.util.Date;
public class DateType {
private double val;
public DateType(double val) {
this.val = val;
}
public double getVal() {
return val;
}
public void setVal(double val) {
this.val = val;
}
@Override
public String toString() {
return AMF3Tools.amfToString(this);
}
public Date toDate() {
return new Date((long) val);
}
}

View File

@@ -0,0 +1,38 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.amf.amf3.AMF3Tools;
import com.jpexs.decompiler.flash.amf.amf3.Pair;
import java.util.ArrayList;
import java.util.List;
import com.jpexs.decompiler.flash.amf.amf3.WithSubValues;
public class DictionaryType implements WithSubValues {
private boolean weakKeys;
private List<Pair<Object, Object>> pairs;
public DictionaryType(boolean weakKeys, List<Pair<Object, Object>> pairs) {
this.weakKeys = weakKeys;
this.pairs = pairs;
}
public List<Pair<Object, Object>> getPairs() {
return pairs;
}
@Override
public List<Object> getSubValues() {
List<Object> ret = new ArrayList<>();
for (Pair<Object, Object> p : pairs) {
ret.add(p.getFirst());
ret.add(p.getSecond());
}
return ret;
}
@Override
public String toString() {
return AMF3Tools.amfToString(this);
}
}

View File

@@ -0,0 +1,116 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.decompiler.flash.amf.amf3.AMF3Tools;
import com.jpexs.decompiler.flash.amf.amf3.Pair;
import com.jpexs.helpers.Helper;
import java.util.ArrayList;
import java.util.List;
import com.jpexs.decompiler.flash.amf.amf3.WithSubValues;
public class ObjectType implements WithSubValues {
private boolean dynamic;
private List<Pair<String, Object>> sealedMembers;
private List<Pair<String, Object>> dynamicMembers;
private List<Pair<String, Object>> serializedMembers;
private String className;
//null = not serialized or unknown
private byte[] serializedData = null;
private boolean serialized;
public boolean isSerialized() {
return serialized;
}
public ObjectType(String className, byte[] serializedData, List<Pair<String, Object>> serializedMembers) {
this.className = className;
this.serializedData = serializedData;
this.serializedMembers = serializedMembers;
this.dynamicMembers = new ArrayList<>();
this.sealedMembers = new ArrayList<>();
this.serialized = true;
}
public ObjectType(boolean dynamic, List<Pair<String, Object>> sealedMembers, List<Pair<String, Object>> dynamicMembers, String className) {
this.dynamic = dynamic;
this.sealedMembers = sealedMembers;
this.dynamicMembers = dynamicMembers;
this.className = className;
this.serializedMembers = new ArrayList<>();
this.serialized = false;
}
public boolean isDynamic() {
return dynamic;
}
public List<Pair<String, Object>> getDynamicMembers() {
return dynamicMembers;
}
public List<Pair<String, Object>> getSealedMembers() {
return sealedMembers;
}
public String getClassName() {
return className;
}
public void setDynamic(boolean dynamic) {
this.dynamic = dynamic;
}
public void setDynamicMembers(List<Pair<String, Object>> dynamicMembers) {
this.dynamicMembers = dynamicMembers;
}
public void setSealedMembers(List<Pair<String, Object>> sealedMembers) {
this.sealedMembers = sealedMembers;
}
public void setClassName(String className) {
this.className = className;
}
@Override
public List<Object> getSubValues() {
List<Object> ret = new ArrayList<>();
for (Pair<String, Object> p : dynamicMembers) {
ret.add(p.getFirst());
ret.add(p.getSecond());
}
for (Pair<String, Object> p : sealedMembers) {
ret.add(p.getFirst());
ret.add(p.getSecond());
}
for (Pair<String, Object> p : serializedMembers) {
ret.add(p.getFirst());
ret.add(p.getSecond());
}
return ret;
}
@Override
public String toString() {
return AMF3Tools.amfToString(this);
}
public void setSerializedData(byte[] serializedData) {
this.serializedData = serializedData;
}
public byte[] getSerializedData() {
return serializedData;
}
public void setSerializedMembers(List<Pair<String, Object>> serializedMembers) {
this.serializedMembers = serializedMembers;
}
public List<Pair<String, Object>> getSerializedMembers() {
return serializedMembers;
}
}

View File

@@ -0,0 +1,16 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import java.util.List;
public class VectorDoubleType extends AbstractVectorType<Double> {
public VectorDoubleType(boolean fixed, List<Double> values) {
super(fixed, values);
}
@Override
public String getTypeName() {
return "Number";
}
}

View File

@@ -0,0 +1,16 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import java.util.List;
public class VectorIntType extends AbstractVectorType<Long> {
public VectorIntType(boolean fixed, List<Long> values) {
super(fixed, values);
}
@Override
public String getTypeName() {
return "int";
}
}

View File

@@ -0,0 +1,21 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import com.jpexs.helpers.Helper;
import java.util.ArrayList;
import java.util.List;
public class VectorObjectType extends AbstractVectorType<Object> {
private String typeName;
public VectorObjectType(boolean fixed, String typeName, List<Object> values) {
super(fixed, values);
this.typeName = typeName;
}
@Override
public String getTypeName() {
return typeName;
}
}

View File

@@ -0,0 +1,17 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
import java.util.ArrayList;
import java.util.List;
public class VectorUIntType extends AbstractVectorType<Long> {
public VectorUIntType(boolean fixed, List<Long> values) {
super(fixed, values);
}
@Override
public String getTypeName() {
return "uint";
}
}

View File

@@ -0,0 +1,24 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
public class XmlDocType {
private String data;
public XmlDocType(String data) {
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public String toString() {
return data;
}
}

View File

@@ -0,0 +1,24 @@
package com.jpexs.decompiler.flash.amf.amf3.types;
public class XmlType {
private String data;
public XmlType(String data) {
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public String toString() {
return data;
}
}

View File

@@ -0,0 +1,171 @@
package com.jpexs.decompiler.flash.amf.amf3;
import com.jpexs.decompiler.flash.amf.amf3.types.ArrayType;
import com.jpexs.decompiler.flash.amf.amf3.types.DictionaryType;
import com.jpexs.decompiler.flash.amf.amf3.types.ObjectType;
import com.jpexs.decompiler.flash.amf.amf3.types.VectorObjectType;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.testng.Assert.*;
import org.testng.annotations.AfterTest;
import org.testng.annotations.Test;
public class Amf3InputStreamTest {
private FileInputStream fis;
private AMF3InputStream is;
@AfterTest
public void deinitStream() {
if (is != null) {
try {
is.close();
} catch (IOException ex) {
//ignore
}
is = null;
}
if (fis != null) {
try {
fis.close();
} catch (IOException ex) {
//ignore
}
fis = null;
}
}
private void initStream(String fileName) throws FileNotFoundException {
fis = new FileInputStream("testdata/amf3/generated/" + fileName);
is = new AMF3InputStream(fis);
}
@Test
public void testReadObject() throws IOException, NoSerializerExistsException {
initStream("all.bin");
is.readValue();
}
private Map<String, ObjectTypeSerializeHandler> getSerializers() {
Map<String, ObjectTypeSerializeHandler> serializers = new HashMap<>();
serializers.put("CustomClass", new ObjectTypeSerializeHandler() {
@Override
public List<Pair<String, Object>> readObject(String className, InputStream is) throws IOException {
List<Pair<String, Object>> members = new ArrayList<>();
members.add(new Pair<>("val8", (long) is.read()));
members.add(new Pair<>("val32", (long) ((is.read() << 24) + (is.read() << 16) + (is.read() << 8) + (is.read()))));
return members;
}
@Override
public void writeObject(List<Pair<String, Object>> members, OutputStream os) throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
});
return serializers;
}
@Test(expectedExceptions = NoSerializerExistsException.class)
public void testReadCustomSerializedNeedsSerializer() throws IOException, NoSerializerExistsException {
initStream("custom.bin");
is.readValue(); //needs deserializer for CustomClass
}
@Test
public void testReadCustomSerialized() throws IOException, NoSerializerExistsException {
initStream("custom.bin");
is.readValue(getSerializers());
}
@Test
public void testNoSerializerHandlingInObjectDynamicProp() throws IOException {
initStream("noserializer_object_dynamic.bin");
try {
is.readValue();
} catch (NoSerializerExistsException ex) {
assertTrue(ex.getIncompleteData() instanceof ObjectType, "Expected datatype: ObjectType, Actual datatype: " + ex.getIncompleteData().getClass());
//TODO: examinate the data more
}
}
@Test
public void testNoSerializerHandlingInObjectSealedProp() throws IOException {
initStream("noserializer_object_sealed.bin");
try {
is.readValue();
} catch (NoSerializerExistsException ex) {
assertTrue(ex.getIncompleteData() instanceof ObjectType, "Expected datatype: ObjectType, Actual datatype: " + ex.getIncompleteData().getClass());
//TODO: examinate the data more
}
}
@Test
public void testNoSerializerHandlingInArrayDense() throws IOException {
initStream("noserializer_array_dense.bin");
try {
is.readValue();
} catch (NoSerializerExistsException ex) {
assertTrue(ex.getIncompleteData() instanceof ArrayType, "Expected datatype: ArrayType, Actual datatype: " + ex.getIncompleteData().getClass());
//TODO: examinate the data more
}
}
@Test
public void testNoSerializerHandlingInArrayAssociative() throws IOException {
initStream("noserializer_array_associative.bin");
try {
is.readValue();
} catch (NoSerializerExistsException ex) {
assertTrue(ex.getIncompleteData() instanceof ArrayType, "Expected datatype: ArrayType, Actual datatype: " + ex.getIncompleteData().getClass());
//TODO: examinate the data more
}
}
@Test
public void testNoSerializerHandlingInVector() throws IOException {
initStream("noserializer_vector.bin");
try {
is.readValue();
} catch (NoSerializerExistsException ex) {
assertTrue(ex.getIncompleteData() instanceof VectorObjectType, "Expected datatype: VectorObjectType, Actual datatype: " + ex.getIncompleteData().getClass());
//TODO: examinate the data more
}
}
@Test
public void testNoSerializerHandlingInDictionaryValues() throws IOException {
initStream("noserializer_dictionary_value.bin");
try {
is.readValue();
} catch (NoSerializerExistsException ex) {
assertTrue(ex.getIncompleteData() instanceof DictionaryType, "Expected datatype: DictionaryType, Actual datatype: " + ex.getIncompleteData().getClass());
//TODO: examinate the data more
}
}
@Test
public void testNoSerializerHandlingInDictionaryKeys() throws IOException {
initStream("noserializer_dictionary_key.bin");
try {
is.readValue();
} catch (NoSerializerExistsException ex) {
assertTrue(ex.getIncompleteData() instanceof DictionaryType, "Expected datatype: DictionaryType, Actual datatype: " + ex.getIncompleteData().getClass());
//TODO: examinate the data more
}
}
@Test(expectedExceptions = UnsupportedValueType.class)
public void testUnsupportedMarker() throws IOException, NoSerializerExistsException {
final int UNSUPPORTED_MARKER = 100;
is = new AMF3InputStream(new ByteArrayInputStream(new byte[]{UNSUPPORTED_MARKER}));
is.readValue();
}
}

View File

@@ -0,0 +1,169 @@
package
{
import flash.display.Sprite;
import flash.utils.ByteArray;
import flash.filesystem.File;
import flash.filesystem.FileStream;
import flash.filesystem.FileMode;
import flash.utils.Dictionary;
import flash.geom.Point;
import flash.xml.XMLDocument;
import flash.net.registerClassAlias;
/**
* Generating test data file
*/
public class AmfTest extends Sprite
{
private function writeToFile(filename:String,data)
{
var urlStr:String = "file:///"+File.applicationDirectory.nativePath+"/generated/"+filename;
var file:File = new File() ;
file.url = urlStr;
var fileStream:FileStream = new FileStream();
fileStream.open(file, FileMode.WRITE);
var bytes:ByteArray = new ByteArray();
bytes.writeObject(data);
fileStream.writeBytes(bytes, 0, bytes.length);
fileStream.close();
}
public function AmfTest() {
var ar = ["a","b","c"];
var asoc_array = [1,2,3];
asoc_array["key1"] = 5;
asoc_array["key2"] = 6;
var ba:ByteArray = new ByteArray();
ba.writeByte(65);
ba.writeByte(66);
ba.writeByte(67);
var vector_int:Vector.<int> = new <int>[-10,20,-30,40];
var vector_uint:Vector.<uint> = new <uint>[10,20,30,40];
var vector_double:Vector.<Number> = new <Number>[-10.1,20.2,-30.3,40.4];
var vector_string:Vector.<String> = new <String>["x","y","z"];
var vector_point:Vector.<Point> = new <Point>[new Point(10,20),new Point(30,40),new Point(50,60)];
var dict = new Dictionary();
dict["dkey1"] = "TestOne";
dict["dkey2"] = "TestTwo";
var txmldoc:XMLDocument = new XMLDocument("<foo><bar>aa</bar></foo>");
var txml:XML = <foo>
<bar>Hello</bar>
<bar>Hi</bar>
</foo>;
var date = new Date();
var me = {
"01_int":5,
"02_negative-int":-5,
"03_string":"String",
"04_true":true,
"05_false":false,
"06_undefined":undefined,
"07_null":null,
"08_double":5.6,
"09_negative-double":-5.6,
"10_array":ar,
"11_asoc_array": asoc_array,
"12_date":date,
"13_xml":txml,
"14_byteArray":ba,
"15_vectorInt":vector_int,
"16_vectorUInt":vector_uint,
"17_vectorDouble":vector_double,
"18_vectorString":vector_string,
"19_vectorPoint":vector_point,
"20_dictionary":dict,
"21_xmldoc":txmldoc,
"22_ref_string":"String",
"23_ref_array":ar,
"24_ref_date":date,
"25_ref_byteArray":ba,
"26_ref_vectorInt":vector_int,
"27_ref_vectorUInt":vector_uint,
"28_ref_vectorDouble":vector_double,
"29_ref_vectorString":vector_string,
"30_ref_dictionary":dict,
"31_ref_xml":txml,
"32_ref_xmldoc":txmldoc
};
me["33_me"] = me;
writeToFile("all.bin",me);
var custom = new CustomClass();
custom.setVal8(127);
custom.setVal32(-2500);
registerClassAlias("CustomClass", CustomClass);
var cust_obj = {
"member1":5,
"member2":custom,
"member3":27
};
writeToFile("custom.bin",cust_obj);
writeToFile("noserializer_object_dynamic.bin",{
"a":5,
"b":8,
"c":custom,
"d":6,
"e":7,
"f":26
});
writeToFile("noserializer_array_dense.bin",
["a","b",custom,"d","e","f"]
);
var arx = [];
arx["a"] = 1;
arx["b"] = 2;
arx["c"] = custom;
arx["d"] = 4;
arx["e"] = 5;
arx["f"] = 6;
writeToFile("noserializer_array_associative.bin", arx);
writeToFile("noserializer_vector.bin", new <Object>["a","b",custom,"d","e","f"]);
var nsdict = new Dictionary();
nsdict["a"] = "One";
nsdict["b"] = "Two";
nsdict["c"] = custom;
nsdict["d"] = "Three";
nsdict["e"] = "Four";
nsdict["f"] = "Five";
writeToFile("noserializer_dictionary_value.bin", nsdict);
var nsdict2 = new Dictionary();
nsdict2["a"] = "One";
nsdict2["b"] = "Two";
nsdict2[custom] = "Three";
nsdict2["d"] = "Three";
nsdict2["e"] = "Four";
nsdict2["f"] = "Five";
writeToFile("noserializer_dictionary_key.bin", nsdict2);
var objWithCustom:ObjectWithCustom = new ObjectWithCustom("1","2",custom,"4","5","6");
writeToFile("noserializer_object_sealed.bin", objWithCustom);
}
}
}

Binary file not shown.

View File

@@ -0,0 +1,32 @@
package
{
import flash.utils.IExternalizable;
import flash.utils.IDataOutput;
import flash.utils.IDataInput;
public class CustomClass implements IExternalizable
{
private var val8:int;
private var val32:int;
public function CustomClass() { //No constructor parameters allowed
}
public function setVal8(v:int){
this.val8 = v;
}
public function setVal32(v:int){
this.val32 = v;
}
public function writeExternal(output:IDataOutput):void {
output.writeByte(val8);
output.writeInt(val32);
}
public function readExternal(input:IDataInput):void {
this.val8 = input.readByte();
this.val32 = input.readInt();
}
}
}

View File

@@ -0,0 +1,24 @@
package
{
public class ObjectWithCustom
{
public var a:String;
public var b:String;
public var c:CustomClass;
public var d:String;
public var e:String;
public var f:String;
public function ObjectWithCustom(a:String,b:String,c:CustomClass,d:String,e:String,f:String) {
this.a = a;
this.b = b;
this.c = c;
this.d = d;
this.e = e;
this.f = f;
}
}
}

14
libsrc/ffdec_lib/testdata/amf3/app.xml vendored Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://ns.adobe.com/air/application/3.1">
<id>samples.flex.HelloWorld</id>
<versionNumber>1</versionNumber>
<filename>HelloWorld</filename>
<initialWindow>
<content>AmfTest.swf</content>
<visible>true</visible>
<systemChrome>none</systemChrome>
<transparent>true</transparent>
<width>400</width>
<height>200</height>
</initialWindow>
</application>

Binary file not shown.

View File

@@ -0,0 +1,3 @@
member3member1member2
CustomClass<73><7F><EFBFBD><

View File

@@ -0,0 +1,2 @@
fabc
CustomClass<73><7F><EFBFBD><de

View File

@@ -0,0 +1,2 @@
ab

View File

@@ -0,0 +1,3 @@
fabc
CustomClass<73><7F><EFBFBD><de

View File

@@ -0,0 +1,3 @@
ccfadbe
CustomClass<73><7F><EFBFBD><61425

Binary file not shown.

20
libsrc/ffdec_lib/testdata/amf3/run.bat vendored Normal file
View File

@@ -0,0 +1,20 @@
@echo off
set ISDEBUG=false
if "%1" == "debug" goto blockset
goto block2
:blockset
set ISDEBUG=true
:block2
set AIRPATH=c:\air
set COMPILERPATH=%AIRPATH%\bin\amxmlc.bat
if not exist %COMPILERPATH% goto notex
call %COMPILERPATH% -warnings=false -debug=%ISDEBUG% AmfTest.as>NUL
if errorlevel==1 goto failed
goto end
:notex
echo AIR SDK not found. Download and unpack Flex SDK into C:\air directory, then run build again
goto end
:failed
pause
:end
%AIRPATH%\bin\adl.exe app.xml

View File

@@ -0,0 +1,34 @@
package
{
import flash.utils.IExternalizable;
import flash.utils.IDataOutput;
import flash.utils.IDataInput;
public class CustomClass implements IExternalizable
{
private var val8:int;
private var val32:int;
public function CustomClass()
{
}
public function setVal8(v:int){
this.val8 = v;
}
public function setVal32(v:int){
this.val32 = v;
}
public function writeExternal(output:IDataOutput):void {
output.writeByte(val8);
output.writeInt(val32);
}
public function readExternal(input:IDataInput):void {
this.val8 = input.readByte();
this.val32 = input.readInt();
}
}
}