Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Here are some non-exhaustive examples:
```typescript
import { Decoder } from "binary-util"

const data = Buffer.from([
const data = new Uint8Array([
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21,
])
const decoder = new Decoder(data)
Expand Down
70 changes: 38 additions & 32 deletions src/decoder.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { Buffer } from "node:buffer"

import { Decoder } from "binary-util"
import { describe, expect, it } from "vitest"

describe(".currentOffset", () => {
it("example works as described", () => {
const buffer = Buffer.from([0x01, 0x01, 0x00, 0x00])
const buffer = new Uint8Array([0x01, 0x01, 0x00, 0x00])
const decoder = new Decoder(buffer)
expect(decoder.readUint32()).toBe(257)
expect(decoder.currentOffset).toBe(4)
Expand All @@ -14,7 +12,7 @@ describe(".currentOffset", () => {

describe(".endianness", () => {
it("example works as described", () => {
const buffer = Buffer.from([0x00, 0x00, 0x01, 0x01])
const buffer = new Uint8Array([0x00, 0x00, 0x01, 0x01])
const decoder = new Decoder(buffer)
decoder.endianness("big")
expect(decoder.readUint32()).toBe(257)
Expand All @@ -23,7 +21,7 @@ describe(".endianness", () => {

describe(".seek", () => {
it("example works as described", () => {
const buffer = Buffer.from([0xff, 0xff, 0x01, 0x00])
const buffer = new Uint8Array([0xff, 0xff, 0x01, 0x00])
const decoder = new Decoder(buffer)
expect(decoder.seek(2)).toBe(0)
expect(decoder.readUint16()).toBe(1)
Expand All @@ -32,7 +30,7 @@ describe(".seek", () => {

describe(".goto", () => {
it("example works as described", () => {
const buffer = Buffer.from([0xff, 0xff, 0x01, 0x00])
const buffer = new Uint8Array([0xff, 0xff, 0x01, 0x00])
const decoder = new Decoder(buffer)
expect(decoder.readUint16()).toBe(65535)
expect(decoder.goto(0)).toBe(2)
Expand All @@ -43,21 +41,21 @@ describe(".goto", () => {

describe(".alignTo", () => {
it("skips forwards correctly if diff if positive", () => {
const decoder = new Decoder(Buffer.alloc(16))
const decoder = new Decoder(new Uint8Array(16))
decoder.seek(12)
decoder.alignTo(16)
expect(decoder.currentOffset).toBe(16)
})

it("skips forwards correctly if diff if negative", () => {
const decoder = new Decoder(Buffer.alloc(16))
const decoder = new Decoder(new Uint8Array(16))
decoder.seek(4)
decoder.alignTo(16)
expect(decoder.currentOffset).toBe(16)
})

it("does nothing if already aligned", () => {
const decoder = new Decoder(Buffer.alloc(16))
const decoder = new Decoder(new Uint8Array(16))
decoder.seek(8)
decoder.alignTo(4)
expect(decoder.currentOffset).toBe(8)
Expand All @@ -66,7 +64,7 @@ describe(".alignTo", () => {
it("example works as described", () => {
// prettier-ignore
// Assume we have a header 5 bytes long, then 3 bytes of padding up to 8 bytes in, then more data
const buffer = Buffer.from([
const buffer = new Uint8Array([
0x74, 0xee, 0xb2, 0x36, 0x0c, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
])
Expand All @@ -83,13 +81,13 @@ describe(".alignTo", () => {
describe("integers", () => {
describe(".readInt8", () => {
it("example works as described", () => {
const decoder = new Decoder(Buffer.from([0xff]))
const decoder = new Decoder(new Uint8Array([0xff]))
expect(decoder.readInt8()).toBe(-1)
})

it("pointer example works as described", () => {
// (3 + 0) | pad | -1
const decoder = new Decoder(Buffer.from([0x03, 0x00, 0x00, 0xff]))
// (3 + 0) | pad | -1
const decoder = new Decoder(new Uint8Array([0x03, 0x00, 0x00, 0xff]))
const pointer = decoder.readUint16()
expect(pointer).toBe(3)
expect(decoder.readInt8({ pointer })).toBe(-1)
Expand All @@ -98,13 +96,13 @@ describe("integers", () => {

describe(".readUint8", () => {
it("example works as described", () => {
const decoder = new Decoder(Buffer.from([0xff]))
const decoder = new Decoder(new Uint8Array([0xff]))
expect(decoder.readUint8()).toBe(255)
})

it("pointer example works as described", () => {
// (3 + 0) | pad | -1
const decoder = new Decoder(Buffer.from([0x03, 0x00, 0x00, 0xff]))
// (3 + 0) | pad | -1
const decoder = new Decoder(new Uint8Array([0x03, 0x00, 0x00, 0xff]))
const pointer = decoder.readUint16()
expect(pointer).toBe(3)
expect(decoder.readUint8({ pointer })).toBe(255)
Expand All @@ -113,12 +111,12 @@ describe("integers", () => {

describe(".readInt16", () => {
it("example works as described", () => {
const decoder = new Decoder(Buffer.from([0xff, 0xff]))
const decoder = new Decoder(new Uint8Array([0xff, 0xff]))
expect(decoder.readInt16()).toBe(-1)
})

it("pointer example works as described", () => {
const decoder = new Decoder(Buffer.from([0x03, 0x00, 0x00, 0xff, 0xff]))
const decoder = new Decoder(new Uint8Array([0x03, 0x00, 0x00, 0xff, 0xff]))
const pointer = decoder.readUint16()
expect(pointer).toBe(3)
expect(decoder.readInt16({ pointer })).toBe(-1)
Expand All @@ -127,13 +125,13 @@ describe("integers", () => {

describe(".readUint16", () => {
it("decodes a value correctly", () => {
const decoder = new Decoder(Buffer.from([0xff, 0xff]))
const decoder = new Decoder(new Uint8Array([0xff, 0xff]))
expect(decoder.readUint16()).toBe(65535)
})

it("decodes a value via a pointer correctly", () => {
// (3 + 0) | pad | -1
const decoder = new Decoder(Buffer.from([0x03, 0x00, 0x00, 0xff, 0xff]))
// (3 + 0) | pad | -1
const decoder = new Decoder(new Uint8Array([0x03, 0x00, 0x00, 0xff, 0xff]))
const pointer = decoder.readUint16()
expect(pointer).toBe(3)
expect(decoder.readUint16({ pointer })).toBe(65535)
Expand All @@ -143,25 +141,31 @@ describe("integers", () => {

describe(".readBuffer", () => {
it("returns part of buffer", () => {
const decoder = new Decoder(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]))
expect(decoder.readBuffer({ length: 2 })).toEqual(Buffer.from([0x01, 0x02]))
const decoder = new Decoder(new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]))
expect(decoder.readBuffer({ length: 2 })).toEqual(new Uint8Array([0x01, 0x02]))
})

it("uses currentOffset", () => {
const decoder = new Decoder(Buffer.from([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]))
const decoder = new Decoder(
new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]),
)
decoder.seek(2)
expect(decoder.readBuffer({ length: 2 })).toEqual(Buffer.from([0x03, 0x04]))
expect(decoder.readBuffer({ length: 2 })).toEqual(new Uint8Array([0x03, 0x04]))
})

it("pointer option works", () => {
const decoder = new Decoder(Buffer.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x09]))
expect(decoder.readBuffer({ pointer: 0x06, length: 2 })).toEqual(Buffer.from([0x08, 0x09]))
const decoder = new Decoder(
new Uint8Array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x09]),
)
expect(decoder.readBuffer({ pointer: 0x06, length: 2 })).toEqual(
new Uint8Array([0x08, 0x09]),
)
})
})

describe(".readString", () => {
it("should parse strings correctly", () => {
const data = Buffer.from("testhelloworld")
const data = new TextEncoder().encode("testhelloworld")

const decoder = new Decoder(data)
expect(decoder.readString({ length: 4 })).toBe("test")
Expand All @@ -170,7 +174,7 @@ describe(".readString", () => {
})

it("should parse zeroed strings correctly", () => {
const data = Buffer.from("test\x00hello\x00world\x00")
const data = new TextEncoder().encode("test\x00hello\x00world\x00")

const decoder = new Decoder(data)
expect(decoder.readString({ zeroed: true })).toBe("test")
Expand All @@ -180,7 +184,7 @@ describe(".readString", () => {
})

it("should parse the README example", () => {
const data = Buffer.from([
const data = new Uint8Array([
0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21,
])
const decoder = new Decoder(data)
Expand All @@ -190,7 +194,9 @@ it("should parse the README example", () => {
expect(decoder.readString({ length: 7 })).toBe(", World")

// Or you can use it more ergonomically when possible
const decoder2 = new Decoder(Buffer.alloc(2, 2))
const filledArray = new Uint8Array(2)
filledArray.fill(2)
const decoder2 = new Decoder(filledArray)
const result = {
a: decoder2.readUint8(),
b: decoder2.readUint8(),
Expand All @@ -200,7 +206,7 @@ it("should parse the README example", () => {
})

it("should allow parsing simple data", () => {
const testBuffer = Buffer.alloc(17)
const testBuffer = new Uint8Array(17)
const values = [3257, 3263, 6483, 9773]
const view = new DataView(testBuffer.buffer)
view.setInt8(0, 4)
Expand Down
51 changes: 26 additions & 25 deletions src/decoder.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { type Buffer } from "node:buffer"

type BaseOptions = {
pointer?: number
}

type BaseStringOptions = {
encoding?: BufferEncoding
encoding?: "utf8"
}

type BufferOptions = BaseOptions & { length: number }

type StringOptions = BaseStringOptions & ({ length: number } | { zeroed: boolean })

export class Decoder {
#buffer: Buffer
#buffer: Uint8Array
#currentOffset = 0
#littleEndian = true

Expand All @@ -23,7 +21,7 @@
*
* Defaults to little endian.
*/
constructor(buffer: Buffer) {
constructor(buffer: Uint8Array) {
this.#buffer = buffer
}

Expand All @@ -32,8 +30,8 @@
*
* @example
* ```ts
* // 1 + 256 + 0 + 0
* const buffer = Buffer.from([0x01, 0x01, 0x00, 0x00])
* // 1 + 256 + 0 + 0
* const buffer = new Uint8Array([0x01, 0x01, 0x00, 0x00])
* const decoder = new Decoder(buffer)
* decoder.readUint32() // 257
* decoder.currentOffset // 4
Expand All @@ -48,8 +46,8 @@
* @param endianness - `big` or `little`
* @example
* ```ts
* // 0 + 0 + 256 + 1
* const buffer = Buffer.from([0x00, 0x00, 0x01, 0x01])
* // 0 + 0 + 256 + 1
* const buffer = new Uint8Array([0x00, 0x00, 0x01, 0x01])
* const decoder = new Decoder(buffer)
* decoder.endianness("big")
* decoder.readUint32() // 257
Expand All @@ -65,8 +63,8 @@
* @returns The previous offset.
* @example
* ```ts
* // (max + max)|(1 + 0)
* const buffer = Buffer.from([0xFF, 0xFF, 0x01, 0x00])
* // (max + max)|(1 + 0)
* const buffer = new Uint8Array([0xFF, 0xFF, 0x01, 0x00])
* const decoder = new Decoder(buffer)
* decoder.seek(2) // 0
* decoder.readUint16() // 1
Expand All @@ -84,8 +82,8 @@
* @returns The previous offset.
* @example
* ```ts
* // (max + max)|(1 + 0)
* const buffer = Buffer.from([0xff, 0xff, 0x01, 0x00])
* // (max + max)|(1 + 0)
* const buffer = new Uint8Array([0xff, 0xff, 0x01, 0x00])
* const decoder = new Decoder(buffer)
* decoder.readUint16() // 65535
* decoder.goto(0) // 2
Expand All @@ -109,7 +107,7 @@
* @example
* ```ts
* // Assume we have a header 5 bytes long, then 3 bytes of padding up to 8 bytes in, then more data
* const buffer = Buffer.from([
* const buffer = new Uint8Array([
* 0x74, 0xee, 0xb2, 0x36, 0x0c, 0x00, 0x00, 0x00,
* 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
* ])
Expand Down Expand Up @@ -142,13 +140,13 @@
* @returns Value between -128 and 127.
* @example
* ```ts
* const decoder = new Decoder(Buffer.from([0xff]))
* const decoder = new Decoder(new Uint8Array([0xff]))
* expect(decoder.readInt8()).toBe(-1)
* ```
* @example Read via pointer offset
* ```ts
* // (3 + 0) | pad | -1
* const decoder = new Decoder(Buffer.from([0x03, 0x00, 0x00, 0xff]))
* // (3 + 0) | pad | -1
* const decoder = new Decoder(new Uint8Array([0x03, 0x00, 0x00, 0xff]))
* const pointer = decoder.readUint16() // 3
* decoder.readInt8({ pointer }) // -1
* ```
Expand All @@ -174,13 +172,13 @@
* @returns Value between 0 and 255.
* @example
* ```ts
* const decoder = new Decoder(Buffer.from([0xff]))
* const decoder = new Decoder(new Uint8Array([0xff]))
* expect(decoder.readInt8()).toBe(255)
* ```
* @example Read via pointer offset
* ```ts
* // (3 + 0) | pad | -1
* const decoder = new Decoder(Buffer.from([0x03, 0x00, 0x00, 0xff]))
* // (3 + 0) | pad | -1
* const decoder = new Decoder(new Uint8Array([0x03, 0x00, 0x00, 0xff]))
* const pointer = decoder.readUint16() // 3
* decoder.readInt8({ pointer }) // 255
* ```
Expand Down Expand Up @@ -377,7 +375,7 @@
* @param opts.length {number} - Length of slice (not inclusive).
* @param opts.pointer {number} - See {@link Decoder#readUint8}.
*/
readBuffer(opts: BufferOptions): Buffer {
readBuffer(opts: BufferOptions): Uint8Array {
const offset = opts.pointer ?? this.#currentOffset
const result = this.#buffer.subarray(offset, offset + opts.length)

Expand All @@ -396,19 +394,19 @@
* @param opts.encoding - Defaults to `utf8`.
* @example Zeroed
* ```ts
* const decoder = new Decoder(Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00]))
* const decoder = new Decoder(new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x00]))
* decoder.readString({ zeroed: true }) // "hello"
* ```
* @example Length
* ```ts
* const decoder = new Decoder(Buffer.from([0x68, 0x65, 0x6c, 0x6c, 0x6f]))
* const decoder = new Decoder(new Uint8Array([0x68, 0x65, 0x6c, 0x6c, 0x6f]))
* decoder.readString({ length: 5 }) // "hello"
* ```
*/
readString(opts: StringOptions): string {
let strBuffer: Buffer | null = null
let strBuffer: Uint8Array | null = null

if ("length" in opts && opts.length != null) {

Check warning on line 409 in src/decoder.ts

View workflow job for this annotation

GitHub Actions / lint

typescript(no-unnecessary-condition)

This condition will always return the same value since the types have no overlap.
strBuffer = this.readBuffer(opts)
}

Expand All @@ -428,6 +426,9 @@
throw new Error("You need to specify either `zeroed` or `length`.")
}

return strBuffer.toString(opts.encoding ?? "utf8")
this.#textDecoder ??= new TextDecoder(opts.encoding ?? "utf8")

Check warning on line 429 in src/decoder.ts

View workflow job for this annotation

GitHub Actions / lint

typescript(no-unnecessary-condition)

Unnecessary optional chain on a non-nullish value.
return this.#textDecoder.decode(strBuffer)
}

#textDecoder!: TextDecoder
}
Loading
Loading