Decode PER unaligned binary data back into JavaScript/TypeScript objects.
There are two levels of decoding API:
- High-level:
SchemaCodec(src/schema/SchemaCodec.ts) - decode aUint8Arrayor hex string into a plain object using aSchemaNodedefinition. - Metadata decoding:
decodeWithMetadatareturns aDecodedNodetree with bit positions, raw bytes, and codec references for every field. UsestripMetadatato convert back to a plain object. - Low-level: Individual codec classes (
src/codecs/) +BitBuffer(src/BitBuffer.ts) - decode directly from a bit-level buffer.
import { SchemaCodec } from 'asn1-per-ts';
const codec = new SchemaCodec({
type: 'SEQUENCE',
fields: [
{ name: 'id', schema: { type: 'INTEGER', min: 0, max: 255 } },
{ name: 'active', schema: { type: 'BOOLEAN' } },
],
});
const decoded = codec.decodeFromHex('2a80');
// decoded === { id: 42, active: true }SchemaCodec.decodeFromHex() converts the hex string to bytes internally and runs the decoder. See src/schema/SchemaCodec.ts:37.
const bytes = new Uint8Array([0x2a, 0x80]);
const decoded = codec.decode(bytes);
// decoded === { id: 42, active: true }SchemaCodec.decode() wraps the Uint8Array in a BitBuffer and calls the underlying codec's decode() method. See src/schema/SchemaCodec.ts:31.
const codec = new SchemaCodec({
type: 'SEQUENCE',
fields: [
{ name: 'id', schema: { type: 'INTEGER', min: 0, max: 255 } },
{ name: 'nickname', schema: { type: 'IA5String' }, optional: true },
{ name: 'version', schema: { type: 'INTEGER', min: 0, max: 10 }, defaultValue: 1 },
],
});
// Encode with all fields present
const hex1 = codec.encodeToHex({ id: 5, nickname: 'hello', version: 3 });
const result1 = codec.decodeFromHex(hex1);
// result1 === { id: 5, nickname: 'hello', version: 3 }
// Encode with optional/default fields absent
const hex2 = codec.encodeToHex({ id: 5 });
const result2 = codec.decodeFromHex(hex2);
// result2 === { id: 5, version: 1 }
// nickname is absent (undefined), version falls back to defaultValueconst codec = new SchemaCodec({
type: 'ENUMERATED',
values: ['pending', 'approved', 'rejected'],
});
const decoded = codec.decodeFromHex('40');
// decoded === 'approved' (index 1 in the enumeration)const codec = new SchemaCodec({
type: 'CHOICE',
alternatives: [
{ name: 'flag', schema: { type: 'BOOLEAN' } },
{ name: 'count', schema: { type: 'INTEGER', min: 0, max: 255 } },
],
});
const decoded = codec.decodeFromHex('95');
// decoded === { count: 42 } — the CHOICE index selects 'count'CHOICE values decode as an object with a single key matching the chosen alternative name.
Every codec has a decodeWithMetadata method that returns a DecodedNode tree. Each node wraps the decoded value with metadata about its encoding: bit offset, bit length, raw bytes, the codec that decoded it, and schema flags (optional, present, default, extension).
import { SchemaCodec, stripMetadata } from 'asn1-per-ts';
import type { DecodedNode } from 'asn1-per-ts';
const codec = new SchemaCodec({
type: 'SEQUENCE',
fields: [
{ name: 'id', schema: { type: 'INTEGER', min: 0, max: 255 } },
{ name: 'active', schema: { type: 'BOOLEAN' } },
{ name: 'name', schema: { type: 'IA5String', minSize: 1, maxSize: 50 }, optional: true },
],
});
const hex = codec.encodeToHex({ id: 42, active: true, name: 'hello' });
const node = codec.decodeFromHexWithMetadata(hex);SchemaCodec.decodeFromHexWithMetadata() and SchemaCodec.decodeWithMetadata() return a DecodedNode instead of a plain object.
// The root node wraps a SEQUENCE — value is Record<string, DecodedNode>
const fields = node.value as Record<string, DecodedNode>;
// Each field has a value and metadata
console.log(fields.id.value); // 42
console.log(fields.id.meta.bitOffset); // 0
console.log(fields.id.meta.bitLength); // 8
console.log(fields.id.meta.rawBytes); // Uint8Array — the raw PER encoding of this field
console.log(fields.active.value); // true
console.log(fields.active.meta.bitOffset); // 8 (starts right after id)
console.log(fields.active.meta.bitLength); // 1
// Optional field metadata
console.log(fields.name.meta.optional); // true
console.log(fields.name.meta.present); // true (was encoded)| Property | Type | Description |
|---|---|---|
bitOffset |
number |
Start bit position in the source buffer |
bitLength |
number |
Number of bits consumed |
rawBytes |
Uint8Array |
Raw PER encoding of this value, left-aligned |
codec |
Codec<unknown> |
The codec instance that decoded this node |
optional |
boolean? |
Whether the schema declared this field OPTIONAL |
present |
boolean? |
Whether this field was actually present in encoding |
isDefault |
boolean? |
Whether the DEFAULT value was used |
isExtension |
boolean? |
Whether this field is an extension addition |
- Primitive codecs (Boolean, Integer, Enumerated, etc.):
valueis the raw JS value (boolean,number,string, etc.) - SequenceCodec:
valueisRecord<string, DecodedNode>— each field is a wrapped node - SequenceOfCodec:
valueisDecodedNode[]— each array item is a wrapped node - ChoiceCodec:
valueis{ key: string; value: DecodedNode }— the selected alternative is a wrapped node
For absent OPTIONAL fields (not present, no default), the tree includes a node with value: undefined, present: false, bitLength: 0. For DEFAULT fields using the default value, the node has value: <defaultValue>, isDefault: true, present: false.
const codec = new SchemaCodec({
type: 'SEQUENCE',
fields: [
{ name: 'id', schema: { type: 'INTEGER', min: 0, max: 255 } },
{ name: 'name', schema: { type: 'IA5String', minSize: 1, maxSize: 50 }, optional: true },
{ name: 'version', schema: { type: 'INTEGER', min: 0, max: 10 }, defaultValue: 1 },
],
});
const hex = codec.encodeToHex({ id: 5 });
const node = codec.decodeFromHexWithMetadata(hex);
const fields = node.value as Record<string, DecodedNode>;
// Absent optional field
console.log(fields.name.meta.present); // false
console.log(fields.name.meta.optional); // true
console.log(fields.name.value); // undefined
// Default field (not explicitly encoded)
console.log(fields.version.meta.present); // false
console.log(fields.version.meta.isDefault); // true
console.log(fields.version.value); // 1stripMetadata walks the DecodedNode tree and reconstructs a plain object identical to decode():
import { stripMetadata } from 'asn1-per-ts';
const node = codec.decodeFromHexWithMetadata(hex);
const plain = stripMetadata(node);
// plain is identical to codec.decodeFromHex(hex)stripMetadata dispatches on the codec stored in each node's metadata using instanceof checks. It throws if it encounters an unhandled codec type.
Every codec class also supports decodeWithMetadata:
import { BitBuffer, IntegerCodec } from 'asn1-per-ts';
const codec = new IntegerCodec({ min: 0, max: 255 });
const buffer = BitBuffer.from(new Uint8Array([0x2a]));
const node = codec.decodeWithMetadata(buffer);
console.log(node.value); // 42
console.log(node.meta.bitOffset); // 0
console.log(node.meta.bitLength); // 8
console.log(node.meta.rawBytes); // Uint8Array([0x2a])
console.log(node.meta.codec); // the IntegerCodec instanceThe extractBits method extracts a range of bits into a new byte-aligned Uint8Array. This is used internally by decodeWithMetadata to populate rawBytes.
const buffer = BitBuffer.from(new Uint8Array([0xab, 0xcd]));
const raw = buffer.extractBits(4, 8);
// raw === Uint8Array([0xbc]) — bits 4..11 extracted and left-alignedCombine the parser with decoding in one pipeline:
import {
parseAsn1Module,
convertModuleToSchemaNodes,
SchemaCodec,
} from 'asn1-per-ts';
const asn1Text = `
Example DEFINITIONS AUTOMATIC TAGS ::= BEGIN
Message ::= SEQUENCE {
id INTEGER (0..65535),
text IA5String (SIZE (1..100))
}
END
`;
const schemas = convertModuleToSchemaNodes(parseAsn1Module(asn1Text));
const codec = new SchemaCodec(schemas.Message);
const decoded = codec.decodeFromHex('002a05776f726c64');
// decoded === { id: 42, text: 'world' }For fine-grained control, use BitBuffer (src/BitBuffer.ts) with individual codec classes from src/codecs/.
import { BitBuffer } from 'asn1-per-ts';
// Create a read buffer from bytes
const buffer = BitBuffer.from(new Uint8Array([0x2a, 0x80]));
console.log(buffer.bitLength); // 16
console.log(buffer.remaining); // 16
// Read individual bits
const bit = buffer.readBit(); // 0 or 1
buffer.reset(); // seek back to start
// Read multiple bits as unsigned integer
const value = buffer.readBits(8); // reads 8 bits → 0x2a = 42const hex = '2a80';
const bytes = new Uint8Array(
hex.match(/.{1,2}/g)!.map(b => parseInt(b, 16)),
);
const buffer = BitBuffer.from(bytes);const buffer = BitBuffer.fromBinaryString('00101010');
// buffer contains byte 0x2aimport { BitBuffer, IntegerCodec } from 'asn1-per-ts';
const codec = new IntegerCodec({ min: 0, max: 255 });
const buffer = BitBuffer.from(new Uint8Array([0x2a]));
const value = codec.decode(buffer);
// value === 42Options for IntegerCodec (see src/codecs/IntegerCodec.ts):
| Option | Type | Description |
|---|---|---|
min |
number |
Lower bound of the value range |
max |
number |
Upper bound of the value range |
extensible |
boolean |
If true, a 1-bit extension marker prefixes the encoding |
import { BitBuffer, BooleanCodec } from 'asn1-per-ts';
const codec = new BooleanCodec();
const buffer = BitBuffer.from(new Uint8Array([0x80])); // MSB = 1
const value = codec.decode(buffer);
// value === trueimport { BitBuffer, EnumeratedCodec } from 'asn1-per-ts';
const codec = new EnumeratedCodec({
values: ['red', 'green', 'blue'],
});
const buffer = BitBuffer.from(new Uint8Array([0x80])); // index 1 → 'green'
const value = codec.decode(buffer);
// value === 'green'Options for EnumeratedCodec (see src/codecs/EnumeratedCodec.ts):
| Option | Type | Description |
|---|---|---|
values |
string[] |
Root enumeration values |
extensionValues |
string[] |
Extension enumeration values (makes type extensible) |
import { BitBuffer, BitStringCodec, OctetStringCodec } from 'asn1-per-ts';
// Fixed-size BIT STRING (no length prefix in encoding)
const bitCodec = new BitStringCodec({ fixedSize: 8 });
const buf1 = BitBuffer.from(new Uint8Array([0xff]));
const bits = bitCodec.decode(buf1);
// bits === { bits: Uint8Array([0xff]), length: 8 }
// Constrained OCTET STRING
const octetCodec = new OctetStringCodec({ minSize: 2, maxSize: 10 });
const buf2 = BitBuffer.from(new Uint8Array([/* encoded data */]));
const octets = octetCodec.decode(buf2);
// octets is a Uint8ArrayOptions for BitStringCodec (see src/codecs/BitStringCodec.ts):
| Option | Type | Description |
|---|---|---|
fixedSize |
number |
Exact bit length (no length determinant) |
minSize |
number |
Minimum bit length |
maxSize |
number |
Maximum bit length |
extensible |
boolean |
1-bit extension marker prefix |
Options for OctetStringCodec (see src/codecs/OctetStringCodec.ts):
| Option | Type | Description |
|---|---|---|
fixedSize |
number |
Exact byte length (no length determinant) |
minSize |
number |
Minimum byte length |
maxSize |
number |
Maximum byte length |
extensible |
boolean |
1-bit extension marker prefix |
import { BitBuffer, UTF8StringCodec } from 'asn1-per-ts';
const codec = new UTF8StringCodec({
type: 'IA5String',
minSize: 1,
maxSize: 50,
});
const buffer = BitBuffer.from(someBytes);
const text = codec.decode(buffer);
// text is a stringOptions for UTF8StringCodec (see src/codecs/UTF8StringCodec.ts):
| Option | Type | Description |
|---|---|---|
type |
'IA5String' | 'VisibleString' | 'UTF8String' |
String type (determines character set) |
alphabet |
string |
Custom alphabet constraint (reduces bits per character) |
fixedSize |
number |
Exact character length |
minSize |
number |
Minimum character length |
maxSize |
number |
Maximum character length |
extensible |
boolean |
1-bit extension marker prefix |
import { BitBuffer, SequenceCodec, IntegerCodec, BooleanCodec } from 'asn1-per-ts';
const codec = new SequenceCodec({
fields: [
{ name: 'id', codec: new IntegerCodec({ min: 0, max: 255 }) },
{ name: 'active', codec: new BooleanCodec() },
],
});
const buffer = BitBuffer.from(new Uint8Array([0x2a, 0x80]));
const value = codec.decode(buffer);
// value === { id: 42, active: true }Options for SequenceCodec (see src/codecs/SequenceCodec.ts):
| Option | Type | Description |
|---|---|---|
fields |
SequenceField[] |
Array of { name, codec, optional?, defaultValue? } |
extensionFields |
SequenceField[] |
Extension fields (makes type extensible) |
import { BitBuffer, ChoiceCodec, BooleanCodec, IntegerCodec } from 'asn1-per-ts';
const codec = new ChoiceCodec({
alternatives: [
{ name: 'flag', codec: new BooleanCodec() },
{ name: 'count', codec: new IntegerCodec({ min: 0, max: 255 }) },
],
});
const buffer = BitBuffer.from(new Uint8Array([0x95]));
const value = codec.decode(buffer);
// value === { count: 42 }Options for ChoiceCodec (see src/codecs/ChoiceCodec.ts):
| Option | Type | Description |
|---|---|---|
alternatives |
ChoiceAlternative[] |
Array of { name, codec } |
extensionAlternatives |
ChoiceAlternative[] |
Extension alternatives (makes type extensible) |
import { BitBuffer, SequenceOfCodec, IntegerCodec } from 'asn1-per-ts';
const codec = new SequenceOfCodec({
itemCodec: new IntegerCodec({ min: 0, max: 255 }),
minSize: 0,
maxSize: 10,
});
const buffer = BitBuffer.from(someBytes);
const items = codec.decode(buffer);
// items is an array of numbersimport { BitBuffer, ObjectIdentifierCodec } from 'asn1-per-ts';
const codec = new ObjectIdentifierCodec();
const buffer = BitBuffer.from(someBytes);
const oid = codec.decode(buffer);
// oid is a string like '1.2.840.113549.1.1'| File | Description |
|---|---|
src/BitBuffer.ts |
BitBuffer - bit-level read/write buffer |
src/schema/SchemaCodec.ts |
SchemaCodec - high-level decode from hex/bytes |
src/schema/SchemaBuilder.ts |
SchemaBuilder.build() / buildAll() - builds codecs from SchemaNode |
src/codecs/Codec.ts |
Codec<T> interface with decode(buffer) and decodeWithMetadata(buffer) methods |
src/codecs/DecodedNode.ts |
DecodedNode, FieldMeta interfaces and primitiveDecodeWithMetadata helper |
src/codecs/stripMetadata.ts |
stripMetadata function — converts DecodedNode tree back to plain object |
src/codecs/BooleanCodec.ts |
BooleanCodec |
src/codecs/IntegerCodec.ts |
IntegerCodec with constraint options |
src/codecs/EnumeratedCodec.ts |
EnumeratedCodec with extension support |
src/codecs/BitStringCodec.ts |
BitStringCodec with size constraints |
src/codecs/OctetStringCodec.ts |
OctetStringCodec with size constraints |
src/codecs/UTF8StringCodec.ts |
UTF8StringCodec for IA5String, VisibleString, UTF8String |
src/codecs/NullCodec.ts |
NullCodec |
src/codecs/ObjectIdentifierCodec.ts |
ObjectIdentifierCodec for OID dot-notation |
src/codecs/ChoiceCodec.ts |
ChoiceCodec with extension support |
src/codecs/SequenceCodec.ts |
SequenceCodec with OPTIONAL/DEFAULT fields |
src/codecs/SequenceOfCodec.ts |
SequenceOfCodec with size constraints |