Buffer Layout
buffer-layout is a pure JavaScript utility library designed for translating between JavaScript values and Node.js Buffers. It enables developers to define and manipulate binary data structures that closely mimic C structs, offering explicit control over memory layout and endianness. The library provides layout constructors for various data types, including signed and unsigned integers (1 to 6 bytes, with 64-bit integers decoded as standard JavaScript Numbers), floats, doubles, sequences, complex structures, unions, bit fields, NUL-terminated C strings, and raw data blobs. The current version, 1.2.2, was last published in 2021. Given its age and lack of recent updates on its GitHub repository (last commit approximately four years ago), the project appears to be in an abandoned state, with no active development or maintenance. A key differentiator is its detailed control over C-style memory layouts, including the necessity of manually accounting for padding and bit fields.
Common errors
-
TypeError: require is not a function
cause Attempting to `require('buffer-layout')` in an ES Module context, or `import`ing `buffer-layout` directly in a CommonJS context.fixEnsure you are using CommonJS `const lo = require('buffer-layout');` in a CJS file, or configure your bundler to handle CJS modules in an ESM project. If using native ESM, you might need to use `import * as lo from 'buffer-layout';` or ensure your `package.json` specifies `"type": "commonjs"` for the file. -
AssertionError: Buffers not equal
cause Often occurs when `encode` or `decode` operations produce unexpected results, frequently due to incorrect layout definitions, especially missing padding for C-style structs.fixReview your layout definition against the target binary format or C struct definition. Pay close attention to data types, endianness, and explicit padding needed for memory alignment. Use `Buffer.compare` or `Buffer.equals` for byte-by-byte comparison during debugging. -
RangeError: offset is out of bounds
cause Trying to encode or decode data beyond the allocated size of the `Buffer`, or specifying an incorrect offset during read/write operations within `buffer-layout` methods.fixVerify that the `Buffer.alloc()` size is sufficient for the defined layout (`layout.span`). Double-check any manual offset parameters passed to `encode` or `decode` methods.
Warnings
- breaking The `buffer-layout` package is currently abandoned. There will be no further updates, bug fixes, or security patches, which could lead to compatibility issues with newer Node.js versions or unaddressed vulnerabilities.
- gotcha When defining structures that mimic C `struct`s, developers must manually account for memory alignment and add explicit padding layouts. Failing to do so will result in incorrect buffer sizes, offsets, and ultimately corrupted data during encoding and decoding.
- gotcha 64-bit integral values (e.g., `lo.u64`, `lo.s64`) are decoded into standard JavaScript `Number` types. Due to JavaScript's floating-point number representation (IEEE 754 double-precision), integers larger than 2^53 - 1 (or smaller than -2^53 + 1) cannot be precisely represented, leading to potential data loss or approximation.
- gotcha The package does not ship with its own TypeScript declaration files. While community-maintained `@types/buffer-layout` might exist, their reliability and up-to-dateness can vary. This leads to a suboptimal developer experience in TypeScript projects.
Install
-
npm install buffer-layout -
yarn add buffer-layout -
pnpm add buffer-layout
Imports
- buffer-layout
import lo from 'buffer-layout';
const lo = require('buffer-layout'); - lo.struct
import { struct } from 'buffer-layout';const { struct } = require('buffer-layout'); // or lo.struct - lo.u8
import { u8 } from 'buffer-layout';const { u8 } = require('buffer-layout'); // or lo.u8
Quickstart
const assert = require('assert');
const lo = require('buffer-layout');
// Define a C-like packed struct without padding
// C: struct { uint8_t a; uint16_t b; } __attribute__((__packed__)) MyPackedStruct;
const packedStruct = lo.struct([
lo.u8('a'),
lo.u16le('b') // u16le for unsigned 16-bit little-endian
]);
const packedBuffer = Buffer.alloc(3); // 1 byte for 'a', 2 bytes for 'b'
const data1 = { a: 0x12, b: 0x3456 };
packedStruct.encode(data1, packedBuffer);
console.log('Packed Buffer:', packedBuffer.toString('hex'));
assert.equal(packedBuffer.toString('hex'), '125634');
assert.deepStrictEqual(packedStruct.decode(packedBuffer), data1);
// Define a C-like struct WITH padding, simulating a 32-bit aligned machine
// C: struct { uint8_t v; uint32_t u32; } MyAlignedStruct;
// In C, u32 would typically be 4-byte aligned, causing 3 bytes of padding after u8.
const alignedStruct = lo.struct([
lo.u8('v'),
lo.seq(lo.u8(), 3, 'padding'), // explicit padding for 4-byte alignment
lo.u32le('u32')
]);
const alignedBuffer = Buffer.alloc(8); // 1 byte (v) + 3 bytes (padding) + 4 bytes (u32)
alignedBuffer.fill(0xBD); // Fill with a recognizable pattern for padding
const data2 = { v: 0x01, u32: 0x12345678 };
alignedStruct.encode(data2, alignedBuffer);
console.log('Aligned Buffer (with padding):', alignedBuffer.toString('hex'));
assert.equal(alignedBuffer.toString('hex'), '01bdbdbd78563412');
assert.deepStrictEqual(alignedStruct.decode(alignedBuffer), { v: 1, u32: 0x12345678 });
console.log('Quickstart examples completed successfully.');