Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
507130f
docs(Controller): use shorthand notation for PrimitiveSymbol
tperale Mar 14, 2025
1bc3283
docs(Condition): use shorthand notation for PrimitiveSymbol
tperale Mar 15, 2025
cbf3cd3
test(Bson): add bson object creation testing
tperale Mar 16, 2025
7dabab2
docs(Controller): add arithmetic expression example
tperale Mar 17, 2025
c35dabe
feat(Primitive): add support for {un}signed 24 bits integer
tperale Mar 18, 2025
65e973f
docs(Primitive): fix primitive shorthand descriptions
tperale Mar 19, 2025
bb41380
chore(build): ts-loader no longer needed
tperale Mar 20, 2025
401c2f7
chore(build): exports decorators categories
tperale Mar 22, 2025
1265528
fix(bindump): various bug fixes
tperale Mar 21, 2025
8fcc034
feat(bindump): support dumping binary objects
tperale Mar 24, 2025
5203615
chore(bindump): use ArrayBufferLike & Views instead BinaryReader
tperale Mar 27, 2025
9c0d4d3
chore(bindump): rename hexdump -> bindump
tperale Mar 27, 2025
ab82cb4
feat(bindump): create metadata while reading binary definition
tperale Mar 25, 2025
b8f33da
test(Bson): add bindump example
tperale Mar 26, 2025
fe43da6
test: custom Matchers accept both ArrayBuffer and views
tperale Mar 28, 2025
75af97b
fix(Cursor): turn Binary{Reader,Writer} into DataView children
tperale Mar 28, 2025
450e633
docs: add browser information
tperale Mar 29, 2025
a9f3117
fix(Cursor): no longer returns EOF
tperale Mar 30, 2025
4676c2e
fix(Controller): uncomplete relations should throw
tperale Mar 30, 2025
2744ff3
refactor(Example): minor changes
tperale Mar 30, 2025
a3bbeb2
feat(reader): bindump on error
tperale Apr 1, 2025
c477185
docs: update w/ new binread API
tperale Apr 1, 2025
3f57666
chore(writer): improve api for binwrite
tperale Apr 2, 2025
de5cada
docs: update to align on binwrite api
tperale Apr 3, 2025
155b239
2.0.0
tperale Apr 3, 2025
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,11 @@ class Protocol {
### 🔍 Reading an ArrayBuffer into Objects

```typescript
import { binread, BinaryReader } from 'binspector'
import { binread } from 'binspector'

const buf = new Uint8Array([0x02, 0x01, 0x02, 0x03, 0x04])

binread(new BinaryReader(buf), Protocol)
binread(buf, Protocol)
// => { len: 2, coords: [{ x: 1, y: 2 }, { x: 3, y: 4 }] }
```

Expand All @@ -151,7 +151,7 @@ import { binwrite, BinaryWriter } from 'binspector'

const obj = { len: 2, coords: [{ x: 1, y: 2 }, { x: 3, y: 4 }] }

binwrite(new BinaryWriter(), Protocol, obj).buffer()
binwrite(obj, Protocol)
// => [0x02, 0x01, 0x02, 0x03, 0x04]
```

Expand Down
54 changes: 51 additions & 3 deletions docs/Getting-Started-With-Binspector.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ import * as fs from 'node:fs'
import * as path from 'node:path'

const data = fs.readFileSync(path.join(import.meta.dirname, 'file.bin'))
const protocol = binread(new BinaryReader(data), Protocol)
const protocol = binread(data, Protocol)
```

From an high level point of view the `binread` function will first check
Expand Down Expand Up @@ -165,9 +165,9 @@ const obj = {
bar: 0x01
}

const protocol = binwrite(new BinaryWriter(), obj, Protocol)
const protocol = binwrite(obj, Protocol)

const buf = protocol.buffer() // <= ArrayBuffer(...)
const buf = protocol.buffer // <= ArrayBuffer(...)

await fs.appendFile(path.join(__dirname, 'proto.bin'), new Uint8Array(buf));
```
Expand Down Expand Up @@ -196,3 +196,51 @@ flowchart TB
end
PostOperation --> A@{ shape: framed-circle, label: "Stop" }
```

### 🌐 Using Binspector in the browser

Considering the following tag in the webpage.

```html
<!-- Input to pass the file to the browser -->
<input type="file" id="file-input" />
<!-- Button to download the file -->
<button id="file-download" />
```

A file `input` that will read the content of the file when sent to the webpage
and a `button` that will download a serialized version of the object sent to
the webpage.

The following code shows how to handle the content with Binspector.

```typescript
import { BinaryReader, binread } from 'binspector'
import * as fs from 'node:fs'
import * as path from 'node:path'

let protocol = undefined

const fileInput = document.getElementById("file-input")
fileInput.addEventListener("change", handleFileInput)

const fileDownload = document.getElementById("file-download")
fileDownload.addEventListener("click", handleFileDownload)

function handleFileInput(event) {
const file = event.target.files[0]
file.arraybuffer().then((arr) => {
protocol = binread(arr, Protocol)
})
}

function handleFileDownload(event) {
if (protocol !== undefined) {
const _protocol = binwrite(protocol, Protocol)

const blob = new Blob([_protocol.buffer])
const url = URL.createObjectURL(blob)
window.open(url)
}
}
```
7 changes: 4 additions & 3 deletions example/bitmap/bmp.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PrimitiveSymbol, Relation, Count, Enum, IfThen, Else, Choice, Matrix, Offset, Uint8, Uint16, Uint32, Ascii, Endian, BinaryCursorEndianness } from '../../src/index.ts'
import { PrimitiveSymbol, Relation, Count, Enum, IfThen, Else, Choice, Matrix, Offset, Uint8, Uint16, Uint32, Ascii, LittleEndian } from '../../src/index.ts'
import {
OS22XBITMAPHEADER, BITMAPINFOHEADER, BITMAPV2INFOHEADER, BITMAPV3INFOHEADER, BITMAPV4INFOHEADER, BITMAPV5INFOHEADER,
} from './header.ts'
Expand Down Expand Up @@ -50,7 +50,7 @@ class BitmapFileHeader {
offset: number
}

@Endian(BinaryCursorEndianness.LittleEndian)
@LittleEndian
export class Bitmap {
@Relation(BitmapFileHeader)
file_header: BitmapFileHeader
Expand Down Expand Up @@ -80,7 +80,6 @@ export class Bitmap {
color_table: RGBQ[]

/* The gap size depend on the offset found in the BitmapFileHeader */
/* Just use the `@Pre` decorator to move the cursor to the correct place */
@Offset('file_header.offset')
@Matrix('bitmap_header.width', 'bitmap_header.height', 4)
@Choice('bitmap_header.bits_per_pixels', {
Expand Down Expand Up @@ -112,3 +111,5 @@ export class Bitmap {
console.log(lines.reverse().join('\n'))
}
}

export default Bitmap
60 changes: 54 additions & 6 deletions example/bson/bson.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Count, Enum, Int32, Match, PrimitiveSymbol, Relation, Select, Size, Uint16, Uint32, Uint8, Utf8, LittleEndian, NullTerminated, BinaryReader, binread, jsonify, binwrite, BinaryWriter } from '../../src/index.ts'
import { type BinspectorMetaClass } from '../../src/bindump.ts'
import { Count, Enum, Int32, Match, PrimitiveSymbol, Relation, Select, Size, Uint16, Uint32, Uint8, Utf8, LittleEndian, NullTerminated, BinaryReader, binread, jsonify, binwrite, computeBinSize } from '../../src/index.ts'

// When calling JSON.stringify on a BigInt an error will be raised by default
// We need to define the serializer for this type.
Expand All @@ -10,6 +11,9 @@

BigInt.prototype.toJSON = () => Number(this)

type JSONPrimitive = string | number | boolean | JSONObject | null | undefined
type JSONObject = { [key: string]: JSONPrimitive } | JSONPrimitive[]

enum BSONType {
EndOfObject = 0x00,
NumberDouble = 0x01,
Expand Down Expand Up @@ -44,7 +48,7 @@

@Match(0)
@Uint8
terminator: number
terminator: number = 0

toJson () {
return this.name
Expand Down Expand Up @@ -161,7 +165,7 @@
name: string

@Select(_ => BSONTypeMap[_.bson_type]())
data: any

Check warning on line 168 in example/bson/bson.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

toJson () {
switch (this.bson_type) {
Expand Down Expand Up @@ -192,6 +196,38 @@
return { [this.name]: jsonify(this.data) }
}
}

static create (property: string, obj: JSONPrimitive | JSONObject) {
const res = new BsonElement()
res.name = property
if (Array.isArray(obj)) {
res.bson_type = BSONType.Array
res.data = Bson.fromObject(obj)
} else if (typeof obj === 'string') {
res.bson_type = BSONType.String
const str = new BsonString()
str.name = obj
str.size = (obj.length + 1)
res.data = str
} else if (typeof obj === 'number') {
res.bson_type = BSONType.NumberInt
res.data = obj
} else if (typeof obj === 'bigint') {
res.bson_type = BSONType.NumberLong
res.data = obj
} else if (typeof obj === 'boolean') {
res.bson_type = BSONType.Boolean
res.data = obj
} else if (obj === null || typeof obj === 'undefined') {
res.bson_type = BSONType.Null
res.data = undefined
} else if (typeof obj === 'object') {
res.bson_type = BSONType.Object
res.data = Bson.fromObject(obj)
}

return res
}
}

@LittleEndian
Expand All @@ -205,7 +241,7 @@

@Match(0)
@Uint8
terminator: number
terminator: number = 0

toJson () {
return this.fields.reduce((obj, curr) => ({
Expand All @@ -215,10 +251,22 @@
}

toBuffer () {
return binwrite(new BinaryWriter(), Bson, this).buffer()
return binwrite(this)
}

static from (buf: ArrayBufferLike) {
return binread(new BinaryReader(buf), Bson)
static from (buf: ArrayBufferLike, meta: Partial<BinspectorMetaClass> = {}) {
return binread(new BinaryReader(buf), Bson, { meta })
}

static fromObject (obj: JSONObject) {
const bson = new Bson()
const fields = Object.entries(obj).map(([k, v]) => BsonElement.create(k, v))

bson.fields = fields
bson.size = (computeBinSize(fields) + 5)

return bson
}
}

export default Bson
24 changes: 24 additions & 0 deletions example/bson/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,27 @@ test('Testing BSON "nested.bson" read/write equality', () => {
const filename = path.join(path.dirname(fileURLToPath(import.meta.url)), 'nested.bson')
expect(filename).fileReadWriteEquality(Bson)
})

test('Testing Object Serialization', () => {
const obj = {
foo: undefined,
bar: {
first: [1, 2, 3],
second: 'hello'
}
}

const bson = Bson.fromObject(obj)
expect(bson.fields.length).toStrictEqual(2)
expect(bson.fields[0].name).toStrictEqual('foo')
expect(bson.fields[1].name).toStrictEqual('bar')
expect(bson.fields[0].data).toStrictEqual(undefined)
expect(bson.fields[1].data instanceof Bson).toStrictEqual(true)
expect(bson.fields[1].data.fields[0].name).toStrictEqual('first')
expect(bson.fields[1].data.fields[1].name).toStrictEqual('second')
expect(bson.fields[1].data.fields[0].data instanceof Bson).toStrictEqual(true)
expect(bson.fields[1].data.fields[0].data.fields.length).toStrictEqual(3)
expect(bson.fields[1].data.fields[1].data.name).toStrictEqual('hello')

expect(bson.toJson()).toMatchObject(obj)
})
55 changes: 48 additions & 7 deletions example/bson/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,60 @@
import { promises as fs } from 'node:fs'
import fs from 'node:fs'
import path from 'node:path'
import { Bson } from './bson.ts'
import BinDump, { type MetaBinReadClass } from '../../src/bindump.ts'

const USAGE = `
Usage:
Usage: deno run index.ts [options] <file>.bson

> deno run index.ts <file>.bson
Show the content of a BSON binary file as a JSON object using binspector.

Arguments:
file BSON file to read.

Options:
-j, --to-json Output the content as a JSON object.
-d, --dump Dump the content of the BSON binary in bindump format.
-h, --help Display this help.
`

function parseArgument (argv: string[]) {
const result = {
toJson: false,
dump: false,
help: false,
filename: '',
}

argv.forEach((arg) => {
if (arg === '-j' || arg === '--to-json') {
result.toJson = true
} else if (arg === '-d' || arg === '--dump') {
result.dump = true
} else if (arg === '-h' || arg === '--help') {
result.help = true
} else if ((arg = path.join(import.meta.dirname, arg)) && fs.existsSync(arg)) {
result.filename = arg
}
})

return result
}

if (process.argv[1] === import.meta.filename) {
console.log(JSON.stringify(process.argv))
const argv = process.argv.slice(2)
if (argv.length == 1) {
const data = await fs.readFile(path.join(import.meta.dirname, argv[0]))
console.log(JSON.stringify(Bson.from(data.buffer).toJson(), null, 2))
const args = parseArgument(process.argv.slice(2))
if (args.filename.length) {
const data = fs.readFileSync(args.filename)

const meta = {}
const bson = Bson.from(data.buffer, meta)
if (args.help) {
console.log(USAGE)
} else if (args.dump) {
console.log(BinDump.dump(data, meta as MetaBinReadClass, bson))
} else {
console.log(JSON.stringify(bson.toJson(), null, 2))
}
} else {
console.log(USAGE)
}
Expand Down
6 changes: 4 additions & 2 deletions example/devicetree/devicetree.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NullTerminatedString, Choice, Relation, Count, Match, While, Enum, Peek, Offset, Until, EOF, Uint8, Uint32, Uint64, Padding, Endian, BinaryCursorEndianness } from '../../src/index.ts'
import { NullTerminatedString, Choice, Relation, Count, Match, While, Enum, Peek, Offset, Until, EOF, Uint8, Uint32, Uint64, Padding, BigEndian } from '../../src/index.ts'

enum DTBStructureBlockToken {
FDT_BEGIN_NODE = 0x1,
Expand Down Expand Up @@ -131,7 +131,7 @@
}

function asObjectDtb (structs: DTBStructBlock[]): object {
function setObject (o: Record<string, any>, current: string[], key: string, value: any) {

Check warning on line 134 in example/devicetree/devicetree.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type

Check warning on line 134 in example/devicetree/devicetree.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
const currentObj = current.reduce((obj, k) => obj[k], o)

currentObj[key] = value
Expand Down Expand Up @@ -168,7 +168,7 @@
return result
}

@Endian(BinaryCursorEndianness.BigEndian)
@BigEndian
export class DTB {
@Relation(DTBHeader)
header: DTBHeader
Expand All @@ -188,7 +188,9 @@
@NullTerminatedString()
strings: string[]

asObject (): Record<string, any> {

Check warning on line 191 in example/devicetree/devicetree.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
return Reflect.get(asObjectDtb(this.structs), '')
}
}

export default DTB
7 changes: 4 additions & 3 deletions example/png/png.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Relation, Count, Match, Validate, While, Enum, Choice, Uint8, Uint16, Uint32, Ascii, Endian, BinaryCursorEndianness } from '../../src/index.ts'
import { Relation, Count, Match, Validate, While, Enum, Choice, Uint8, Uint16, Uint32, Ascii, BigEndian } from '../../src/index.ts'

enum PNGTypes {
IHDR = 'IHDR',
Expand Down Expand Up @@ -139,12 +139,11 @@ class PNGChunk {
})
data: PNGChunkIDAT | PNGChunkPLTE | PNGChunkbKGD | PNGChunkpHYs | PNGChunktIME | PNGChunkIHDR

// @Crc(u32)
@Uint32
crc: number
}

@Endian(BinaryCursorEndianness.BigEndian)
@BigEndian
export class PNG {
@Match([137, 80, 78, 71, 13, 10, 26, 10])
@Count(8)
Expand All @@ -156,3 +155,5 @@ export class PNG {
@Relation(PNGChunk)
chunks: PNGChunk[]
}

export default PNG
Loading
Loading