fix AMF3 JSON value encoding
This commit is contained in:
parent
7a28e0534b
commit
1c73e03f5d
@ -5,8 +5,97 @@
|
||||
*/
|
||||
|
||||
import Operation from "../Operation.mjs";
|
||||
import Utils from "../Utils.mjs";
|
||||
import "reflect-metadata"; // Required as a shim for the amf library
|
||||
import { AMF0, AMF3 } from "@astronautlabs/amf";
|
||||
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
|
||||
@ -42,8 +131,9 @@ class AMFEncode extends Operation {
|
||||
*/
|
||||
run(input, args) {
|
||||
const [format] = args;
|
||||
const handler = format === "AMF0" ? AMF0 : AMF3;
|
||||
const output = handler.Value.any(input).serialize();
|
||||
const output = format === "AMF0" ?
|
||||
AMF0.Value.any(input).serialize() :
|
||||
Uint8Array.from(encodeAMF3Value(input));
|
||||
return output.buffer;
|
||||
}
|
||||
|
||||
|
||||
100
tests/operations/tests/AMF.mjs
Normal file
100
tests/operations/tests/AMF.mjs
Normal file
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* AMF tests.
|
||||
*
|
||||
* @copyright Crown Copyright 2026
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
import TestRegister from "../../lib/TestRegister.mjs";
|
||||
|
||||
TestRegister.addTests([
|
||||
{
|
||||
name: "AMF3 Encode: object string",
|
||||
input: "{\"a\": \"test\"}",
|
||||
expectedOutput: "0a13010361060974657374",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "AMF Encode",
|
||||
args: ["AMF3"]
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "AMF3 Encode: object boolean true",
|
||||
input: "{\"a\": true}",
|
||||
expectedOutput: "0a1301036103",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "AMF Encode",
|
||||
args: ["AMF3"]
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "AMF3 Encode: object boolean false",
|
||||
input: "{\"a\": false}",
|
||||
expectedOutput: "0a1301036102",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "AMF Encode",
|
||||
args: ["AMF3"]
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "AMF3 Encode: object null",
|
||||
input: "{\"a\": null}",
|
||||
expectedOutput: "0a1301036101",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "AMF Encode",
|
||||
args: ["AMF3"]
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "AMF3 Encode: object array",
|
||||
input: "{\"a\": []}",
|
||||
expectedOutput: "0a13010361090101",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "AMF Encode",
|
||||
args: ["AMF3"]
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "AMF3 Encode: object nested JSON values",
|
||||
input: "{\"a\": [true, false, null, \"x\", 1]}",
|
||||
expectedOutput: "0a13010361090b01030201060378053ff0000000000000",
|
||||
recipeConfig: [
|
||||
{
|
||||
op: "AMF Encode",
|
||||
args: ["AMF3"]
|
||||
},
|
||||
{
|
||||
op: "To Hex",
|
||||
args: ["None", 0]
|
||||
}
|
||||
],
|
||||
},
|
||||
]);
|
||||
Loading…
x
Reference in New Issue
Block a user