143 lines
3.5 KiB
JavaScript
143 lines
3.5 KiB
JavaScript
/**
|
|
* @author n1474335 [n1474335@gmail.com]
|
|
* @copyright Crown Copyright 2022
|
|
* @license Apache-2.0
|
|
*/
|
|
|
|
import Operation from "../Operation.mjs";
|
|
import Utils from "../Utils.mjs";
|
|
import "reflect-metadata"; // Required as a shim for the amf library
|
|
import { AMF0 } from "@astronautlabs/amf";
|
|
|
|
const AMF3_MARKER = {
|
|
Null: 0x01,
|
|
False: 0x02,
|
|
True: 0x03,
|
|
Double: 0x05,
|
|
String: 0x06,
|
|
Array: 0x09,
|
|
Object: 0x0a
|
|
};
|
|
|
|
/**
|
|
* @param {number} value
|
|
* @returns {number[]}
|
|
*/
|
|
function encodeU29(value) {
|
|
if (value < 0x80) {
|
|
return [value];
|
|
}
|
|
if (value < 0x4000) {
|
|
return [
|
|
((value >> 7) & 0x7f) | 0x80,
|
|
value & 0x7f
|
|
];
|
|
}
|
|
if (value < 0x200000) {
|
|
return [
|
|
((value >> 14) & 0x7f) | 0x80,
|
|
((value >> 7) & 0x7f) | 0x80,
|
|
value & 0x7f
|
|
];
|
|
}
|
|
return [
|
|
((value >> 22) & 0x7f) | 0x80,
|
|
((value >> 15) & 0x7f) | 0x80,
|
|
((value >> 8) & 0x7f) | 0x80,
|
|
value & 0xff
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @returns {number[]}
|
|
*/
|
|
function encodeAMF3Utf8(value) {
|
|
const bytes = Utils.strToUtf8ByteArray(value);
|
|
return encodeU29((bytes.length << 1) | 1).concat(bytes);
|
|
}
|
|
|
|
/**
|
|
* @param {number} value
|
|
* @returns {number[]}
|
|
*/
|
|
function encodeAMF3Double(value) {
|
|
const bytes = new Uint8Array(8);
|
|
new DataView(bytes.buffer).setFloat64(0, value, false);
|
|
return [AMF3_MARKER.Double].concat(Array.from(bytes));
|
|
}
|
|
|
|
/**
|
|
* @param {JSON} value
|
|
* @returns {number[]}
|
|
*/
|
|
function encodeAMF3Value(value) {
|
|
if (value === null) return [AMF3_MARKER.Null];
|
|
if (value === false) return [AMF3_MARKER.False];
|
|
if (value === true) return [AMF3_MARKER.True];
|
|
if (typeof value === "number") return encodeAMF3Double(value);
|
|
if (typeof value === "string") return [AMF3_MARKER.String].concat(encodeAMF3Utf8(value));
|
|
|
|
if (Array.isArray(value)) {
|
|
return [
|
|
AMF3_MARKER.Array,
|
|
...encodeU29((value.length << 1) | 1),
|
|
0x01,
|
|
...value.flatMap(encodeAMF3Value)
|
|
];
|
|
}
|
|
|
|
const keys = Object.keys(value);
|
|
return [
|
|
AMF3_MARKER.Object,
|
|
...encodeU29((keys.length << 4) | 0x03),
|
|
0x01,
|
|
...keys.flatMap(encodeAMF3Utf8),
|
|
...keys.flatMap(key => encodeAMF3Value(value[key]))
|
|
];
|
|
}
|
|
|
|
/**
|
|
* AMF Encode operation
|
|
*/
|
|
class AMFEncode extends Operation {
|
|
|
|
/**
|
|
* AMFEncode constructor
|
|
*/
|
|
constructor() {
|
|
super();
|
|
|
|
this.name = "AMF Encode";
|
|
this.module = "Encodings";
|
|
this.description = "Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives.";
|
|
this.infoURL = "https://wikipedia.org/wiki/Action_Message_Format";
|
|
this.inputType = "JSON";
|
|
this.outputType = "ArrayBuffer";
|
|
this.args = [
|
|
{
|
|
name: "Format",
|
|
type: "option",
|
|
value: ["AMF0", "AMF3"],
|
|
defaultIndex: 1
|
|
}
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param {JSON} input
|
|
* @param {Object[]} args
|
|
* @returns {ArrayBuffer}
|
|
*/
|
|
run(input, args) {
|
|
const [format] = args;
|
|
const output = format === "AMF0" ?
|
|
AMF0.Value.any(input).serialize() :
|
|
Uint8Array.from(encodeAMF3Value(input));
|
|
return output.buffer;
|
|
}
|
|
|
|
}
|
|
|
|
export default AMFEncode;
|