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 writeU8(int v) throws IOException {
write(v);
}
public void writeU16(int v) throws IOException {
int b1 = (v >> 8) & 0xff;
int b2 = v & 0xff;
write(b1);
write(b2);
}
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);
int b4 = (int) (v & 0xff);
write(b1);
write(b2);
write(b3);
write(b4);
}
public void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
}
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) & 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) & 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) & 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);
write(b4);
} else {
throw new IllegalArgumentException("Value too long");
}
}
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);
}
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 (String key : val.associativeKeySet()) {
writeUtf8Vr(key, stringTable);
writeValue(val.getAssociative(key), 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.sealedMembersSize() << 4) | NO_REFERENCE_FLAG | NO_TRAIT_REFERENCE_FLAG | (traits.isDynamic() ? DYNAMIC_FLAG : 0));
writeUtf8Vr(val.getClassName(), stringTable);
for (String key : val.sealedMembersKeySet()) {
writeUtf8Vr(key, stringTable);
}
}
} else {
writeU29((traitsIndex << 2) | NO_REFERENCE_FLAG);
}
for (String key : val.sealedMembersKeySet()) {
writeValue(val.getSealedMember(key), serializers, stringTable, traitsTable, objectTable);
}
if (traits.isDynamic()) {
for (String key : val.dynamicMembersKeySet()) {
writeUtf8Vr(key, stringTable);
writeValue(val.getDynamicMember(key), 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.size() << 1) | NO_REFERENCE_FLAG);
writeU8(val.hasWeakKeys() ? 1 : 0);
for (Object key : val.keySet()) {
writeValue(key, serializers, stringTable, traitsTable, objectTable);
writeValue(val.get(key), serializers, stringTable, traitsTable, objectTable);
}
} else {
writeU29(dictionaryIndex << 1);
}
} else {
throw new UnsupportedValueTypeException(object.getClass());
}
}
}