binutils64 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: binutils-reviewer
3
+ description: Reviews changes to the binutils64 library (binutils.js, README.md, tests) for binary-correctness bugs and convention adherence. Use proactively after any edit to binutils.js or when asked to review a Read/Write change, a new type, or a PR touching this library.
4
+ tools: Read, Grep, Glob, Bash
5
+ model: sonnet
6
+ ---
7
+
8
+ You are a focused code reviewer for **binutils64**, a single-file ES5 CommonJS
9
+ library (`binutils.js`) providing .NET-style `BinaryReader` and `BinaryWriter`
10
+ with selectable endianness. Your job is to catch binary-correctness bugs and
11
+ convention drift in changes to this library. Be precise and skeptical — this code
12
+ already shipped a real width-mismatch bug (the 64-bit writers emit only 32 bits).
13
+
14
+ ## What to review
15
+
16
+ Read `binutils.js`, the relevant `README.md` sections, and any `test/` files. Use
17
+ `git diff` (via Bash) to scope the change when reviewing edits. If a round-trip is
18
+ in doubt, actually run it: `node -e "..."` or `node --test`.
19
+
20
+ ## Correctness checklist (highest priority)
21
+
22
+ 1. **Write width == read width.** For every `WriteX`/`ReadX` pair, the number of
23
+ bytes written must equal the bytes read and the declared width `N`. Flag any
24
+ writer using a narrower method than its buffer size (e.g. `writeUInt32LE` into
25
+ an 8-byte buffer — this was the original `WriteUInt64`/`WriteInt64` bug, since
26
+ fixed). 64-bit writers use `writeBigUInt64*`/`writeBigInt64*` and coerce with
27
+ `BigInt(p_Value)`. Prefer confirming with a real round-trip.
28
+ 2. **Endianness branches** present and correct for every multi-byte method: the
29
+ `'little'` branch uses the `*LE` variant, the else branch uses `*BE`. Single-byte
30
+ methods (`*UInt8`/`*Int8`) must NOT have an endianness branch.
31
+ 3. **Reader is destructive and consistent**: each `ReadX` slices exactly `N` bytes
32
+ off `this.ByteBuffer` and advances `this.Position` by exactly `N`. `this.Length`
33
+ must NOT change on reads (it reflects the original buffer length).
34
+ 4. **Writer accounting**: each `WriteX` increases `this.Length` by exactly `N` and
35
+ passes the updated `this.Length` as the `Buffer.concat` total-length argument.
36
+ 5. **Out-of-range reads return the zero value** (`0`, `0.0`, or empty `Buffer`) and
37
+ never throw. Verify the length guard matches the width.
38
+ 6. **Signed/unsigned and BigInt**: signed types use the signed Buffer methods;
39
+ 64-bit reads return `bigint` (`readBig*64*`). Watch for sign/precision errors.
40
+
41
+ ## Convention checklist
42
+
43
+ - ES5 only: `var`, prototype assignment. No `let`/`const`/classes/arrow functions.
44
+ - `PascalCase` method and property names; parameters prefixed `p_`; locals `s_`.
45
+ - `new Buffer(...)` is intentional for `node >=0.12`. Do NOT recommend
46
+ `Buffer.alloc`/`Buffer.from` unless the change also raises `engines.node`.
47
+ - New/changed public methods must have matching `README.md` entries with the
48
+ existing phrasing, and a round-trip test.
49
+ - Methods placed beside their family in the file, ordered consistently.
50
+
51
+ ## Output format
52
+
53
+ Report findings grouped by severity. For each: a one-line title, the
54
+ `binutils.js:line` reference, why it is wrong, and the concrete fix. Lead with
55
+ **Correctness** issues, then **Conventions**, then **Docs/Tests**. State a
56
+ confidence level and only raise convention nits if no correctness issue is
57
+ outstanding. If you ran a round-trip to confirm, show the command and result. If
58
+ nothing is wrong, say so plainly — do not invent issues.
@@ -0,0 +1,21 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(git checkout *)",
5
+ "Bash(node *)",
6
+ "Bash(echo \"exit code: $?\")",
7
+ "Read(//tmp/**)",
8
+ "Bash(echo \"exit: $?\")",
9
+ "mcp__claude_ai_Context7__resolve-library-id",
10
+ "mcp__claude_ai_Context7__query-docs",
11
+ "Bash(python3 -c \"import yaml,sys; yaml.safe_load\\(open\\('.github/workflows/test.yml'\\)\\); print\\('YAML OK'\\)\")",
12
+ "Bash(git add *)",
13
+ "Bash(git commit -m 'fix 64-bit writers and WriteBytes type guard *)",
14
+ "Bash(git commit -m 'add test suite using node:test *)",
15
+ "Bash(git commit -m 'add github actions workflow to run tests *)",
16
+ "Bash(git commit -m 'add project docs and claude tooling *)",
17
+ "Bash(git push *)",
18
+ "Bash(git commit -m 'rewrite readme with full api reference and examples *)"
19
+ ]
20
+ }
21
+ }
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: add-binary-type
3
+ description: Add a new matched Read/Write method pair to binutils.js following the library's exact ES5 conventions, then update README.md and add a round-trip test. Use when asked to support a new numeric/binary type (e.g. a new width, a string type, a boolean) in the BinaryReader/BinaryWriter classes.
4
+ ---
5
+
6
+ # Adding a binary type to binutils64
7
+
8
+ This library exposes mirrored methods on `BinaryReader` and `BinaryWriter` in the
9
+ single file `binutils.js`. Every supported type has a `ReadX` and a matching
10
+ `WriteX`. Adding a type means editing both prototypes, updating `README.md`, and
11
+ adding a round-trip test. Follow the templates below **exactly** — style
12
+ deviations and width mismatches are the main source of bugs here (this library
13
+ previously shipped a low-32-bit width bug in the 64-bit writers — don't repeat it).
14
+
15
+ ## Hard rules (match existing code)
16
+
17
+ - ES5 only: `var`, prototype assignment. No `let`/`const`/classes/arrow functions.
18
+ - Method names are `PascalCase`: `ReadUInt24`, `WriteBool`, etc.
19
+ - Parameters are prefixed `p_` (`p_Value`); locals are prefixed `s_` (`s_Val`,
20
+ `s_TempBuffer`).
21
+ - Keep `new Buffer(...)` — the package targets `node >=0.12`. Do not switch to
22
+ `Buffer.alloc`/`Buffer.from` unless `package.json` `engines.node` is also raised.
23
+ - **The write width MUST equal the read width.** When the type is wider than 32
24
+ bits, use the correct wide Buffer method (`writeBigUInt64LE`, etc.), NOT
25
+ `writeUInt32LE` — using a 32-bit write into an 8-byte buffer was the original
26
+ 64-bit writer bug (now fixed). 64-bit values are `BigInt`; coerce with
27
+ `BigInt(p_Value)` so both `Number` and `BigInt` arguments work.
28
+
29
+ ## Reader method template
30
+
31
+ Out-of-range reads return `0` (or `0.0` / empty `Buffer`) — they do not throw.
32
+ Reads are destructive: slice the consumed bytes off `this.ByteBuffer` and advance
33
+ `this.Position` by the byte width `N`.
34
+
35
+ ```javascript
36
+ ReadTYPE: function() {
37
+ if (this.ByteBuffer.length < N) {
38
+ return 0;
39
+ }
40
+
41
+ var s_Val = (this.Endianness == 'little') ? this.ByteBuffer.readTYPELE(0) : this.ByteBuffer.readTYPEBE(0);
42
+ this.ByteBuffer = this.ByteBuffer.slice(N);
43
+ this.Position += N;
44
+ return s_Val;
45
+ },
46
+ ```
47
+
48
+ For a single-byte type there is no endianness branch (see `ReadUInt8`/`ReadInt8`),
49
+ and use `++this.Position;`.
50
+
51
+ ## Writer method template
52
+
53
+ Allocate an `N`-byte temp buffer, write with the correct-width method honoring
54
+ endianness, grow `this.Length` by `N`, and concat.
55
+
56
+ ```javascript
57
+ WriteTYPE: function(p_Value) {
58
+ var s_TempBuffer = new Buffer(N);
59
+ if (this.Endianness == 'little') {
60
+ s_TempBuffer.writeTYPELE(p_Value, 0);
61
+ } else {
62
+ s_TempBuffer.writeTYPEBE(p_Value, 0);
63
+ }
64
+ this.Length += N;
65
+ this.ByteBuffer = Buffer.concat([this.ByteBuffer, s_TempBuffer], this.Length);
66
+ },
67
+ ```
68
+
69
+ For a single-byte type, drop the endianness branch (see `WriteUInt8`/`WriteInt8`).
70
+
71
+ ## Placement
72
+
73
+ Keep the read/write methods grouped by family and ordered as in the existing file
74
+ (unsigned ascending width, then signed ascending width, then float/double, then
75
+ bytes). Add the new method next to its siblings, not at the end.
76
+
77
+ ## After editing binutils.js
78
+
79
+ 1. **README.md** — add a `### ReadX(...)` and `### WriteX(value)` entry in the
80
+ matching BinaryReader / BinaryWriter sections, mirroring the existing phrasing
81
+ ("Reads/Writes a … and advances the current position by N bytes").
82
+ 2. **Test** — add a round-trip test (see the `test-binutils` skill): write a value
83
+ in both `'big'` and `'little'`, read it back, assert equality. Include a min/max
84
+ or negative boundary value for signed/wide types. For 64-bit values assert the
85
+ returned type is `bigint`.
86
+ 3. Run the tests: `node --test`.
87
+
88
+ ## Verify before finishing
89
+
90
+ - Write width == read width (round-trip test passes for both endiannesses).
91
+ - `Position`/`Length` advance by exactly `N`.
92
+ - Out-of-range read returns the documented zero value, not a throw.
93
+ - README entries added; method placed beside its family.
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: test-binutils
3
+ description: Author and run tests for the binutils64 library using node:test and node:assert. Use when asked to add test coverage, write tests for a Read/Write method, verify a change, or set up the test suite for this project.
4
+ ---
5
+
6
+ # Testing binutils64
7
+
8
+ The library ships with no test framework today. Use the built-in `node:test`
9
+ runner + `node:assert/strict` — zero dependencies. (The library's runtime floor
10
+ stays `node >=0.12`; `node:test` only affects the dev/test environment, which
11
+ needs Node >= 18.)
12
+
13
+ ## Layout & running
14
+
15
+ - Put tests in `test/`, named `*.test.js`.
16
+ - Run the whole suite: `node --test`.
17
+ - Add to `package.json` if missing:
18
+ ```json
19
+ "scripts": { "test": "node --test" }
20
+ ```
21
+
22
+ ## Test file skeleton
23
+
24
+ ```javascript
25
+ var test = require('node:test');
26
+ var assert = require('node:assert/strict');
27
+ var binutils = require('../binutils.js');
28
+
29
+ test('UInt32 round-trips both endiannesses', function() {
30
+ ['big', 'little'].forEach(function(endian) {
31
+ var w = new binutils.BinaryWriter(endian);
32
+ w.WriteUInt32(0xDEADBEEF);
33
+ var r = new binutils.BinaryReader(w.ByteBuffer, endian);
34
+ assert.equal(r.ReadUInt32(), 0xDEADBEEF);
35
+ });
36
+ });
37
+ ```
38
+
39
+ Keep test code ES5-compatible in spirit (`var`, `function`) to match repo style,
40
+ though the test runner itself requires modern Node.
41
+
42
+ ## Coverage priorities (highest value first)
43
+
44
+ ### 1. Round-trips — write then read back
45
+ For every type, both `'big'` and `'little'`: write a value, feed `writer.ByteBuffer`
46
+ into a reader, assert the read value equals the written one.
47
+ - `UInt8/16/32`, `Int8/16/32`, `Float`, `Double`, `Bytes`.
48
+ - `UInt64/Int64`: assert against the correct BigInt value. The writers accept a
49
+ `Number` or a `BigInt` and emit all 8 bytes; the reader returns a `BigInt`, so
50
+ compare against a `BigInt` literal (`123n`), not a `Number` (`123n !== 123` under
51
+ strict equality).
52
+ - Signed boundaries: `Int32` min `-2147483648` and max `2147483647`, negatives for
53
+ `Int8/16`.
54
+ - `Float`: compare with tolerance (`assert.ok(Math.abs(got - want) < 1e-6)`) due to
55
+ precision loss. `Double` is exact for representable values.
56
+
57
+ ### 2. BinaryReader behavior
58
+ - **Endianness**: identical bytes read as `'big'` vs `'little'` produce the expected
59
+ swapped values; default (no arg) is `'big'`.
60
+ - **Invariants**: after reads, `Position` advanced by the right byte count, `Length`
61
+ unchanged at the original size, `ByteBuffer` shrank by the consumed bytes.
62
+ - **Out-of-range** reads return `0` / `0.0` / empty `Buffer` (never throw) — read
63
+ past the end for each method.
64
+ - **Constructor inputs**: `Buffer`, `Array`, and `string` (+ encoding) all build;
65
+ an invalid input (e.g. a number) throws.
66
+ - **Constructor copies input**: mutate the caller's original buffer after
67
+ constructing and confirm reader output is unaffected.
68
+ - `ReadUInt64`/`ReadInt64` return a `bigint` (`assert.equal(typeof v, 'bigint')`).
69
+ - README reader example: bytes `[1,0,2,0,0,0,3,1,2,3,4,5,6]` yield `1`, `2`, `3`,
70
+ a 6-byte buffer, then `Position` and `Length` both `13`.
71
+
72
+ ### 3. BinaryWriter behavior
73
+ - Defaults: endianness `'big'`, encoding `'ascii'`.
74
+ - `Length` increments by the correct width per write; emitted bytes match expected
75
+ hex for both endiannesses.
76
+ - `WriteBytes` accepts `string`, `Array`, and `Buffer`, producing identical bytes,
77
+ and throws on any other input type.
78
+ - README writer example yields `<Buffer ff ff 00 00 00 00 ff ff ff ff 05 04 03 02 01>`,
79
+ `Length` 15.
80
+
81
+ ### 4. Maintaining bug-documentation tests
82
+ There are no open known bugs at the moment. When you discover one, pin its current
83
+ behavior with a clearly-commented test so regressions are noticed. When a bug is
84
+ fixed, delete its pinned test and convert any matching TODO round-trip into a normal
85
+ assertion — this is how the former 64-bit writer bug (low-32-bit truncation) and the
86
+ `WriteBytes` guard bug (`!p_Value instanceof Buffer` mis-parsing, which let invalid
87
+ input through) were retired.
88
+
89
+ ## Verify before finishing
90
+ - `node --test` passes (except intentionally-`todo` bug tests).
91
+ - New types added via the `add-binary-type` skill have a matching round-trip test.
@@ -0,0 +1,22 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ node: [20, 22, 24]
15
+ name: Node ${{ matrix.node }}
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+ - uses: actions/setup-node@v6
19
+ with:
20
+ node-version: ${{ matrix.node }}
21
+ # No install step: the package has zero dependencies and no lockfile.
22
+ - run: npm test
package/CLAUDE.md ADDED
@@ -0,0 +1,54 @@
1
+ # CLAUDE.md
2
+
3
+ Guidance for working in this repository.
4
+
5
+ ## What this is
6
+
7
+ `binutils64` — a small npm package providing .NET-style `BinaryReader` and
8
+ `BinaryWriter` classes for Node.js, with selectable endianness (`'big'` default
9
+ or `'little'`). All logic lives in a single file: `binutils.js`. Published to npm
10
+ as `binutils64`.
11
+
12
+ ## Layout
13
+
14
+ - `binutils.js` — the entire library. Two ES5 prototype-based "classes"
15
+ (`BinaryReader`, `BinaryWriter`) exported via `module.exports`.
16
+ - `README.md` — the public API reference (keep in sync when adding/changing methods).
17
+ - No `src/`, no build step, no dependencies, no test suite.
18
+
19
+ ## Running / testing
20
+
21
+ There is no test framework, lint config, or build. To verify a change, write an
22
+ ad-hoc script and run it with node, e.g.:
23
+
24
+ ```bash
25
+ node -e "var b=require('./binutils.js'); var w=new b.BinaryWriter('little'); w.WriteUInt32(3); console.log(w.ByteBuffer);"
26
+ ```
27
+
28
+ When you add or change a public method, update the matching section in `README.md`.
29
+
30
+ ## Code conventions (match the existing style exactly)
31
+
32
+ - ES5 only: `var`, prototype assignment, no classes/arrow functions/`let`/`const`.
33
+ - Method and property names are `PascalCase` (`ReadUInt32`, `ByteBuffer`, `Position`).
34
+ - Function parameters are prefixed `p_` (`p_InputBuffer`, `p_Value`).
35
+ - Local variables are prefixed `s_` (`s_Val`, `s_TempBuffer`).
36
+ - Targets `node >=0.12`, so `new Buffer(...)` is used deliberately despite being
37
+ deprecated in modern Node. Don't "fix" it to `Buffer.alloc`/`Buffer.from` unless
38
+ the engines floor is also raised.
39
+ - Commit messages are short, lowercase, imperative (e.g. `add read/write 64 bit values`,
40
+ `fix read int64`). No AI/Claude attribution.
41
+
42
+ ## Behavior to preserve
43
+
44
+ - **Reads are destructive.** Each `Read*` slices the consumed bytes off
45
+ `this.ByteBuffer` and advances `this.Position`. `Length` stays at the original
46
+ buffer length; `ByteBuffer` shrinks as you read.
47
+ - **Out-of-range reads return `0` (or `0.0`/empty Buffer), they do not throw.**
48
+ - `ReadUInt64`/`ReadInt64` return a `BigInt` (via `readBigUInt64*`/`readBigInt64*`).
49
+ - The constructor copies the input buffer (`new Buffer(p_InputBuffer)`) so the
50
+ caller's buffer is not mutated — preserve this.
51
+ - `WriteUInt64`/`WriteInt64` accept a `Number` or `BigInt` (coerced via
52
+ `BigInt(p_Value)`) and emit all 8 bytes via `writeBigUInt64*`/`writeBigInt64*`.
53
+ - `WriteBytes` accepts a `Buffer`, `Array`, or `string`, and throws on any other
54
+ input type.
package/README.md CHANGED
@@ -1,163 +1,256 @@
1
- binutils - .NET like BinaryReader and BinaryWriter for node.js
2
- ==============================================================
3
-
4
- These utilities provide you with a BinaryReader and BinaryWriter class with functions similar to the corresponding .NET classes.
5
- They also allow you to define a specific endianness!
6
-
7
- Basic installation and usage
8
- ----------------------------
9
-
10
- You can install this package either by using npm or by downloading the source from GitHub and requiring it directly.
11
-
12
- To install using npm open your terminal (or command line), make sure you're in your application directory and execute the following command:
13
-
14
- npm install binutils64
15
-
16
- You can then start using the package by requiring it from your application as such:
17
-
18
- var binutils = require('binutils64');
19
-
20
- BinaryReader Class
21
- ------------------
22
-
23
- ### BinaryReader(inputBuffer, endianness, encoding)
24
-
25
- * Initializes a BinaryReader with the specified settings
26
- * inputBuffer can be a `Buffer`, a `string` or an `array`
27
-
28
- ### ReadUInt8()
29
-
30
- * Reads a 8 bit unsigned integer from the buffer and advances the current position by 1 byte
31
-
32
- ### ReadUInt16()
33
-
34
- * Reads a 16 bit unsigned integer from the buffer and advances the current position by 2 bytes
35
-
36
- ### ReadUInt32()
37
-
38
- * Reads a 32 bit unsigned integer from the buffer and advances the current position by 4 bytes
39
-
40
- ### ReadInt8()
41
-
42
- * Reads a 8 bit signed integer from the buffer and advances the current position by 1 byte
43
-
44
- ### ReadInt16()
45
-
46
- * Reads a 16 bit signed integer from the buffer and advances the current position by 2 bytes
47
-
48
- ### ReadInt32()
49
-
50
- * Reads a 32 bit signed integer from the buffer and advances the current position by 4 bytes
51
-
52
- ### ReadFloat()
53
-
54
- * Reads a float from the buffer and advances the current position by 4 bytes
55
-
56
- ### ReadDouble()
57
-
58
- * Reads a double from the buffer and advances the current position by 8 bytes
59
-
60
- ### ReadBytes(count)
61
-
62
- * Reads the specified number of bytes into a new buffer and advances the current position by that number of bytes
63
-
64
- ### Length [Number]
65
-
66
- * The length of the initially provided data
67
-
68
- ### Position [Number]
69
-
70
- * The current position (index) of the reader
71
-
72
- ### ByteBuffer [Buffer]
73
-
74
- * A buffer containing the remaining data from the original buffer
75
-
76
- BinaryWriter Class
77
- ------------------
78
-
79
- ### BinaryWriter(endianness, encoding)
80
-
81
- * Initializes a BinaryWriter with the specified settings
82
-
83
- ### WriteUInt8(value)
84
-
85
- * Writes a 8 bit unsigned integer to the buffer and advances the current position by 1 byte
86
-
87
- ### WriteUInt16(value)
88
-
89
- * Writes a 16 bit unsigned integer to the buffer and advances the current position by 2 bytes
90
-
91
- ### WriteUInt32(value)
92
-
93
- * Writes a 32 bit unsigned integer to the buffer and advances the current position by 4 bytes
94
-
95
- ### WriteInt8(value)
96
-
97
- * Writes a 8 bit signed integer to the buffer and advances the current position by 1 byte
98
-
99
- ### WriteInt16(value)
100
-
101
- * Writes a 16 bit signed integer to the buffer and advances the current position by 2 bytes
102
-
103
- ### WriteInt32(value)
104
-
105
- * Writes a 32 bit signed integer to the buffer and advances the current position by 4 bytes
106
-
107
- ### WriteFloat(value)
108
-
109
- * Writes a float to the buffer and advances the current position by 4 bytes
110
-
111
- ### WriteDouble(value)
112
-
113
- * Writes a double to the buffer and advances the current position by 8 bytes
114
-
115
- ### WriteBytes(value)
116
-
117
- * Reads the specified number of bytes into a new buffer and advances the current position by that number of bytes
118
- * value can be a `Buffer`, a `string` or an `array`
119
-
120
- ### Length [Number]
121
-
122
- * The length of the current data buffer
123
-
124
- ### ByteBuffer [Buffer]
125
-
126
- * A buffer containing the data written using the class functions
127
-
128
- Example
129
- -------
130
-
131
- ```javascript
132
- var binutils = require('./binutils.js');
133
-
134
- var buffer = new Buffer([1, 0, 2, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6]);
135
-
136
- var reader = new binutils.BinaryReader(buffer);
137
-
138
- console.log(reader.ReadUInt8()); // Will print '1'
139
- console.log(reader.ReadUInt16()); // Will print '2'
140
- console.log(reader.ReadUInt32()); // Will print '3'
141
- console.log(reader.ReadBytes(6)); // Will print '<Buffer 01 02 03 04 05 06>'
142
- console.log(reader.Position); // Will print '13'
143
- console.log(reader.Length); // Will print '13'
144
-
145
- //
146
-
147
- var writer = new binutils.BinaryWriter();
148
-
149
- writer.WriteUInt16(65535);
150
- writer.WriteUInt32(0);
151
- writer.WriteInt32(-1);
152
- writer.WriteBytes([5, 4, 3, 2, 1]);
153
-
154
- console.log(writer.ByteBuffer); // Will print '<Buffer ff ff 00 00 00 00 ff ff ff ff 05 04 03 02 01>'
155
- console.log(writer.Length); // Will print '15'
156
- ```
157
-
158
- TODO
159
- ----
160
-
161
- * Add support for 64-bit integers
162
- * Add support for strings
163
- * Add support for booleans
1
+ # binutils64
2
+
3
+ > A .NET-style `BinaryReader` and `BinaryWriter` for Node.js, with selectable endianness.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/binutils64.svg)](https://www.npmjs.com/package/binutils64)
6
+ [![Tests](https://github.com/mihailShumilov/node-binutils/actions/workflows/test.yml/badge.svg)](https://github.com/mihailShumilov/node-binutils/actions/workflows/test.yml)
7
+
8
+ `binutils64` provides two small classes — `BinaryReader` and `BinaryWriter` — that
9
+ make it easy to parse and produce binary data sequentially, with an API modelled on
10
+ the corresponding .NET classes. Both classes let you choose the byte order
11
+ (`big`- or `little`-endian) and support 8-, 16-, 32- and 64-bit integers, floats,
12
+ doubles, and raw byte runs.
13
+
14
+ ## Table of contents
15
+
16
+ - [Features](#features)
17
+ - [Installation](#installation)
18
+ - [Quick start](#quick-start)
19
+ - [API reference](#api-reference)
20
+ - [BinaryReader](#binaryreader)
21
+ - [BinaryWriter](#binarywriter)
22
+ - [Behavior and best practices](#behavior-and-best-practices)
23
+ - [Examples](#examples)
24
+ - [Requirements and compatibility](#requirements-and-compatibility)
25
+ - [Testing](#testing)
26
+ - [Contributing](#contributing)
27
+
28
+ ## Features
29
+
30
+ - Sequential `Read*` / `Write*` methods for every common fixed-width type.
31
+ - Per-instance endianness (`big` by default, or `little`).
32
+ - 64-bit integers via JavaScript `BigInt`.
33
+ - Signed and unsigned integers, IEEE-754 `float` and `double`, and raw byte runs.
34
+ - Zero runtime dependencies.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ npm install binutils64
40
+ ```
41
+
42
+ Then require it:
43
+
44
+ ```javascript
45
+ const binutils = require('binutils64');
46
+ // const { BinaryReader, BinaryWriter } = require('binutils64');
47
+ ```
48
+
49
+ ## Quick start
50
+
51
+ ```javascript
52
+ const { BinaryReader, BinaryWriter } = require('binutils64');
53
+
54
+ // --- Writing ---
55
+ const writer = new BinaryWriter(); // big-endian by default
56
+ writer.WriteUInt16(65535);
57
+ writer.WriteUInt32(0);
58
+ writer.WriteInt32(-1);
59
+ writer.WriteBytes([5, 4, 3, 2, 1]);
60
+
61
+ console.log(writer.ByteBuffer);
62
+ // <Buffer ff ff 00 00 00 00 ff ff ff ff 05 04 03 02 01>
63
+ console.log(writer.Length); // 15
64
+
65
+ // --- Reading ---
66
+ const reader = new BinaryReader(writer.ByteBuffer);
67
+ console.log(reader.ReadUInt16()); // 65535
68
+ console.log(reader.ReadUInt32()); // 0
69
+ console.log(reader.ReadInt32()); // -1
70
+ console.log(reader.ReadBytes(5)); // <Buffer 05 04 03 02 01>
71
+ ```
72
+
73
+ ## API reference
74
+
75
+ ### BinaryReader
76
+
77
+ A reader wraps an immutable copy of the input data and consumes it from the front as
78
+ you read.
79
+
80
+ #### `new BinaryReader(input, [endianness], [encoding])`
81
+
82
+ | Parameter | Type | Default | Description |
83
+ | ------------ | ----------------------------- | ------- | ------------------------------------------------------------------ |
84
+ | `input` | `Buffer` \| `number[]` \| `string` | — | The data to read. A `Buffer` is **copied** so the source is never mutated. |
85
+ | `endianness` | `'big'` \| `'little'` | `'big'` | Byte order used by all multi-byte reads. |
86
+ | `encoding` | `string` | `'ascii'` | Used only when `input` is a `string`, to turn it into bytes. |
87
+
88
+ Throws `Error` if `input` is not a `Buffer`, array, or string.
89
+
90
+ #### Read methods
91
+
92
+ | Method | Bytes | Returns | Notes |
93
+ | ----------------- | ----- | --------- | ---------------------------------------------- |
94
+ | `ReadUInt8()` | 1 | `number` | Unsigned. |
95
+ | `ReadInt8()` | 1 | `number` | Signed. |
96
+ | `ReadUInt16()` | 2 | `number` | Honors endianness. |
97
+ | `ReadInt16()` | 2 | `number` | Honors endianness. |
98
+ | `ReadUInt32()` | 4 | `number` | Honors endianness. |
99
+ | `ReadInt32()` | 4 | `number` | Honors endianness. |
100
+ | `ReadUInt64()` | 8 | `BigInt` | Honors endianness. Returns a `BigInt`. |
101
+ | `ReadInt64()` | 8 | `BigInt` | Honors endianness. Returns a `BigInt`. |
102
+ | `ReadFloat()` | 4 | `number` | IEEE-754 single precision. |
103
+ | `ReadDouble()` | 8 | `number` | IEEE-754 double precision. |
104
+ | `ReadBytes(count)`| `count` | `Buffer`| Copies `count` bytes into a new `Buffer`. |
105
+
106
+ If fewer than the required number of bytes remain, integer/float reads return `0`
107
+ (or `0.0`), and `ReadBytes` returns an empty `Buffer` — **without** throwing or
108
+ advancing the position. See [Behavior and best practices](#behavior-and-best-practices).
109
+
110
+ #### Reader properties
111
+
112
+ | Property | Type | Description |
113
+ | ------------ | -------- | ------------------------------------------------------------------- |
114
+ | `ByteBuffer` | `Buffer` | The **remaining** unread data. Shrinks as you read. |
115
+ | `Position` | `number` | Number of bytes consumed so far (starts at `0`). |
116
+ | `Length` | `number` | The length of the original input. Does **not** change as you read. |
117
+ | `Endianness` | `string` | `'big'` or `'little'`. |
118
+ | `Encoding` | `string` | The encoding passed to the constructor. |
119
+
120
+ ### BinaryWriter
121
+
122
+ A writer accumulates bytes in an internal buffer that grows with every write.
123
+
124
+ #### `new BinaryWriter([endianness], [encoding])`
125
+
126
+ | Parameter | Type | Default | Description |
127
+ | ------------ | --------------------- | --------- | ---------------------------------------- |
128
+ | `endianness` | `'big'` \| `'little'` | `'big'` | Byte order used by all multi-byte writes.|
129
+ | `encoding` | `string` | `'ascii'` | Stored on the instance; reserved. |
130
+
131
+ #### Write methods
132
+
133
+ | Method | Bytes | Accepts | Notes |
134
+ | --------------------- | ----- | ------------------- | -------------------------------------------------- |
135
+ | `WriteUInt8(value)` | 1 | `number` | Unsigned. |
136
+ | `WriteInt8(value)` | 1 | `number` | Signed. |
137
+ | `WriteUInt16(value)` | 2 | `number` | Honors endianness. |
138
+ | `WriteInt16(value)` | 2 | `number` | Honors endianness. |
139
+ | `WriteUInt32(value)` | 4 | `number` | Honors endianness. |
140
+ | `WriteInt32(value)` | 4 | `number` | Honors endianness. |
141
+ | `WriteUInt64(value)` | 8 | `number` \| `BigInt`| Coerced with `BigInt(value)`; honors endianness. |
142
+ | `WriteInt64(value)` | 8 | `number` \| `BigInt`| Coerced with `BigInt(value)`; honors endianness. |
143
+ | `WriteFloat(value)` | 4 | `number` | IEEE-754 single precision. |
144
+ | `WriteDouble(value)` | 8 | `number` | IEEE-754 double precision. |
145
+ | `WriteBytes(value)` | varies| `Buffer` \| `number[]` \| `string` | Strings are written as one byte per character code. Throws on any other type. |
146
+
147
+ Writing a value outside the target type's range throws a `RangeError` (the standard
148
+ Node.js `Buffer` write behavior) — e.g. `WriteUInt8(256)`.
149
+
150
+ #### Writer properties
151
+
152
+ | Property | Type | Description |
153
+ | ------------ | -------- | -------------------------------------------- |
154
+ | `ByteBuffer` | `Buffer` | All bytes written so far. |
155
+ | `Length` | `number` | The current length of `ByteBuffer`. |
156
+ | `Endianness` | `string` | `'big'` or `'little'`. |
157
+ | `Encoding` | `string` | The encoding passed to the constructor. |
158
+
159
+ ## Behavior and best practices
160
+
161
+ - **Reads are destructive.** Each `Read*` call consumes bytes from the front of
162
+ `ByteBuffer` and advances `Position`. If you need the original bytes again, keep
163
+ your own copy before reading. Create a fresh `BinaryReader` to start over.
164
+ - **`Length` vs. `Position`.** On a reader, `Length` is the original size and never
165
+ changes; `Position` tracks how much you have consumed. The bytes left to read are
166
+ `Length - Position` (also `ByteBuffer.length`).
167
+ - **Out-of-range reads do not throw.** Reading past the end returns `0` / `0.0` /
168
+ an empty `Buffer` and leaves `Position` unchanged. Check the remaining length
169
+ yourself if a short buffer should be treated as an error:
170
+
171
+ ```javascript
172
+ if (reader.ByteBuffer.length < 4) {
173
+ throw new Error('truncated record');
174
+ }
175
+ const value = reader.ReadUInt32();
176
+ ```
177
+
178
+ - **64-bit values are `BigInt`s.** `ReadUInt64`/`ReadInt64` always return a `BigInt`.
179
+ When writing, pass a `BigInt` for any value above `Number.MAX_SAFE_INTEGER`
180
+ (`2^53 - 1`) to avoid silent precision loss; smaller `number`s are accepted and
181
+ coerced automatically.
182
+ - **Set endianness once, at construction.** All multi-byte methods follow the
183
+ instance's `Endianness`; reader and writer must agree to round-trip correctly.
184
+ - **Constructing a reader from a string?** Pass the encoding explicitly
185
+ (e.g. `new BinaryReader(text, 'big', 'utf8')`) so the bytes are interpreted the
186
+ way you expect.
187
+
188
+ ## Examples
189
+
190
+ ### Round-tripping a 64-bit integer
191
+
192
+ ```javascript
193
+ const { BinaryReader, BinaryWriter } = require('binutils64');
194
+
195
+ const writer = new BinaryWriter(); // big-endian
196
+ writer.WriteUInt64(0x1234567890ABCDEFn); // pass a BigInt for large values
197
+ console.log(writer.ByteBuffer); // <Buffer 12 34 56 78 90 ab cd ef>
198
+
199
+ const reader = new BinaryReader(writer.ByteBuffer);
200
+ console.log(reader.ReadUInt64()); // 1311768467294899695n
201
+ ```
202
+
203
+ ### Little-endian
204
+
205
+ ```javascript
206
+ const { BinaryReader, BinaryWriter } = require('binutils64');
207
+
208
+ const writer = new BinaryWriter('little');
209
+ writer.WriteUInt32(0x01020304);
210
+ console.log(writer.ByteBuffer); // <Buffer 04 03 02 01>
211
+
212
+ const reader = new BinaryReader(writer.ByteBuffer, 'little');
213
+ console.log(reader.ReadUInt32().toString(16)); // "1020304"
214
+ ```
215
+
216
+ ### Parsing a structured record
217
+
218
+ ```javascript
219
+ const { BinaryReader } = require('binutils64');
220
+
221
+ // type (u8), id (u32, big-endian), payload (6 bytes)
222
+ const reader = new BinaryReader(Buffer.from([1, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6]));
223
+
224
+ const record = {
225
+ type: reader.ReadUInt8(), // 1
226
+ id: reader.ReadUInt32(), // 3
227
+ payload: reader.ReadBytes(6), // <Buffer 01 02 03 04 05 06>
228
+ };
229
+
230
+ console.log(record, 'read', reader.Position, 'of', reader.Length, 'bytes');
231
+ ```
232
+
233
+ ## Requirements and compatibility
234
+
235
+ - **Node.js.** The 8-, 16- and 32-bit methods run on very old Node.js versions, but
236
+ the 64-bit methods (`ReadUInt64`, `ReadInt64`, `WriteUInt64`, `WriteInt64`) rely on
237
+ `BigInt` and the `Buffer` big-integer methods, which require **Node.js 12 or newer**.
238
+ - The library uses the legacy `Buffer` constructor internally; on modern Node.js you
239
+ may see a one-time `DEP0005` deprecation warning. This does not affect behavior.
240
+ - Running the test suite uses the built-in `node:test` runner, which requires
241
+ **Node.js 18 or newer**.
242
+
243
+ ## Testing
244
+
245
+ ```bash
246
+ npm test # runs `node --test`
247
+ ```
248
+
249
+ The suite covers every reader/writer method, round-trips across both endiannesses,
250
+ edge cases (out-of-range reads, constructor input types, buffer-copy isolation), and
251
+ the documented examples. Continuous integration runs it on Node.js 20, 22 and 24.
252
+
253
+ ## Contributing
254
+
255
+ Issues and pull requests are welcome. Please add or update tests for any behavioral
256
+ change and make sure `npm test` passes before opening a pull request.
package/binutils.js CHANGED
@@ -60,7 +60,7 @@ BinaryReader.prototype = {
60
60
  return 0;
61
61
  }
62
62
 
63
- var s_Val = (this.Endianness == 'little') ? this.ByteBuffer.readUInt32LE(0) : this.ByteBuffer.readUInt32BE(0);
63
+ var s_Val = (this.Endianness == 'little') ? this.ByteBuffer.readBigUInt64LE(0) : this.ByteBuffer.readBigUInt64BE(0);
64
64
  this.ByteBuffer = this.ByteBuffer.slice(8);
65
65
  this.Position += 8;
66
66
  return s_Val;
@@ -104,7 +104,7 @@ BinaryReader.prototype = {
104
104
  return 0;
105
105
  }
106
106
 
107
- var s_Val = (this.Endianness == 'little') ? this.ByteBuffer.readInt32LE(0) : this.ByteBuffer.readInt32BE(0);
107
+ var s_Val = (this.Endianness == 'little') ? this.ByteBuffer.readBigInt64LE(0) : this.ByteBuffer.readBigInt64BE(0);
108
108
  this.ByteBuffer = this.ByteBuffer.slice(8);
109
109
  this.Position += 8;
110
110
  return s_Val;
@@ -195,10 +195,11 @@ BinaryWriter.prototype = {
195
195
 
196
196
  WriteUInt64: function(p_Value) {
197
197
  var s_TempBuffer = new Buffer(8);
198
+ var s_Value = BigInt(p_Value);
198
199
  if (this.Endianness == 'little') {
199
- s_TempBuffer.writeUInt32LE(p_Value, 0);
200
+ s_TempBuffer.writeBigUInt64LE(s_Value, 0);
200
201
  } else {
201
- s_TempBuffer.writeUInt32BE(p_Value, 0);
202
+ s_TempBuffer.writeBigUInt64BE(s_Value, 0);
202
203
  }
203
204
  this.Length += 8;
204
205
  this.ByteBuffer = Buffer.concat([this.ByteBuffer, s_TempBuffer], this.Length);
@@ -235,10 +236,11 @@ BinaryWriter.prototype = {
235
236
 
236
237
  WriteInt64: function(p_Value) {
237
238
  var s_TempBuffer = new Buffer(8);
239
+ var s_Value = BigInt(p_Value);
238
240
  if (this.Endianness == 'little') {
239
- s_TempBuffer.writeInt32LE(p_Value, 0);
241
+ s_TempBuffer.writeBigInt64LE(s_Value, 0);
240
242
  } else {
241
- s_TempBuffer.writeInt32BE(p_Value, 0);
243
+ s_TempBuffer.writeBigInt64BE(s_Value, 0);
242
244
  }
243
245
  this.Length += 8;
244
246
  this.ByteBuffer = Buffer.concat([this.ByteBuffer, s_TempBuffer], this.Length);
@@ -279,7 +281,7 @@ BinaryWriter.prototype = {
279
281
  p_Value = s_BytesArray;
280
282
  }
281
283
 
282
- if (!p_Value instanceof Buffer && !p_Value instanceof Array) {
284
+ if (!(p_Value instanceof Buffer) && !(p_Value instanceof Array)) {
283
285
  throw new Error("Invalid Buffer object provided.");
284
286
  }
285
287
 
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "binutils64",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "author": "Mihail Shumilov <mschumilow@gmail.com>",
5
5
  "description": "A .NET-like BinaryReader and BinaryWriter with endianness support.",
6
6
  "main": "./binutils.js",
7
+ "scripts": {
8
+ "test": "node --test"
9
+ },
7
10
  "engines": {
8
- "node": ">=0.10"
11
+ "node": ">=0.12"
9
12
  },
10
13
  "repository": {
11
14
  "type" : "git",
@@ -0,0 +1,76 @@
1
+ var test = require('node:test');
2
+ var assert = require('node:assert/strict');
3
+ var binutils = require('../binutils.js');
4
+ var BinaryReader = binutils.BinaryReader;
5
+
6
+ test('constructor accepts a Buffer', function() {
7
+ var r = new BinaryReader(Buffer.from([1, 2, 3]));
8
+ assert.equal(r.Length, 3);
9
+ assert.equal(r.ReadUInt8(), 1);
10
+ });
11
+
12
+ test('constructor accepts an Array', function() {
13
+ var r = new BinaryReader([1, 2, 3]);
14
+ assert.equal(r.Length, 3);
15
+ assert.equal(r.ReadUInt8(), 1);
16
+ });
17
+
18
+ test('constructor accepts a string with encoding', function() {
19
+ var r = new BinaryReader('ABC', 'big', 'ascii');
20
+ assert.equal(r.Length, 3);
21
+ assert.equal(r.ReadUInt8(), 65);
22
+ });
23
+
24
+ test('constructor rejects invalid input types', function() {
25
+ assert.throws(function() { new BinaryReader(42); }, /Invalid buffer input/);
26
+ assert.throws(function() { new BinaryReader({}); }, /Invalid buffer input/);
27
+ assert.throws(function() { new BinaryReader(null); }, /Invalid buffer input/);
28
+ });
29
+
30
+ test('constructor copies the input buffer (no aliasing)', function() {
31
+ var src = Buffer.from([1, 2, 3]);
32
+ var r = new BinaryReader(src);
33
+ src[0] = 99; // mutate the caller's buffer after construction
34
+ assert.equal(r.ReadUInt8(), 1, 'reader is unaffected by later mutation of the source');
35
+ });
36
+
37
+ test('out-of-range integer reads return 0 without advancing Position', function() {
38
+ var r = new BinaryReader([0x01], 'big'); // only 1 byte available
39
+ assert.equal(r.ReadUInt16(), 0);
40
+ assert.equal(r.ReadUInt32(), 0);
41
+ assert.equal(r.ReadUInt64(), 0);
42
+ assert.equal(r.ReadInt16(), 0);
43
+ assert.equal(r.ReadInt32(), 0);
44
+ assert.equal(r.ReadInt64(), 0);
45
+ assert.equal(r.Position, 0, 'failed reads do not advance Position');
46
+ assert.equal(r.ByteBuffer.length, 1, 'failed reads do not consume bytes');
47
+ });
48
+
49
+ test('out-of-range float/double reads return 0', function() {
50
+ var r = new BinaryReader([0x01], 'big');
51
+ assert.equal(r.ReadFloat(), 0);
52
+ assert.equal(r.ReadDouble(), 0);
53
+ });
54
+
55
+ test('ReadUInt8 on an empty buffer returns 0', function() {
56
+ var r = new BinaryReader([], 'big');
57
+ assert.equal(r.Length, 0);
58
+ assert.equal(r.ReadUInt8(), 0);
59
+ assert.equal(r.ReadInt8(), 0);
60
+ });
61
+
62
+ test('ReadBytes returns an empty Buffer when count exceeds remaining', function() {
63
+ var r = new BinaryReader([1, 2, 3], 'big');
64
+ var out = r.ReadBytes(5);
65
+ assert.ok(Buffer.isBuffer(out));
66
+ assert.equal(out.length, 0);
67
+ assert.equal(r.Position, 0, 'over-long ReadBytes does not advance');
68
+ assert.equal(r.ByteBuffer.length, 3);
69
+ });
70
+
71
+ test('ReadBytes reading exactly the remaining length succeeds', function() {
72
+ var r = new BinaryReader([1, 2, 3], 'big');
73
+ assert.deepEqual(Array.from(r.ReadBytes(3)), [1, 2, 3]);
74
+ assert.equal(r.Position, 3);
75
+ assert.equal(r.ByteBuffer.length, 0);
76
+ });
@@ -0,0 +1,106 @@
1
+ var test = require('node:test');
2
+ var assert = require('node:assert/strict');
3
+ var binutils = require('../binutils.js');
4
+ var BinaryReader = binutils.BinaryReader;
5
+
6
+ function reader(arr, endian) {
7
+ return new BinaryReader(Buffer.from(arr), endian);
8
+ }
9
+
10
+ test('BinaryReader: constructor defaults', function() {
11
+ var r = reader([1, 2, 3]);
12
+ assert.equal(r.Endianness, 'big', 'defaults to big-endian');
13
+ assert.equal(r.Encoding, 'ascii', 'defaults to ascii encoding');
14
+ assert.equal(r.Length, 3, 'Length is the original buffer length');
15
+ assert.equal(r.Position, 0, 'Position starts at 0');
16
+ assert.equal(r.ByteBuffer.length, 3);
17
+ });
18
+
19
+ test('BinaryReader: ReadUInt8', function() {
20
+ var r = reader([200, 5]);
21
+ assert.equal(r.ReadUInt8(), 200);
22
+ assert.equal(r.ReadUInt8(), 5);
23
+ });
24
+
25
+ test('BinaryReader: ReadInt8 (signed)', function() {
26
+ var r = reader([0xFF, 0x80, 0x7F]);
27
+ assert.equal(r.ReadInt8(), -1);
28
+ assert.equal(r.ReadInt8(), -128);
29
+ assert.equal(r.ReadInt8(), 127);
30
+ });
31
+
32
+ test('BinaryReader: ReadUInt16 honors endianness', function() {
33
+ assert.equal(reader([0x12, 0x34], 'big').ReadUInt16(), 0x1234);
34
+ assert.equal(reader([0x12, 0x34], 'little').ReadUInt16(), 0x3412);
35
+ });
36
+
37
+ test('BinaryReader: ReadInt16 (signed)', function() {
38
+ assert.equal(reader([0xFF, 0xFF], 'big').ReadInt16(), -1);
39
+ assert.equal(reader([0x80, 0x00], 'big').ReadInt16(), -32768);
40
+ assert.equal(reader([0x00, 0x80], 'little').ReadInt16(), -32768);
41
+ });
42
+
43
+ test('BinaryReader: ReadUInt32 honors endianness', function() {
44
+ assert.equal(reader([0x12, 0x34, 0x56, 0x78], 'big').ReadUInt32(), 0x12345678);
45
+ assert.equal(reader([0x78, 0x56, 0x34, 0x12], 'little').ReadUInt32(), 0x12345678);
46
+ });
47
+
48
+ test('BinaryReader: ReadInt32 (signed)', function() {
49
+ assert.equal(reader([0xFF, 0xFF, 0xFF, 0xFF], 'big').ReadInt32(), -1);
50
+ assert.equal(reader([0x80, 0x00, 0x00, 0x00], 'big').ReadInt32(), -2147483648);
51
+ });
52
+
53
+ test('BinaryReader: ReadUInt64 returns a BigInt', function() {
54
+ var r = reader([0, 0, 0, 0, 0, 0, 0, 5], 'big');
55
+ var v = r.ReadUInt64();
56
+ assert.equal(typeof v, 'bigint');
57
+ assert.equal(v, 5n);
58
+ assert.equal(reader([5, 0, 0, 0, 0, 0, 0, 0], 'little').ReadUInt64(), 5n);
59
+ });
60
+
61
+ test('BinaryReader: ReadInt64 returns a signed BigInt', function() {
62
+ assert.equal(reader([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], 'big').ReadInt64(), -1n);
63
+ });
64
+
65
+ test('BinaryReader: ReadFloat', function() {
66
+ // 1.5 as IEEE-754 single precision = 0x3FC00000
67
+ assert.equal(reader([0x3F, 0xC0, 0x00, 0x00], 'big').ReadFloat(), 1.5);
68
+ assert.equal(reader([0x00, 0x00, 0xC0, 0x3F], 'little').ReadFloat(), 1.5);
69
+ });
70
+
71
+ test('BinaryReader: ReadDouble', function() {
72
+ // 1.5 as IEEE-754 double precision = 0x3FF8000000000000
73
+ assert.equal(reader([0x3F, 0xF8, 0, 0, 0, 0, 0, 0], 'big').ReadDouble(), 1.5);
74
+ });
75
+
76
+ test('BinaryReader: ReadBytes returns a Buffer of the requested length', function() {
77
+ var r = reader([1, 2, 3, 4, 5, 6]);
78
+ var out = r.ReadBytes(4);
79
+ assert.ok(Buffer.isBuffer(out));
80
+ assert.deepEqual(Array.from(out), [1, 2, 3, 4]);
81
+ assert.equal(r.Position, 4);
82
+ assert.equal(r.ByteBuffer.length, 2);
83
+ });
84
+
85
+ test('BinaryReader: Position advances and Length stays constant; ByteBuffer shrinks', function() {
86
+ var r = reader([1, 2, 3, 4, 5, 6, 7, 8], 'big');
87
+
88
+ assert.equal(r.ReadUInt8(), 1);
89
+ assert.equal(r.Position, 1);
90
+ assert.equal(r.Length, 8, 'Length never changes on reads');
91
+ assert.equal(r.ByteBuffer.length, 7, 'ByteBuffer shrinks by consumed bytes');
92
+
93
+ assert.equal(r.ReadUInt16(), 0x0203);
94
+ assert.equal(r.Position, 3);
95
+ assert.equal(r.ByteBuffer.length, 5);
96
+
97
+ assert.equal(r.ReadUInt32(), 0x04050607);
98
+ assert.equal(r.Position, 7);
99
+ assert.equal(r.ByteBuffer.length, 1);
100
+ });
101
+
102
+ test('BinaryReader: same bytes decode differently per endianness', function() {
103
+ var b = [0x00, 0x00, 0x00, 0x01];
104
+ assert.equal(reader(b, 'big').ReadUInt32(), 1);
105
+ assert.equal(reader(b, 'little').ReadUInt32(), 0x01000000);
106
+ });
@@ -0,0 +1,33 @@
1
+ var test = require('node:test');
2
+ var assert = require('node:assert/strict');
3
+ var binutils = require('../binutils.js');
4
+
5
+ // These mirror the worked examples in README.md. If the documented output ever
6
+ // changes, the docs and these tests must change together.
7
+
8
+ test('README reader example', function() {
9
+ var buffer = Buffer.from([1, 0, 2, 0, 0, 0, 3, 1, 2, 3, 4, 5, 6]);
10
+ var reader = new binutils.BinaryReader(buffer);
11
+
12
+ assert.equal(reader.ReadUInt8(), 1);
13
+ assert.equal(reader.ReadUInt16(), 2);
14
+ assert.equal(reader.ReadUInt32(), 3);
15
+ assert.deepEqual(Array.from(reader.ReadBytes(6)), [1, 2, 3, 4, 5, 6]);
16
+ assert.equal(reader.Position, 13);
17
+ assert.equal(reader.Length, 13);
18
+ });
19
+
20
+ test('README writer example', function() {
21
+ var writer = new binutils.BinaryWriter();
22
+
23
+ writer.WriteUInt16(65535);
24
+ writer.WriteUInt32(0);
25
+ writer.WriteInt32(-1);
26
+ writer.WriteBytes([5, 4, 3, 2, 1]);
27
+
28
+ assert.deepEqual(
29
+ Array.from(writer.ByteBuffer),
30
+ [0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 5, 4, 3, 2, 1]
31
+ );
32
+ assert.equal(writer.Length, 15);
33
+ });
@@ -0,0 +1,120 @@
1
+ var test = require('node:test');
2
+ var assert = require('node:assert/strict');
3
+ var binutils = require('../binutils.js');
4
+ var BinaryReader = binutils.BinaryReader;
5
+ var BinaryWriter = binutils.BinaryWriter;
6
+
7
+ // Write a value with WriteMethod, then read it back with ReadMethod, asserting
8
+ // equality across both endiannesses.
9
+ function roundTrip(writeMethod, readMethod, value, compare) {
10
+ ['big', 'little'].forEach(function(endian) {
11
+ var w = new BinaryWriter(endian);
12
+ w[writeMethod](value);
13
+ var r = new BinaryReader(w.ByteBuffer, endian);
14
+ var got = r[readMethod]();
15
+ if (compare) {
16
+ compare(got, value, endian);
17
+ } else {
18
+ assert.equal(got, value, writeMethod + '/' + readMethod + ' (' + endian + ')');
19
+ }
20
+ });
21
+ }
22
+
23
+ test('round-trip: UInt8', function() {
24
+ roundTrip('WriteUInt8', 'ReadUInt8', 0);
25
+ roundTrip('WriteUInt8', 'ReadUInt8', 200);
26
+ roundTrip('WriteUInt8', 'ReadUInt8', 255);
27
+ });
28
+
29
+ test('round-trip: Int8', function() {
30
+ roundTrip('WriteInt8', 'ReadInt8', -128);
31
+ roundTrip('WriteInt8', 'ReadInt8', -1);
32
+ roundTrip('WriteInt8', 'ReadInt8', 127);
33
+ });
34
+
35
+ test('round-trip: UInt16', function() {
36
+ roundTrip('WriteUInt16', 'ReadUInt16', 0);
37
+ roundTrip('WriteUInt16', 'ReadUInt16', 0xBEEF);
38
+ roundTrip('WriteUInt16', 'ReadUInt16', 0xFFFF);
39
+ });
40
+
41
+ test('round-trip: Int16', function() {
42
+ roundTrip('WriteInt16', 'ReadInt16', -32768);
43
+ roundTrip('WriteInt16', 'ReadInt16', -1);
44
+ roundTrip('WriteInt16', 'ReadInt16', 32767);
45
+ });
46
+
47
+ test('round-trip: UInt32', function() {
48
+ roundTrip('WriteUInt32', 'ReadUInt32', 0);
49
+ roundTrip('WriteUInt32', 'ReadUInt32', 0xDEADBEEF);
50
+ roundTrip('WriteUInt32', 'ReadUInt32', 0xFFFFFFFF);
51
+ });
52
+
53
+ test('round-trip: Int32', function() {
54
+ roundTrip('WriteInt32', 'ReadInt32', -2147483648);
55
+ roundTrip('WriteInt32', 'ReadInt32', -1);
56
+ roundTrip('WriteInt32', 'ReadInt32', 2147483647);
57
+ });
58
+
59
+ test('round-trip: Float (within single-precision tolerance)', function() {
60
+ roundTrip('WriteFloat', 'ReadFloat', 1.5);
61
+ roundTrip('WriteFloat', 'ReadFloat', 3.14, function(got, want, endian) {
62
+ assert.ok(Math.abs(got - want) < 1e-5, 'Float ~3.14 (' + endian + '), got ' + got);
63
+ });
64
+ });
65
+
66
+ test('round-trip: Double (exact)', function() {
67
+ roundTrip('WriteDouble', 'ReadDouble', Math.PI);
68
+ roundTrip('WriteDouble', 'ReadDouble', -123456.789);
69
+ });
70
+
71
+ test('round-trip: Bytes', function() {
72
+ ['big', 'little'].forEach(function(endian) {
73
+ var payload = [1, 2, 3, 4, 5];
74
+ var w = new BinaryWriter(endian);
75
+ w.WriteBytes(payload);
76
+ var r = new BinaryReader(w.ByteBuffer, endian);
77
+ assert.deepEqual(Array.from(r.ReadBytes(payload.length)), payload);
78
+ });
79
+ });
80
+
81
+ // 64-bit round-trips. The writers accept a Number or a BigInt and emit all 8
82
+ // bytes; the reader always returns a BigInt.
83
+
84
+ test('round-trip: UInt64 (BigInt argument)', function() {
85
+ var value = 0x1234567890ABCDEFn;
86
+ ['big', 'little'].forEach(function(endian) {
87
+ var w = new BinaryWriter(endian);
88
+ w.WriteUInt64(value);
89
+ var r = new BinaryReader(w.ByteBuffer, endian);
90
+ assert.equal(r.ReadUInt64(), value);
91
+ });
92
+ });
93
+
94
+ test('round-trip: UInt64 (Number argument is coerced)', function() {
95
+ ['big', 'little'].forEach(function(endian) {
96
+ var w = new BinaryWriter(endian);
97
+ w.WriteUInt64(123);
98
+ var r = new BinaryReader(w.ByteBuffer, endian);
99
+ assert.equal(r.ReadUInt64(), 123n);
100
+ });
101
+ });
102
+
103
+ test('round-trip: Int64 (BigInt argument)', function() {
104
+ var value = -81985529216486896n;
105
+ ['big', 'little'].forEach(function(endian) {
106
+ var w = new BinaryWriter(endian);
107
+ w.WriteInt64(value);
108
+ var r = new BinaryReader(w.ByteBuffer, endian);
109
+ assert.equal(r.ReadInt64(), value);
110
+ });
111
+ });
112
+
113
+ test('round-trip: Int64 (negative Number argument is coerced)', function() {
114
+ ['big', 'little'].forEach(function(endian) {
115
+ var w = new BinaryWriter(endian);
116
+ w.WriteInt64(-123);
117
+ var r = new BinaryReader(w.ByteBuffer, endian);
118
+ assert.equal(r.ReadInt64(), -123n);
119
+ });
120
+ });
@@ -0,0 +1,115 @@
1
+ var test = require('node:test');
2
+ var assert = require('node:assert/strict');
3
+ var binutils = require('../binutils.js');
4
+ var BinaryWriter = binutils.BinaryWriter;
5
+
6
+ function bytes(writer) {
7
+ return Array.from(writer.ByteBuffer);
8
+ }
9
+
10
+ test('BinaryWriter: constructor defaults', function() {
11
+ var w = new BinaryWriter();
12
+ assert.equal(w.Endianness, 'big');
13
+ assert.equal(w.Encoding, 'ascii');
14
+ assert.equal(w.Length, 0);
15
+ assert.equal(w.ByteBuffer.length, 0);
16
+ });
17
+
18
+ test('BinaryWriter: WriteUInt8', function() {
19
+ var w = new BinaryWriter();
20
+ w.WriteUInt8(200);
21
+ assert.deepEqual(bytes(w), [200]);
22
+ assert.equal(w.Length, 1);
23
+ });
24
+
25
+ test('BinaryWriter: WriteInt8 (signed)', function() {
26
+ var w = new BinaryWriter();
27
+ w.WriteInt8(-1);
28
+ assert.deepEqual(bytes(w), [0xFF]);
29
+ assert.equal(w.Length, 1);
30
+ });
31
+
32
+ test('BinaryWriter: WriteUInt16 honors endianness', function() {
33
+ var big = new BinaryWriter('big');
34
+ big.WriteUInt16(0x1234);
35
+ assert.deepEqual(bytes(big), [0x12, 0x34]);
36
+ assert.equal(big.Length, 2);
37
+
38
+ var little = new BinaryWriter('little');
39
+ little.WriteUInt16(0x1234);
40
+ assert.deepEqual(bytes(little), [0x34, 0x12]);
41
+ });
42
+
43
+ test('BinaryWriter: WriteUInt32 honors endianness', function() {
44
+ var big = new BinaryWriter('big');
45
+ big.WriteUInt32(0x12345678);
46
+ assert.deepEqual(bytes(big), [0x12, 0x34, 0x56, 0x78]);
47
+ assert.equal(big.Length, 4);
48
+
49
+ var little = new BinaryWriter('little');
50
+ little.WriteUInt32(0x12345678);
51
+ assert.deepEqual(bytes(little), [0x78, 0x56, 0x34, 0x12]);
52
+ });
53
+
54
+ test('BinaryWriter: WriteInt16 / WriteInt32 (signed)', function() {
55
+ var w16 = new BinaryWriter('big');
56
+ w16.WriteInt16(-2);
57
+ assert.deepEqual(bytes(w16), [0xFF, 0xFE]);
58
+
59
+ var w32 = new BinaryWriter('little');
60
+ w32.WriteInt32(-1);
61
+ assert.deepEqual(bytes(w32), [0xFF, 0xFF, 0xFF, 0xFF]);
62
+ });
63
+
64
+ test('BinaryWriter: WriteFloat', function() {
65
+ var big = new BinaryWriter('big');
66
+ big.WriteFloat(1.5);
67
+ assert.deepEqual(bytes(big), [0x3F, 0xC0, 0x00, 0x00]);
68
+ assert.equal(big.Length, 4);
69
+
70
+ var little = new BinaryWriter('little');
71
+ little.WriteFloat(1.5);
72
+ assert.deepEqual(bytes(little), [0x00, 0x00, 0xC0, 0x3F]);
73
+ });
74
+
75
+ test('BinaryWriter: WriteDouble', function() {
76
+ var w = new BinaryWriter('big');
77
+ w.WriteDouble(1.5);
78
+ assert.deepEqual(bytes(w), [0x3F, 0xF8, 0, 0, 0, 0, 0, 0]);
79
+ assert.equal(w.Length, 8);
80
+ });
81
+
82
+ test('BinaryWriter: WriteBytes accepts an array', function() {
83
+ var w = new BinaryWriter();
84
+ w.WriteBytes([1, 2, 3]);
85
+ assert.deepEqual(bytes(w), [1, 2, 3]);
86
+ assert.equal(w.Length, 3);
87
+ });
88
+
89
+ test('BinaryWriter: WriteBytes accepts a Buffer', function() {
90
+ var w = new BinaryWriter();
91
+ w.WriteBytes(Buffer.from([9, 8, 7]));
92
+ assert.deepEqual(bytes(w), [9, 8, 7]);
93
+ assert.equal(w.Length, 3);
94
+ });
95
+
96
+ test('BinaryWriter: WriteBytes accepts a string (per-char codes)', function() {
97
+ var w = new BinaryWriter();
98
+ w.WriteBytes('ABC');
99
+ assert.deepEqual(bytes(w), [65, 66, 67]);
100
+ assert.equal(w.Length, 3);
101
+ });
102
+
103
+ test('BinaryWriter: WriteBytes rejects non-Buffer/non-Array input', function() {
104
+ assert.throws(function() { new BinaryWriter().WriteBytes(4); }, /Invalid Buffer object/);
105
+ assert.throws(function() { new BinaryWriter().WriteBytes({}); }, /Invalid Buffer object/);
106
+ });
107
+
108
+ test('BinaryWriter: multiple writes accumulate and track Length', function() {
109
+ var w = new BinaryWriter('big');
110
+ w.WriteUInt8(1);
111
+ w.WriteUInt16(0x0203);
112
+ w.WriteUInt32(0x04050607);
113
+ assert.deepEqual(bytes(w), [1, 2, 3, 4, 5, 6, 7]);
114
+ assert.equal(w.Length, 7);
115
+ });