Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 0bcb9b80 authored by Simon Chan's avatar Simon Chan
Browse files

fix(struct): `buffer` and `struct` may not have correct size

parent f8b40e83
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
---
"@yume-chan/struct": patch
---

Fix `buffer` and `struct` may not have correct size
+26 −21
Original line number Diff line number Diff line
@@ -3,36 +3,41 @@ import { describe, it } from "node:test";

import { buffer } from "./buffer.js";
import type { ExactReadable } from "./readable.js";
import { ExactReadableEndedError } from "./readable.js";
import {
    ExactReadableEndedError,
    Uint8ArrayExactReadable,
} from "./readable.js";
import { struct } from "./struct.js";

describe("buffer", () => {
    describe("fixed size", () => {
        it("should deserialize", () => {
            const A = struct({ value: buffer(10) }, { littleEndian: false });
            const reader: ExactReadable = {
                position: 0,
                readExactly() {
                    return new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
                },
            };
            assert.deepStrictEqual(A.deserialize(reader), {
                value: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
        it("should have correct size", () => {
            const a = buffer(10);
            assert.strictEqual(a.size, 10);
        });

        it("should deserialize", () => {
            const a = buffer(10);
            const data = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
            assert.deepStrictEqual(
                a.deserialize(new Uint8ArrayExactReadable(data), {
                    dependencies: {} as never,
                    littleEndian: true,
                }),
                data,
            );
        });

        it("should throw for not enough data", () => {
            const A = struct({ value: buffer(10) }, { littleEndian: false });
            const reader: ExactReadable = {
                position: 0,
                readExactly() {
                    (this as { position: number }).position = 5;
                    throw new ExactReadableEndedError();
                },
            };
            const a = buffer(10);
            const data = new Uint8Array([1, 2, 3, 4, 5]);
            assert.throws(
                () => A.deserialize(reader),
                /The underlying readable was ended before the struct was fully deserialized/,
                () =>
                    a.deserialize(new Uint8ArrayExactReadable(data), {
                        dependencies: {} as never,
                        littleEndian: true,
                    }),
                /Error: ExactReadable ended/,
            );
        });

+2 −2
Original line number Diff line number Diff line
@@ -99,7 +99,7 @@ function _buffer(
            }

            return field(
                0,
                lengthOrField,
                "byob",
                (value, { buffer, index }) => {
                    buffer.set(value.slice(0, lengthOrField), index);
@@ -131,7 +131,7 @@ function _buffer(
        }

        return field(
            0,
            lengthOrField,
            "byob",
            (value, { buffer, index }) => {
                buffer.set(value.slice(0, lengthOrField), index);
+52 −1
Original line number Diff line number Diff line
import * as assert from "node:assert";
import { describe, it } from "node:test";

import { u16, u8 } from "./number.js";
import { u16, u32, u8 } from "./number.js";
import { Uint8ArrayExactReadable } from "./readable.js";
import { string } from "./string.js";
import { struct } from "./struct.js";

describe("Struct", () => {
@@ -30,4 +31,54 @@ describe("Struct", () => {
            },
        );
    });

    describe("type", () => {
        it("should be `byob` when empty", () => {
            const A = struct({}, { littleEndian: true });
            assert.strictEqual(A.type, "byob");
        });

        it("should be `byob` if all fields are byob", () => {
            const A = struct({ a: u8 }, { littleEndian: true });
            assert.strictEqual(A.type, "byob");

            const B = struct({ a: u8, b: u16 }, { littleEndian: true });
            assert.strictEqual(B.type, "byob");

            const C = struct(
                { a: u8, b: u16, c: string(10) },
                { littleEndian: true },
            );
            assert.strictEqual(C.type, "byob");
        });

        it("should be `default` if any field is default", () => {
            const A = struct({ a: string(u32) }, { littleEndian: true });
            assert.strictEqual(A.type, "default");

            const B = struct(
                { a: string(u32), b: u32 },
                { littleEndian: true },
            );
            assert.strictEqual(B.type, "default");
        });
    });

    describe("size", () => {
        it("should be 0 when empty", () => {
            const A = struct({}, { littleEndian: true });
            assert.strictEqual(A.size, 0);
        });

        it("should be sum of all fields", () => {
            const A = struct({ a: u8, b: u16 }, { littleEndian: true });
            assert.strictEqual(A.size, 3);

            const B = struct({ a: string(10) }, { littleEndian: true });
            assert.strictEqual(B.size, 10);

            const C = struct({ a: string(u32) }, { littleEndian: true });
            assert.strictEqual(C.size, 4);
        });
    });
});
+12 −3
Original line number Diff line number Diff line
@@ -100,7 +100,15 @@ export function struct<
    },
): Struct<Fields, Extra, PostDeserialize> {
    const fieldList = Object.entries(fields);
    const size = fieldList.reduce((sum, [, field]) => sum + field.size, 0);

    let size = 0;
    let byob = true;
    for (const [, field] of fieldList) {
        size += field.size;
        if (byob && field.type !== "byob") {
            byob = false;
        }
    }

    const littleEndian = options.littleEndian;
    const extra = options.extra
@@ -109,10 +117,11 @@ export function struct<

    return {
        littleEndian,
        type: "byob",
        fields,
        size,
        extra: options.extra,

        type: byob ? "byob" : "default",
        size,
        serialize(
            source: FieldsInit<Fields>,
            bufferOrContext?: Uint8Array | StructSerializeContext,
Loading