structron
Version:
A reader, writer and validator for binary data structures
209 lines (176 loc) • 7.28 kB
JavaScript
const Struct = require('./index.js');
function testImage() {
// --- First we need to define the custom datatypes ---
// Define via another struct
const Dimensions = new Struct()
.addMember(Struct.TYPES.INT, "width")
.addMember(Struct.TYPES.INT, "height");
// Define via custom function
const rgb565 = {
read(buffer, offset) {
let short = buffer.readUInt16LE(offset);
return {
r: (short & 0b1111100000000000) >> 11,
g: (short & 0b0000011111100000) >> 5,
b: (short & 0b0000000000011111)
}
},
write(value, context, offset) {
let val = (value.r << 11) | (value.g << 5) | value.b;
context.buffer.writeUInt16LE(val, offset);
},
SIZE: 2 // Size in bytes
};
const Pixel = new Struct()
.addMember(rgb565, "color")
.addMember(Struct.TYPES.BYTE, "alpha");
// --- Then we define our file header ---
const Image = new Struct()
.addMember(Struct.TYPES.INT, "magicNumber")
.addMember(Dimensions, "size")
.addMember(Struct.TYPES.INT, "pixelOffset")
.addMember(Struct.TYPES.INT, "pixelNumber")
.addMember(Struct.TYPES.INT, "nameIndex")
.addMember(Struct.TYPES.SKIP(8), "unused")
.addReference(Struct.TYPES.NULL_TERMINATED_STRING(), "name", "nameIndex")
.addArray(Pixel, "pixels", "pixelOffset", "pixelNumber");
// Create image data
let data = Buffer.alloc(85);
data.writeInt32LE(0x24011999, 0);
data.writeInt32LE(4, 4);
data.writeInt32LE(4, 8);
data.writeInt32LE(32, 12);
data.writeInt32LE(16, 16);
data.writeInt32LE(80, 20); // Name address
// Skip 24 and 28
for (let i = 32; i < 80; i++) {
data.writeUInt8((i * 9) % 255, i);
}
data.writeInt8(0x54, 80);
data.writeInt8(0x69, 81);
data.writeInt8(0x6e, 82);
data.writeInt8(0x61, 83);
// Read image data
let out = Image.readContext(data, 0, { hideReferenceValues: true });
if ("nameIndex" in out.data) return false;
let jsonData = `{"magicNumber":604051865,"size":{"width":4,"height":4},"unused":null,"pixels":[{"color":{"r":5,"g":17,"b":1},"alpha":51},{"color":{"r":8,"g":41,"b":28},"alpha":78},{"color":{"r":12,"g":2,"b":23},"alpha":105},{"color":{"r":15,"g":27,"b":18},"alpha":132},{"color":{"r":18,"g":52,"b":13},"alpha":159},{"color":{"r":22,"g":13,"b":8},"alpha":186},{"color":{"r":25,"g":38,"b":3},"alpha":213},{"color":{"r":28,"g":62,"b":30},"alpha":240},{"color":{"r":0,"g":31,"b":25},"alpha":12},{"color":{"r":3,"g":48,"b":21},"alpha":39},{"color":{"r":7,"g":9,"b":16},"alpha":66},{"color":{"r":10,"g":34,"b":11},"alpha":93},{"color":{"r":13,"g":59,"b":6},"alpha":120},{"color":{"r":17,"g":20,"b":1},"alpha":147},{"color":{"r":20,"g":44,"b":28},"alpha":174},{"color":{"r":24,"g":5,"b":23},"alpha":201}],"name":"Tina"}`;
if (JSON.stringify(out.data) != jsonData) return false;
// Write image data
let ctx = Image.write(JSON.parse(jsonData));
let buffer = ctx.buffer;
// Read image data again from written buffer
ctx = Image.readContext(buffer, 0, { hideReferenceValues: true });
let newJsonData = JSON.stringify(ctx.data);
return (newJsonData == jsonData);
}
function testWriteOnePixel() {
const Dimensions = new Struct()
.addMember(Struct.TYPES.INT, "width")
.addMember(Struct.TYPES.INT, "height");
const rgb565 = {
read(buffer, offset) {
let short = buffer.readUInt16LE(offset);
return {
r: (short & 0b1111100000000000) >> 11,
g: (short & 0b0000011111100000) >> 5,
b: (short & 0b0000000000011111)
}
},
write(value, context, offset) {
let val = (value.r << 11) | (value.g << 5) | value.b;
context.buffer.writeUInt16LE(val, offset);
},
SIZE: 2
};
const Pixel = new Struct()
.addMember(rgb565, "color")
.addMember(Struct.TYPES.BYTE, "alpha");
const Image = new Struct()
.addMember(Struct.TYPES.INT, "magicNumber")
.addMember(Dimensions, "size")
.addMember(Struct.TYPES.INT, "pixelOffset")
.addMember(Struct.TYPES.INT, "pixelNumber")
.addMember(Struct.TYPES.INT, "nameIndex")
.addMember(Struct.TYPES.SKIP(8), "unused")
.addReference(Struct.TYPES.NULL_TERMINATED_STRING(), "name", "nameIndex")
.addArray(Pixel, "pixels", "pixelOffset", "pixelNumber");
let object = {
magicNumber: 604051865,
size: { width: 1, height: 1},
pixels: [{color: {r: 2, g: 3, b: 2}, alpha: 255}],
name: "One Pixel"
};
let ctx = Image.write(object);
return ctx.buffer.toString("base64") == "mRkBJAEAAAABAAAAIAAAAAEAAAAjAAAAAAAAAAAAAABiEP9PbmUgUGl4ZWwA";
}
/**
* Tests overlapping arrays and usage
*/
function testReadOverlappingArrays() {
let buffer = Buffer.alloc(64);
buffer.writeUInt8(16, 0);
buffer.writeUInt8(48, 1);
buffer.writeUInt8(32, 2);
buffer.writeUInt8(16, 3);
let testStruct = new Struct()
.addMember(Struct.TYPES.BYTE, "aPos")
.addMember(Struct.TYPES.BYTE, "aLen")
.addMember(Struct.TYPES.BYTE, "bPos")
.addMember(Struct.TYPES.BYTE, "bLen")
.addArray(Struct.TYPES.BYTE, "a", "aPos", "aLen")
.addArray(Struct.TYPES.BYTE, "b", "bPos", "bLen");
let ctx = testStruct.readContext(buffer, 0, { monitorUsage: true });
return ctx.errors.length == 1 && ctx.getUsage() == 52;
}
function testRuleEqual() {
let buffer = Buffer.alloc(64);
buffer.writeUInt32LE(0x12657832, 0);
let testStruct = new Struct()
.addMember(Struct.TYPES.UINT, "magic")
.addRule(Struct.RULES.EQUAL("magic", 308639794))
.addRule(Struct.RULES.EQUAL("magic", 42));
let report = testStruct.report(buffer);
return report.errors.length == 1;
}
function testCharType() {
let buffer = Buffer.alloc(8);
buffer[0] = 2;
buffer[1] = 6;
buffer[2] = 76;
buffer[3] = 69;
buffer[4] = 79;
buffer[5] = 78;
buffer[6] = 73;
buffer[7] = 69;
let testStruct = new Struct()
.addMember(Struct.TYPES.BYTE, "offset")
.addMember(Struct.TYPES.BYTE, "length")
.addArray(Struct.TYPES.CHAR, "chars", "offset", "length");
let report = testStruct.report(buffer);
return report.data.chars.join("") == "LEONIE";
}
function testRecursiveRead() {
let buffer = Buffer.alloc(16);
buffer.writeUInt32LE(2206, 0);
buffer.writeUInt32LE(8, 4);
buffer.writeUInt32LE(1994, 8);
buffer.writeUInt32LE(0, 12);
let node = new Struct()
.addMember(Struct.TYPES.INT, "exampleValue")
.addMember(Struct.TYPES.INT, "partnerOffset");
node.addReference(node, "partner", "partnerOffset");
let report = node.report(buffer);
return (report.data.partner.exampleValue === report.data.partner.partner.partner.exampleValue);
}
function executeTest(name, test) {
console.log(name, test() ? "\x1b[32mSuccess!" : "\x1b[31mFailed!", "\x1b[37m");
}
console.log("--- Reading tests ---")
executeTest("Equal rule", testRuleEqual);
executeTest("Character type:", testCharType);
executeTest("Recursive:", testRecursiveRead);
executeTest("Overlapping Arrays:", testReadOverlappingArrays);
console.log("--- Writing tests ---");
executeTest("One pixel Image:", testWriteOnePixel);
console.log("--- Common ---");
executeTest("Image:", testImage);