Parse ASN.1 text notation into SchemaNode JSON definitions that can be used for PER unaligned encoding and decoding.
The schema parser converts ASN.1 module text (.asn files) into a Record<string, SchemaNode> map, where each top-level type assignment becomes an entry. The pipeline has two steps:
- Parse the ASN.1 text into an AST using
parseAsn1Module()(src/parser/AsnParser.ts) - Convert the AST into
SchemaNodeobjects usingconvertModuleToSchemaNodes()(src/parser/toSchemaNode.ts)
The resulting SchemaNode objects can be used directly with SchemaCodec (src/schema/SchemaCodec.ts) or SchemaBuilder (src/schema/SchemaBuilder.ts) for encoding and decoding.
import { parseAsn1Module, convertModuleToSchemaNodes } from 'asn1-per-ts';
const asn1Text = `
MyModule DEFINITIONS AUTOMATIC TAGS ::= BEGIN
PersonRecord ::= SEQUENCE {
name IA5String (SIZE (1..50)),
age INTEGER (0..150),
active BOOLEAN
}
END
`;
// Step 1: Parse ASN.1 text into AST
const module = parseAsn1Module(asn1Text);
// module.name === 'MyModule'
// module.assignments is an array of AsnTypeAssignment objects
// Step 2: Convert AST to SchemaNode map
const schemas = convertModuleToSchemaNodes(module);
// schemas.PersonRecord is a SchemaNode of type 'SEQUENCE'
console.log(JSON.stringify(schemas, null, 2));Output:
{
"PersonRecord": {
"type": "SEQUENCE",
"fields": [
{ "name": "name", "schema": { "type": "IA5String", "minSize": 1, "maxSize": 50 } },
{ "name": "age", "schema": { "type": "INTEGER", "min": 0, "max": 150 } },
{ "name": "active", "schema": { "type": "BOOLEAN" } }
]
}
}import {
parseAsn1Module,
convertModuleToSchemaNodes,
SchemaCodec,
} from 'asn1-per-ts';
const asn1Text = `
Example DEFINITIONS AUTOMATIC TAGS ::= BEGIN
Status ::= ENUMERATED { pending, approved, rejected }
Request ::= SEQUENCE {
id INTEGER (0..65535),
status Status
}
END
`;
const schemas = convertModuleToSchemaNodes(parseAsn1Module(asn1Text));
const codec = new SchemaCodec(schemas.Request);
const hex = codec.encodeToHex({ id: 42, status: 'approved' });
const decoded = codec.decodeFromHex(hex);
// decoded === { id: 42, status: 'approved' }When a module contains type references that create cycles (e.g., a tree structure), the converter emits $ref nodes. Use SchemaBuilder.buildAll() instead of SchemaCodec to resolve them:
import {
parseAsn1Module,
convertModuleToSchemaNodes,
SchemaBuilder,
BitBuffer,
} from 'asn1-per-ts';
const asn1Text = `
TreeModule DEFINITIONS AUTOMATIC TAGS ::= BEGIN
Tree ::= SEQUENCE {
value INTEGER (0..255),
children SEQUENCE OF Tree
}
END
`;
const schemas = convertModuleToSchemaNodes(parseAsn1Module(asn1Text));
// schemas.Tree contains $ref nodes for the recursive reference
// buildAll() resolves $ref lazily
const codecs = SchemaBuilder.buildAll(schemas);
const buffer = BitBuffer.alloc();
codecs.Tree.encode(buffer, {
value: 1,
children: [
{ value: 2, children: [] },
{ value: 3, children: [{ value: 4, children: [] }] },
],
});The cli/generate-schema.ts tool converts an .asn file to a .schema.json file from the command line:
# Print schema JSON to stdout
npx tsx cli/generate-schema.ts input.asn
# Write schema JSON to a file
npx tsx cli/generate-schema.ts input.asn output.schema.jsonThe tool reads the ASN.1 file, parses it, converts all type assignments, and outputs a single JSON object mapping type names to SchemaNode definitions. See cli/generate-schema.ts for the implementation.
The parser (src/parser/grammar.ts, src/parser/types.ts) supports the following ASN.1 types:
| ASN.1 Type | SchemaNode type |
Notes |
|---|---|---|
BOOLEAN |
BOOLEAN |
|
NULL |
NULL |
|
INTEGER |
INTEGER |
With optional value constraint (min..max) |
ENUMERATED |
ENUMERATED |
Root values and optional extension values |
BIT STRING |
BIT STRING |
With optional SIZE constraint |
OCTET STRING |
OCTET STRING |
With optional SIZE constraint |
IA5String |
IA5String |
With optional SIZE constraint |
VisibleString |
VisibleString |
With optional SIZE constraint |
UTF8String |
UTF8String |
With optional SIZE constraint |
OBJECT IDENTIFIER |
OBJECT IDENTIFIER |
OID dot-notation strings |
SEQUENCE |
SEQUENCE |
Fields with OPTIONAL / DEFAULT support |
SEQUENCE OF |
SEQUENCE OF |
With optional SIZE constraint |
CHOICE |
CHOICE |
Tagged union of alternatives |
SmallInt ::= INTEGER (0..255) -- fixed range
FlexInt ::= INTEGER (0..100, ...) -- extensible rangeProduces SchemaNode:
{ "type": "INTEGER", "min": 0, "max": 255 }
{ "type": "INTEGER", "min": 0, "max": 100, "extensible": true }min/maxdefine the constrained range. PER encoding uses the minimum number of bits for the range.extensible: trueadds a 1-bit extension marker prefix. Values inside the root range use compact encoding; values outside use unconstrained encoding.
Name ::= IA5String (SIZE (1..50))
FixedId ::= OCTET STRING (SIZE (4))
FlexBuf ::= BIT STRING (SIZE (8..256, ...))Produces SchemaNode:
{ "type": "IA5String", "minSize": 1, "maxSize": 50 }
{ "type": "OCTET STRING", "fixedSize": 4 }
{ "type": "BIT STRING", "minSize": 8, "maxSize": 256, "extensible": true }- When
min === max, the converter usesfixedSize(no length determinant encoded). - Otherwise
minSize/maxSizeare used. extensible: trueadds a 1-bit extension marker for the size constraint.
Extension markers (...) indicate that a type may be extended in future versions. They affect encoding by adding a 1-bit prefix.
-- SEQUENCE with extension marker
MessageV1 ::= SEQUENCE {
id INTEGER (0..255),
...
}
-- SEQUENCE with extension additions
MessageV2 ::= SEQUENCE {
id INTEGER (0..255),
...,
email IA5String
}
-- ENUMERATED with extensions
Color ::= ENUMERATED { red, green, blue, ..., yellow }
-- CHOICE with extensions
Shape ::= CHOICE { circle BOOLEAN, ..., polygon INTEGER }Produces SchemaNode:
{
"type": "SEQUENCE",
"fields": [{ "name": "id", "schema": { "type": "INTEGER", "min": 0, "max": 255 } }],
"extensionFields": []
}extensionFields: [](present but empty) marks the SEQUENCE as extensible with no additions.extensionFields: [...](non-empty) marks it as extensible with extension additions.- Omitting
extensionFieldsentirely means the type is not extensible. - The same pattern applies to
extensionValues(ENUMERATED) andextensionAlternatives(CHOICE).
Record ::= SEQUENCE {
required INTEGER (0..255),
nickname IA5String OPTIONAL,
version INTEGER (0..10) DEFAULT 1
}Produces SchemaNode:
{
"type": "SEQUENCE",
"fields": [
{ "name": "required", "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 }
]
}optional: truefields are preceded by a 1-bit presence flag in the encoding.defaultValuefields also use a presence flag; when absent, the default is used on decode.
You can construct SchemaNode objects directly in TypeScript without using the ASN.1 parser. This is useful for simple schemas or when you want full control:
import { SchemaCodec, type SchemaNode } from 'asn1-per-ts';
const schema: SchemaNode = {
type: 'SEQUENCE',
fields: [
{ name: 'id', schema: { type: 'INTEGER', min: 0, max: 255 } },
{ name: 'active', schema: { type: 'BOOLEAN' } },
{
name: 'status',
schema: { type: 'ENUMERATED', values: ['pending', 'approved', 'rejected'] },
},
],
};
const codec = new SchemaCodec(schema);See src/schema/SchemaBuilder.ts for the full SchemaNode type definition.
| File | Description |
|---|---|
src/parser/AsnParser.ts |
parseAsn1Module() - parses ASN.1 text into AST |
src/parser/toSchemaNode.ts |
convertModuleToSchemaNodes() - converts AST to SchemaNode map |
src/parser/grammar.ts |
PEG grammar for ASN.1 notation subset |
src/parser/types.ts |
TypeScript types for ASN.1 AST (AsnModule, AsnType, etc.) |
src/schema/SchemaBuilder.ts |
SchemaBuilder.build() / buildAll() - builds codecs from SchemaNode |
src/schema/SchemaCodec.ts |
SchemaCodec - high-level encode/decode with hex helpers |
cli/generate-schema.ts |
CLI tool to convert .asn files to .schema.json |