writing AMF3 + tests

traits handling fix
empty string handling fix
This commit is contained in:
Jindra Petřík
2016-07-17 19:05:50 +02:00
parent 092bfb4acb
commit d33c8e8c32
7 changed files with 454 additions and 73 deletions

View File

@@ -115,8 +115,11 @@ public class Amf3InputStream extends InputStream {
int stringNoRefFlag = (int) (u & 1);
if (stringNoRefFlag == 1) {
int byteLength = (int) (u >> 1); //TODO: long strings, int is not enough for them
String retString = readUtf8Char(byteLength);
stringTable.add(retString);
String retString = "";
if (byteLength > 0) {
retString = readUtf8Char(byteLength);
stringTable.add(retString);
}
LOGGER.log(Level.FINE, "Read string: \"{0}\"", retString);
return retString;
} else { //flag==0
@@ -288,13 +291,14 @@ public class Amf3InputStream extends InputStream {
if (objectTraitsExtFlag == 1) {
String className = readUtf8Vr(stringTable);
if (!serializers.containsKey(className)) {
throw new NoSerializerExistsException(className, new ObjectType(className, null, new ArrayList<>()), null);
throw new NoSerializerExistsException(className, new ObjectType(new Traits(className, false, new ArrayList<>()), (byte[]) null, new ArrayList<>()), null);
}
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);
Traits unserTraits = new Traits(className, false, new ArrayList<>());
retObjectType = new ObjectType(unserTraits, serData, serMembers);
LOGGER.log(Level.FINER, "Object/Traits value: customSerialized");
objectTable.add(retObjectType);
@@ -312,20 +316,16 @@ public class Amf3InputStream extends InputStream {
}
traits = new Traits(className, dynamicFlag == 1, sealedMemberNames);
}
traitsTable.add(traits);
} else {
int refIndexTraits = (int) (objectU29 >> 2);
traits = traitsTable.get(refIndexTraits);
LOGGER.log(Level.FINER, "Traits value: reference({0}) - traitsize={1}", new Object[]{refIndexTraits, traits.getSealedMemberNames().size()});
}
if (objectTraitsNoRefFlag == 1) {
traitsTable.add(traits);
}
List<Pair<String, Object>> sealedMembers = new ArrayList<>();
List<Pair<String, Object>> dynamicMembers = new ArrayList<>();
Object retObjectType = new ObjectType(traits.isDynamic(), sealedMembers, dynamicMembers, traits.getClassName());
Object retObjectType = new ObjectType(traits, sealedMembers, dynamicMembers);
objectTable.add(retObjectType); //add it before any subvalue can reference it
List<Object> sealedMemberValues = new ArrayList<>();
NoSerializerExistsException error = null;
@@ -503,6 +503,8 @@ public class Amf3InputStream extends InputStream {
int numEntries = (int) (dictionaryObjectU29 >> 1);
int weakKeys = readU8();
List<Pair<Object, Object>> data = new ArrayList<>();
DictionaryType retDictionary = new DictionaryType(weakKeys == 1, data);
objectTable.add(retDictionary);
NoSerializerExistsException error = null;
for (int i = 0; i < numEntries; i++) {
Object key;
@@ -529,8 +531,6 @@ public class Amf3InputStream extends InputStream {
break;
}
}
DictionaryType retDictionary = new DictionaryType(weakKeys == 1, data);
objectTable.add(retDictionary);
if (error != null) {
throw new NoSerializerExistsException(error.getClassName(), retDictionary, error);
}
@@ -541,7 +541,7 @@ public class Amf3InputStream extends InputStream {
return objectTable.get(refIndexDictionary);
}
default:
throw new UnsupportedValueType(marker);
throw new UnsupportedValueTypeException(marker);
}
}

View File

@@ -1,28 +1,65 @@
package com.jpexs.decompiler.flash.amf.amf3;
import com.jpexs.decompiler.flash.amf.amf3.types.ArrayType;
import com.jpexs.decompiler.flash.amf.amf3.types.BasicType;
import com.jpexs.decompiler.flash.amf.amf3.types.ByteArrayType;
import com.jpexs.decompiler.flash.amf.amf3.types.DateType;
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.VectorDoubleType;
import com.jpexs.decompiler.flash.amf.amf3.types.VectorIntType;
import com.jpexs.decompiler.flash.amf.amf3.types.VectorObjectType;
import com.jpexs.decompiler.flash.amf.amf3.types.VectorUIntType;
import com.jpexs.decompiler.flash.amf.amf3.types.XmlDocType;
import com.jpexs.decompiler.flash.amf.amf3.types.XmlType;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
public class Amf3OutputStream extends OutputStream {
public final static Logger LOGGER = Logger.getLogger(Amf3OutputStream.class.getName());
private final OutputStream os;
private static final int NO_REFERENCE_FLAG = 1;
private static final int NO_TRAIT_REFERENCE_FLAG = 2;
private static final int TRAIT_EXT_FLAG = 4;
private static final int DYNAMIC_FLAG = 8;
public Amf3OutputStream(OutputStream os) {
this.os = os;
}
public void writeUI8(int v) throws IOException {
public void writeU8(int v) throws IOException {
write(v);
}
public void writeUI16(int v) throws IOException {
public void writeU16(int v) throws IOException {
int b1 = (v >> 8) & 0xff;
int b2 = v & 0xff;
write(b1);
write(b2);
}
public void writeUI32(long v) throws IOException {
private void writeLong(long value) throws IOException {
byte[] writeBuffer = new byte[8];
writeBuffer[0] = (byte) (value >>> 56);
writeBuffer[1] = (byte) (value >>> 48);
writeBuffer[2] = (byte) (value >>> 40);
writeBuffer[3] = (byte) (value >>> 32);
writeBuffer[4] = (byte) (value >>> 24);
writeBuffer[5] = (byte) (value >>> 16);
writeBuffer[6] = (byte) (value >>> 8);
writeBuffer[7] = (byte) (value);
write(writeBuffer);
}
public void writeU32(long v) throws IOException {
int b1 = (int) ((v >> 24) & 0xff);
int b2 = (int) ((v >> 16) & 0xff);
int b3 = (int) ((v >> 8) & 0xff);
@@ -35,29 +72,39 @@ public class Amf3OutputStream extends OutputStream {
}
public void writeDouble(double v) throws IOException {
writeUI32(Double.doubleToLongBits(v));
writeLong(Double.doubleToLongBits(v));
}
public void writeUI29(long v) throws IOException {
public void writeBytes(byte[] data) throws IOException {
os.write(data);
}
public void writeU29(long v) throws IOException {
v = v & 0x3FFFFFFF; //make unsigned
final int USE_NEXT_BYTE_FLAG = 0x80;
final int SEVEN_BITS_MASK = 0x7f;
final int EIGHT_BITS_MASK = 0xff;
if (v <= 0x7F) {
write((int) v);
} else if (v <= 0x3FFF) {
int b1 = (int) ((v >> 7) & 0x7F);
int b2 = (int) (v & 0x7F);
int b1 = (int) ((v >> 7) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG;
int b2 = (int) (v & SEVEN_BITS_MASK);
write(b1);
write(b2);
} else if (v <= 0x1FFFFF) {
int b1 = (int) ((v >> 14) & 0x7F);
int b2 = (int) ((v >> 7) & 0x7F);
int b3 = (int) (v & 0x7F);
int b1 = (int) ((v >> 14) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG;
int b2 = (int) ((v >> 7) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG;
int b3 = (int) (v & SEVEN_BITS_MASK);
write(b1);
write(b2);
write(b3);
} else if (v <= 0x3FFFFFFF) {
int b1 = (int) ((v >> 21) & 0x7F);
int b2 = (int) ((v >> 14) & 0x7F);
int b3 = (int) ((v >> 7) & 0x7F);
int b4 = (int) (v & 0x7F);
int b1 = (int) ((v >> 21) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG;
int b2 = (int) ((v >> 14) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG;
int b3 = (int) ((v >> 7) & SEVEN_BITS_MASK) | USE_NEXT_BYTE_FLAG;
int b4 = (int) (v & EIGHT_BITS_MASK);
write(b1);
write(b2);
write(b3);
@@ -67,13 +114,252 @@ public class Amf3OutputStream extends OutputStream {
}
}
private void writeUtf8Vr(String val, List<String> stringTable) throws IOException {
int stringIndex = stringTable.indexOf(val);
if (stringIndex == -1) {
if (!val.isEmpty()) {
stringTable.add(val);
}
byte data[] = val.getBytes("UTF-8");
writeU29((data.length << 1) | NO_REFERENCE_FLAG);
writeBytes(data);
} else {
writeU29((stringIndex << 1));
}
}
private void writeByteArray(ByteArrayType val, List<Object> objectTable) throws IOException {
int objectIndex = objectTable.indexOf(val);
if (objectIndex == -1) {
objectTable.add(val);
byte data[] = val.getData();
writeU29((data.length << 1) | NO_REFERENCE_FLAG);
writeBytes(data);
} else {
writeU29((objectIndex << 1));
}
}
private void writeXmlDoc(XmlDocType val, List<Object> objectTable) throws IOException {
int objectIndex = objectTable.indexOf(val);
if (objectIndex == -1) {
objectTable.add(val);
byte data[] = val.getData().getBytes("UTF-8");
writeU29((data.length << 1) | NO_REFERENCE_FLAG);
writeBytes(data);
} else {
writeU29((objectIndex << 1));
}
}
private void writeXml(XmlType val, List<Object> objectTable) throws IOException {
int objectIndex = objectTable.indexOf(val);
if (objectIndex == -1) {
objectTable.add(val);
byte data[] = val.getData().getBytes("UTF-8");
writeU29((data.length << 1) | NO_REFERENCE_FLAG);
writeBytes(data);
} else {
writeU29((objectIndex << 1));
}
}
@Override
public void write(int v) throws IOException {
os.write(v);
}
public void writeValue(Object object) throws IOException {
//TODO:!!!
throw new UnsupportedOperationException("Not implemented yet");
private void writeArray(ArrayType val, Map<String, ObjectTypeSerializeHandler> serializers, List<String> stringTable, List<Traits> traitsTable, List<Object> objectTable) throws IOException, NoSerializerExistsException {
int objectIndex = objectTable.indexOf(val);
if (objectIndex == -1) {
objectTable.add(val);
writeU29((val.getDenseValues().size() << 1) | NO_REFERENCE_FLAG);
for (Pair<String, Object> p : val.getAssociativeValues()) {
writeUtf8Vr(p.getFirst(), stringTable);
writeValue(p.getSecond(), serializers, stringTable, traitsTable, objectTable);
}
writeUtf8Vr("", stringTable);
for (Object v : val.getDenseValues()) {
writeValue(v, serializers, stringTable, traitsTable, objectTable);
}
} else {
writeU29((objectIndex << 1));
}
}
private void writeObject(ObjectType val, Map<String, ObjectTypeSerializeHandler> serializers, List<String> stringTable, List<Traits> traitsTable, List<Object> objectTable) throws IOException, NoSerializerExistsException {
int objectIndex = objectTable.indexOf(val);
if (objectIndex == -1) {
objectTable.add(val);
Traits traits = val.getTraits();
int traitsIndex = traitsTable.indexOf(traits);
if (traitsIndex == -1) {
if (val.isSerialized()) {
writeU29(NO_REFERENCE_FLAG | NO_TRAIT_REFERENCE_FLAG | TRAIT_EXT_FLAG);
writeUtf8Vr(val.getClassName(), stringTable);
if (serializers.containsKey(val.getClassName())) {
serializers.get(val.getClassName()).writeObject(val.getSerializedMembers(), os);
} else if (val.getSerializedData() != null) {
writeBytes(val.getSerializedData());
} else {
throw new NoSerializerExistsException(val.getClassName(), null, null);
}
} else {
traitsTable.add(traits);
writeU29((val.getSealedMembers().size() << 4) | NO_REFERENCE_FLAG | NO_TRAIT_REFERENCE_FLAG | (traits.isDynamic() ? DYNAMIC_FLAG : 0));
writeUtf8Vr(val.getClassName(), stringTable);
for (Pair<String, Object> v : val.getSealedMembers()) {
writeUtf8Vr(v.getFirst(), stringTable);
}
}
} else {
writeU29((traitsIndex << 2) | NO_REFERENCE_FLAG);
}
for (Pair<String, Object> v : val.getSealedMembers()) {
writeValue(v.getSecond(), serializers, stringTable, traitsTable, objectTable);
}
if (traits.isDynamic()) {
for (Pair<String, Object> v : val.getDynamicMembers()) {
writeUtf8Vr(v.getFirst(), stringTable);
writeValue(v.getSecond(), serializers, stringTable, traitsTable, objectTable);
}
writeUtf8Vr("", stringTable);
}
} else {
writeU29((objectIndex << 1));
}
}
public void writeValue(Object object) throws IOException, NoSerializerExistsException {
writeValue(object, new HashMap<>());
}
public void writeValue(Object object, Map<String, ObjectTypeSerializeHandler> serializers) throws IOException, NoSerializerExistsException {
writeValue(object, serializers, new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
}
private void writeValue(Object object, Map<String, ObjectTypeSerializeHandler> serializers, List<String> stringTable, List<Traits> traitsTable, List<Object> objectTable) throws IOException, NoSerializerExistsException {
if (object == BasicType.UNDEFINED) {
writeU8(Marker.UNDEFINED);
} else if (object == BasicType.NULL) {
writeU8(Marker.NULL);
} else if (object == Boolean.FALSE) {
writeU8(Marker.FALSE);
} else if (object == Boolean.TRUE) {
writeU8(Marker.TRUE);
} else if (object == BasicType.UNKNOWN) {
//Nothing
} else if (object instanceof Long) {
writeU8(Marker.INTEGER);
writeU29((Long) object);
} else if (object instanceof Double) {
writeU8(Marker.DOUBLE);
writeDouble((Double) object);
} else if (object instanceof String) {
writeU8(Marker.STRING);
writeUtf8Vr((String) object, stringTable);
} else if (object instanceof XmlDocType) {
writeU8(Marker.XML_DOC);
writeXmlDoc((XmlDocType) object, objectTable);
} else if (object instanceof DateType) {
writeU8(Marker.DATE);
int dateIndex = objectTable.indexOf(object);
DateType val = (DateType) object;
if (dateIndex == -1) {
objectTable.add(val);
writeU29(NO_REFERENCE_FLAG);
writeDouble(val.getVal());
} else {
writeU29(dateIndex << 1);
}
} else if (object instanceof ArrayType) {
writeU8(Marker.ARRAY);
writeArray((ArrayType) object, serializers, stringTable, traitsTable, objectTable);
} else if (object instanceof ObjectType) {
writeU8(Marker.OBJECT);
writeObject((ObjectType) object, serializers, stringTable, traitsTable, objectTable);
} else if (object instanceof XmlType) {
writeU8(Marker.XML);
writeXml((XmlType) object, objectTable);
} else if (object instanceof ByteArrayType) {
writeU8(Marker.BYTE_ARRAY);
writeByteArray((ByteArrayType) object, objectTable);
} else if (object instanceof VectorIntType) {
writeU8(Marker.VECTOR_INT);
int vectorIndex = objectTable.indexOf(object);
VectorIntType val = (VectorIntType) object;
if (vectorIndex == -1) {
objectTable.add(val);
writeU29((val.getValues().size() << 1) | NO_REFERENCE_FLAG);
writeU8(val.isFixed() ? 1 : 0);
for (long v : val.getValues()) {
writeU32(v);
}
} else {
writeU29(vectorIndex << 1);
}
} else if (object instanceof VectorUIntType) {
writeU8(Marker.VECTOR_UINT);
int vectorIndex = objectTable.indexOf(object);
VectorUIntType val = (VectorUIntType) object;
if (vectorIndex == -1) {
objectTable.add(val);
writeU29((val.getValues().size() << 1) | NO_REFERENCE_FLAG);
writeU8(val.isFixed() ? 1 : 0);
for (long v : val.getValues()) {
writeU32(v);
}
} else {
writeU29(vectorIndex << 1);
}
} else if (object instanceof VectorDoubleType) {
writeU8(Marker.VECTOR_DOUBLE);
int vectorIndex = objectTable.indexOf(object);
VectorDoubleType val = (VectorDoubleType) object;
if (vectorIndex == -1) {
objectTable.add(val);
writeU29((val.getValues().size() << 1) | NO_REFERENCE_FLAG);
writeU8(val.isFixed() ? 1 : 0);
for (double v : val.getValues()) {
writeDouble(v);
}
} else {
writeU29(vectorIndex << 1);
}
} else if (object instanceof VectorObjectType) {
writeU8(Marker.VECTOR_OBJECT);
int vectorIndex = objectTable.indexOf(object);
VectorObjectType val = (VectorObjectType) object;
if (vectorIndex == -1) {
objectTable.add(val);
writeU29((val.getValues().size() << 1) | NO_REFERENCE_FLAG);
writeU8(val.isFixed() ? 1 : 0);
writeUtf8Vr(val.getTypeName(), stringTable);
for (Object v : val.getValues()) {
writeValue(v, serializers, stringTable, traitsTable, objectTable);
}
} else {
writeU29(vectorIndex << 1);
}
} else if (object instanceof DictionaryType) {
writeU8(Marker.DICTIONARY);
int dictionaryIndex = objectTable.indexOf(object);
DictionaryType val = (DictionaryType) object;
if (dictionaryIndex == -1) {
objectTable.add(val);
writeU29((val.getPairs().size() << 1) | NO_REFERENCE_FLAG);
writeU8(val.hasWeakKeys() ? 1 : 0);
for (Pair<Object, Object> p : val.getPairs()) {
writeValue(p.getFirst(), serializers, stringTable, traitsTable, objectTable);
writeValue(p.getSecond(), serializers, stringTable, traitsTable, objectTable);
}
} else {
writeU29(dictionaryIndex << 1);
}
} else {
throw new UnsupportedValueTypeException(object.getClass());
}
}
}

View File

@@ -1,16 +0,0 @@
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,26 @@
package com.jpexs.decompiler.flash.amf.amf3;
public class UnsupportedValueTypeException extends RuntimeException {
private Integer marker = null;
private Class cls = null;
public UnsupportedValueTypeException(Class cls) {
super("Unsupported type of value - class: " + cls.getSimpleName());
this.cls = cls;
}
public UnsupportedValueTypeException(int marker) {
super("Unsupported type of value - marker: 0x" + Integer.toHexString(marker));
this.marker = marker;
}
public Integer getMarker() {
return marker;
}
public Class getUnsupportedClass() {
return this.cls;
}
}

View File

@@ -2,6 +2,7 @@ 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.decompiler.flash.amf.amf3.Traits;
import com.jpexs.helpers.Helper;
import java.util.ArrayList;
import java.util.List;
@@ -9,21 +10,24 @@ 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;
private Traits traits;
public boolean isSerialized() {
return serialized;
}
public ObjectType(String className, byte[] serializedData, List<Pair<String, Object>> serializedMembers) {
this.className = className;
public Traits getTraits() {
return traits;
}
public ObjectType(Traits traits, byte[] serializedData, List<Pair<String, Object>> serializedMembers) {
this.traits = traits;
this.serializedData = serializedData;
this.serializedMembers = serializedMembers;
this.dynamicMembers = new ArrayList<>();
@@ -31,17 +35,16 @@ public class ObjectType implements WithSubValues {
this.serialized = true;
}
public ObjectType(boolean dynamic, List<Pair<String, Object>> sealedMembers, List<Pair<String, Object>> dynamicMembers, String className) {
this.dynamic = dynamic;
public ObjectType(Traits traits, List<Pair<String, Object>> sealedMembers, List<Pair<String, Object>> dynamicMembers) {
this.sealedMembers = sealedMembers;
this.dynamicMembers = dynamicMembers;
this.className = className;
this.serializedMembers = new ArrayList<>();
this.serialized = false;
this.traits = traits;
}
public boolean isDynamic() {
return dynamic;
return traits.isDynamic();
}
public List<Pair<String, Object>> getDynamicMembers() {
@@ -53,23 +56,7 @@ public class ObjectType implements WithSubValues {
}
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;
return traits.getClassName();
}
@Override