Security News
Fluent Assertions Faces Backlash After Abandoning Open Source Licensing
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
construct-js
Advanced tools
construct-js
is a library for creating byte level data structures.
npm i construct-js
construct-js
is all about creating a low-cost and performant abstraction for working with structured, binary data. If you've ever found yourself trying to manually assemble binary data in an ArrayBuffer - stuffing data, calculating sizes, scratching your head over endianness and offsets, then this is likely the tool for you.
construct-js
allows you to specify and manipulate binary data using an expressive API made up of standard primitives that you may be used to in lower level languages - such as structs, pointers, sizeof operators, and standard sized signed and unsigned integer types.
Why not? I mean - I think there is genuine utility, but even if there wasn't, it would simply be an interesting project to undertake.
In terms of actual utility, as the web and distributed services evolve, web pages and JavaScript are taking on increasing more diverse and complex task, as well as connecting to more elaborate and varied services. Typically communication channels between different services use simple interchange formats like JSON over HTTP or WebSockets, but for a variety of reasons this is not always ideal. A large part of the reason this is so widespread is that JavaScript traditionally hasn't had good facilities or abstractions for creating byte-level data structures. Now, however, with the advent and standardisation of Typed Arrays and BigInt, this is no longer the case. construct-js
allows developers to write expressive descriptions using standard native types like numbers, strings, and regular arrays - and outputs to an efficient Uint8Array format for interchange with the network, filesystem, or even across execution environments like WebAssembly.
The following example builds a (just about) valid* zip archive with one file inside - helloworld.txt
.
*At least when unzipped using the unzip command. Some GUI programs seem to have less success
import * as fs from 'fs/promises';
import {RawString, U16, U32, Struct, Pointer32, Endian} from 'construct-js';
const data = RawString('helloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworldhelloworld');
const dataSize = data.computeBufferSize();
const filename = RawString('helloworld.txt');
const filenameSize = filename.computeBufferSize();
// Create a stub for the top level struct that can be referenced by other structs
const zipFile = Struct('ZipFile');
const sharedHeaderInfo = Struct('sharedHeaderInfo')
.field('minVersion', U16(10))
.field('gpFlag', U16(0))
.field('compressionMethod', U16(0))
.field('lastModifiedTime', U16(0))
.field('lastModifiedDate', U16(0))
.field('crc32', U32(0))
.field('compressedSized', U32(dataSize))
.field('uncompressedSized', U32(dataSize))
.field('filenameSize', U16(filenameSize))
.field('extraFieldLength', U16(0));
const localHeader = Struct('localHeader')
.field('header', U32(0x04034b50))
.field('sharedHeaderInfo', sharedHeaderInfo)
.field('filename', filename);
const centralDirectory = Struct('centralDirectory')
.field('header', U32(0x02014b50))
.field('madeByVersion', U16(10))
.field('sharedHeaderInfo', sharedHeaderInfo)
.field('fileCommentSize', U16(0))
.field('diskNumber', U16(0))
.field('internalFileAttributes', U16(0))
.field('externalFileAttributes', U32(0))
.field('relativeOffset', U32(0))
.field('filename', filename);
const endOfCentralDirectory = Struct('endOfCentralDirectory')
.field('header', U32(0x06054b50))
.field('diskNumber', U16(0))
.field('centralDirDiskStart', U16(0))
.field('numberOfCentralDirsOnDisk', U16(1))
.field('totalNumberOfCentralDirs', U16(1))
.field('centralDirSize', U32(0))
.field('offsetToStart', Pointer32(zipFile, 'centralDirectory'))
.field('commentLength', U16(0));
// Finalise the top level struct
zipFile
.field('localHeader', localHeader)
.field('data', data)
.field('centralDirectory', centralDirectory)
.field('endOfCentralDirectory', endOfCentralDirectory);
const fileBuffer = zipFile.toUint8Array();
fs.writeFile('./test.zip', fileBuffer).then(() => {
console.log('Done writing zip file.');
});
.value()
method to fields.NullTerminatedString
fieldgetDeep
that allowed requesting nonsense values in the pathPointer8
, Pointer16
, Pointer32
, SizeOf8
, SizeOf16
and SizeOf32
fieldsStructs
. All endianness information comes directly from the Fields themselvesI8, I16, I32, I8s, I16s, I32s
-> S8, S16, S32, S8s, S16s, S32s
Struct(name: string, alignment = StructAlignment.Packed, paddingDirection = AlignmentPadding.AfterData)
Creates a Struct
object. alignment
specifies how much (if any) padding should be applied to the fields in order for them to align to a fixed byte boundary. paddingDirection
specifies where the extra bytes should be added (before or after the data).
.field(name: string, value: ConstructDataType)
Adds a field to the struct. name is used to lookup the field using methods like struct.get(name)
. value must be either a Struct
or one of the other data types provided by construct-js.
.get<T extends ConstructDataType>(name: string)
Returns the field with that name. Note: When using TypeScript, this value must be cast to the correct type, either using the generic or with the as
keyword:
const s = Struct('example').field('first', U8(0));
s.get<DataType<typeof U8>>('first');
.getOffset(name: string)
Returns the byte offset within the struct of the field with that name.
.getDeep(path: string)
Returns the field within multiple structs, where path is a .
separated string. Note: When using TypeScript, this value must be cast to the correct type, either using the generic or with the as
keyword:
const struct = Struct('firstStruct')
.field('aRawString', RawString('ABC'));
const struct2 = Struct('secondStruct')
.field('deeperStruct', struct);
struct2.getDeep<DataType<RawString>>('deeperStruct.aRawString');
.getDeepOffset(path: string)
Returns the byte offset within multiple structs, where path is a .
separated string.
.computeBufferSize()
Returns the size of the struct in bytes.
.toUint8Array()
Returns a Uint8Array
representation of the Struct.
Fields implement the IField
interface, and optionally the IValue
interface:
interface IField {
computeBufferSize(): number;
toUint8Array(): Uint8Array;
}
interface IValue<T> {
set(value: T): void;
get(): T;
}
U8(value: number) implements IField, IValue<number>
A single 8-bit unsigned value.
U16(value: number, endian = Endian.Little) implements IField, IValue<number>
A single 16-bit unsigned value, in either big or little endian byte order.
U32(value: number, endian = Endian.Little) implements IField, IValue<number>
A single 32-bit unsigned value, in either big or little endian byte order.
U64(value: bigint, endian = Endian.Little) implements IField, IValue<bigint>
A single 64-bit unsigned value, in either big or little endian byte order. Note: Values for 64-bit fields must be specified as bigint
.
I8(value: number) implements IField, IValue<number>
A single 8-bit signed value.
I16(value: number, endian = Endian.Little) implements IField, IValue<number>
A single 16-bit signed value, in either big or little endian byte order.
I32(value: number, endian = Endian.Little) implements IField, IValue<number>
A single 32-bit signed value, in either big or little endian byte order.
I64(value: bigint, endian = Endian.Little) implements IField, IValue<bigint>
A single 64-bit signed value, in either big or little endian byte order. Note: Values for 64-bit fields must be specified as bigint
.
RawString(string) implements IField, IValue<string>
A collection of 8-bit unsigned values, interpreted directly from the string provided.
NullTerminatedString(string) implements IField, IValue<string>
A collection of 8-bit unsigned values, interpreted directly from the string provided. This field appends a single 0x00
byte to the end of the data.
U8s(values: number[]) implements IField, IValue<number[]>
A collection of 8-bit unsigned values.
U16s(values: number[], endian = Endian.Little) implements IField, IValue<number[]>
A collection of 16-bit unsigned values, in either big or little endian byte order.
U32s(values: number[], endian = Endian.Little) implements IField, IValue<number[]>
A collection of 32-bit unsigned values, in either big or little endian byte order.
U64s(values: bigint[], endian = Endian.Little) implements IField, IValue<bigint[]>
A collection of 64-bit unsigned values, in either big or little endian byte order. Note: Values for 64-bit fields must be specified as bigint
.
I8s(values: number[]) implements IField, IValue<number[]>
A collection of 8-bit signed values.
I16s(values: number[], endian = Endian.Little) implements IField, IValue<number[]>
A collection of 16-bit signed values, in either big or little endian byte order.
I32s(values: number[], endian = Endian.Little) implements IField, IValue<number[]>
A collection of 32-bit signed values, in either big or little endian byte order.
I64s(values: bigint[], endian = Endian.Little) implements IField, IValue<bigint[]>
A collection of 64-bit signed values, in either big or little endian byte order. Note: Values for 64-bit fields must be specified as bigint
.
Pointer8(struct: Struct, path: string) implements IField
Pointer8
takes a Struct and a path, and represents an 8-bit pointer (offset) to the field specified by the path in the provided struct.
Pointer16(struct: Struct, path: string, endian = Endian.Little) implements IField
Pointer16
takes a Struct and a path, and represents an 16-bit pointer (offset) to the field specified by the path in the provided struct.
Pointer32(struct: Struct, path: string, endian = Endian.Little) implements IField
Pointer32
takes a Struct and a path, and represents an 32-bit pointer (offset) to the field specified by the path in the provided struct.
Pointer64(struct: Struct, path: string, endian = Endian.Little) implements IField
Pointer64
takes a Struct and a path, and represents an 64-bit pointer (offset) to the field specified by the path in the provided struct.
SizeOf8(target: ConstructDataType) implements IField
SizeOf8
takes a Struct or a Field, and represents the size of the Struct or the Field as an 8-bit unsigned integer.
SizeOf16(target: ConstructDataType, endian = Endian.Little) implements IField
SizeOf16
takes a Struct or a Field, and represents the size of the Struct or the Field as an 16-bit unsigned integer.
SizeOf32(target: ConstructDataType, endian = Endian.Little) implements IField
SizeOf32
takes a Struct or a Field, and represents the size of the Struct or the Field as an 32-bit unsigned integer.
SizeOf64(target: ConstructDataType, endian = Endian.Little) implements IField
SizeOf64
takes a Struct or a Field, and represents the size of the Struct or the Field as an 64-bit unsigned integer.
FAQs
`construct-js` is a library for creating byte level data structures.
The npm package construct-js receives a total of 7 weekly downloads. As such, construct-js popularity was classified as not popular.
We found that construct-js demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
Research
Security News
Socket researchers uncover the risks of a malicious Python package targeting Discord developers.
Security News
The UK is proposing a bold ban on ransomware payments by public entities to disrupt cybercrime, protect critical services, and lead global cybersecurity efforts.