class

ASN1::BER

Inherits BinData / Reference / Object

A single ASN.1 Basic Encoding Rules (BER) TLV element: an identifier (tag), a length and a payload. Used to build and parse SNMP, LDAP, X.509 and similar protocols.

require "bindata/asn1"

ber = ASN1::BER.new
ber.set_integer(42)
io.write_bytes(ber)

ber = io.read_bytes(ASN1::BER)
ber.get_integer # => 42

Typed payload accessors live in data_types.cr (get_integer/set_integer, get_object_id/set_object_id, get_string, get_boolean, ...). A constructed element can be split into / built from sub-elements with #children / #children=.

Constants

AFTER_DESERIALIZE = [] of Nil
BEFORE_SERIALIZE = [] of Nil
DIRECT_STRING_TAGS = {UniversalTags::UTF8String, UniversalTags::CharacterString, UniversalTags::PrintableString, UniversalTags::IA5String, UniversalTags::OctetString, UniversalTags::NumericString, UniversalTags::VisibleString, UniversalTags::GeneralString, UniversalTags::GraphicString}

String types whose repertoire is ASCII-compatible, so the UTF-8 String.new decodes them directly. (T61String / VideotexString use the T.61 / videotex character sets, which are neither ASCII nor UTF-8, so they are not decoded here rather than mis-decoded.)

ENDIAN = ["big"]
GENERALIZEDTIME_FORMAT = /\A(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d+)?(Z|[+-]\d{4})\z/

YYYYMMDDHHMMSS(.fff)?(Z|±HHMM).

KLASS_NAME = [ASN1::BER]
MAX_OID_ARC_BYTES = 32

Largest sub-identifier we decode, in continuation octets. A base-128 arc of this many bytes holds ~224 bits — far beyond any real OID arc (a UUID-based 2.25 arc is 128-bit, ~19 bytes). Bounds the BigInt build against the super-linear CPU cost of a hostile continuation run.

PARTS = [{type: "basic", name: identifier, cls: ASN1::BER::Identifier, onlyif: nil, verify: nil, value: nil}, {type: "basic", name: length, cls: ASN1::BER::Length, onlyif: nil, verify: nil, value: nil}] of Nil
REMAINING = [] of Nil
UTCTIME_FORMAT = /\A(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})?(Z|[+-]\d{4})\z/

YYMMDDHHMM[SS](Z|±HHMM) — the seconds are optional.

Class methods

bit_fields
Source

Instance methods

__format__

A group or bit_field captures the endianness at its declaration point, so declaring endian after one would silently leave it system-endian. Fail loudly.

Source
bitstring_unused_bits

The number of unused (padding) bits in the final data byte (0..7).

Source
children

Parses the payload as a sequence of nested BER elements. The max_content_length and max_depth caps propagate to each child.

Only valid for a constructed element; on a primitive the payload is raw content, not a TLV list, so parsing it would yield garbage. Raises ASN1::Error in that case.

NOTE: this re-parses @payload on every call and returns a fresh array of freshly-decoded children — it is not memoized (the payload is mutable, so a cache would risk going stale). Bind the result to a local if you access the children repeatedly on the same element.

Source
children=(parts)

Encodes parts into the payload and marks this element constructed. The tag class/number are left untouched — set them yourself (e.g. to a universal Sequence/Set, or a constructed context tag) so #sequence? reflects the intended type; this accessor only guarantees the constructed flag.

Source
constructed
Source
constructed=(custom : Bool)
Source
eoc?

Whether this is an end-of-contents marker (00 00) — a primitive universal EndOfContent element with an empty payload, used to terminate indefinite content.

Source
extended
Source
extended=(parts : Array(ExtendedIdentifier))
Source
extended?
Source
get_bit_array

The BIT STRING's significant bits as a BitArray, numbered MSB-first from the first data byte (ASN.1 bit 0 is the high bit of the first byte).

Source
get_bitstring

Reads the BIT STRING data bytes (dropping the leading unused-bit count). The final byte's low #bitstring_unused_bits bits are padding. Any unused count 0..7 is accepted; use #get_bit_array for the exact significant bits.

Source
get_boolean

Reads the payload as a BOOLEAN.

Source
get_bytes

Returns the raw bytes

Source
get_hexstring(universal = true, tag = UniversalTags::OctetString)

Gets a hex representation of the bytes

Source
get_integer(check_tags = {UniversalTags::Integer, UniversalTags::Enumerated}, check_class = TagClass::Universal) : Int64

Reads the payload as a two's-complement signed INTEGER (or ENUMERATED).

With the default universal check_class the tag is validated against check_tags. With a non-universal check_class (e.g. an SNMP context-tagged Counter/Gauge) only the class is checked — the universal-tag check is skipped, since check_tags are universal tags and don't apply to a context tag.

Source
get_integer_bytes
Source
get_object_id

Returns the object ID in string format.

Sub-identifiers are decoded as big-endian base-128 numbers per X.690 §8.19 (every octet but the last has its high bit set), so arcs of any size are supported. BigInt is used because OID arcs are unbounded (e.g. UUID-based OIDs under 2.25, ITU-T X.667).

Source
get_string

Decodes the payload to a String. BMPString is transcoded from UTF-16BE and UniversalString from UTF-32BE (leniently — surrogate pairs are accepted, not strict UCS-2/UCS-4); the ASCII-repertoire types are read directly. An incomplete/malformed byte sequence raises ASN1::InvalidPayload; individual invalid code points may be substituted by the platform transcoder.

Source
get_time

Reads the payload as a UTCTime or GeneralizedTime, normalised to UTC.

A time zone is required (Z or a numeric ±HHMM offset); a bare local time is rejected. UTCTime's two-digit year uses the RFC 5280 pivot (>= 50 => 19xx, < 50 => 20xx).

Source
identifier

Components of a BER object

identifier=(identifier : Identifier)

Components of a BER object

inspect(io : IO) : Nil

Appends a String representation of this object which includes its class name, its object address and the values of all instance variables.

class Person
  def initialize(@name : String, @age : Int32)
  end
end

Person.new("John", 32).inspect # => #<Person:0x10fd31f20 @name="John", @age=32>
Source
length
length=(length : Length)
max_depth

Maximum nesting depth that #children and the indefinite-length reader will descend before raising MaxDepthExceeded. The default (100) guards both a recursive consumer #children walk and the eager recursion of #read over nested indefinite-length elements against stack overflow (a few KB of 30 80 … / 24 80 … encodes thousands of levels); 0 disables the limit (and with it the indefinite-read recursion bound). Propagated to each child alongside max_content_length.

Source
max_depth=(max_depth : Int32)

Maximum nesting depth that #children and the indefinite-length reader will descend before raising MaxDepthExceeded. The default (100) guards both a recursive consumer #children walk and the eager recursion of #read over nested indefinite-length elements against stack overflow (a few KB of 30 80 … / 24 80 … encodes thousands of levels); 0 disables the limit (and with it the indefinite-read recursion bound). Propagated to each child alongside max_content_length.

Source
null?

Whether this is a well-formed universal NULL element: primitive, tag 05, empty payload (X.690 §8.8).

Source
payload
Source
payload=(payload : Bytes)
Source
read(io : IO) : IO

Reads the fields of this instance from io, in declaration order, and returns io. Raises BinData::ParseError (or BinData::VerificationException) on malformed input.

Source
sequence?

Whether this is a constructed universal Sequence or Set, i.e. an element whose payload is itself a list of BER elements (see #children).

Source
set_bit_array(bits : BitArray)

Sets a BIT STRING from a BitArray (numbered MSB-first, as ASN.1 expects).

Source
set_bitstring(bytes : Bytes, unused_bits : Int = 0)

Sets a BIT STRING from bytes, with unused_bits (0..7) of padding in the final byte.

Source
set_boolean(value)

Sets a BOOLEAN payload.

Source
set_bytes(data, tag = UniversalTags::OctetString, tag_class = TagClass::Universal)

Sets the raw payload bytes and the given tag.

Source
set_hexstring(string, tag = UniversalTags::OctetString, tag_class = TagClass::Universal)

Sets bytes from a hexstring

Source
set_integer(value, tag = UniversalTags::Integer, tag_class = TagClass::Universal)

Encodes value as a minimal two's-complement INTEGER payload. ameba:disable Metrics/CyclomaticComplexity

Source
set_null

Sets an empty, primitive NULL payload (encodes as 05 00).

Source
set_object_id(oid)

Sets a string representing an object ID.

Each arc is encoded as a big-endian base-128 number per X.690 §8.19. Arcs are parsed as BigInt, so arbitrarily large values are supported.

Source
set_string(string, tag = UniversalTags::UTF8String, tag_class = TagClass::Universal)

Sets a string. BMPString is encoded to UTF-16BE and UniversalString to UTF-32BE; every other type stores the string's UTF-8 bytes. tag may be a UniversalTags or its integer value.

Source
set_time(time : Time, tag = UniversalTags::GeneralizedTime)

Encodes time (converted to UTC) as a GeneralizedTime (default) or UTCTime, in the canonical …Z form. UTCTime can only represent years 1950..2049. Sub-second precision is dropped (DER forbids fractional seconds), so a Time with a fractional part does not round-trip exactly through #get_time.

Source
size

The current payload length in bytes. Reads from the payload itself (not the decoded Length, which is only refreshed on write), so it is correct for in-memory-built objects and for indefinite-length elements too.

Source
strict=(strict : Bool)

When set, reject non-canonical (DER) encodings: non-minimal / indefinite length, non-{00,FF} / multi-byte BOOLEAN, empty / non-minimal INTEGER, non-minimal OID, embedded-NUL strings, and an out-of-order universal SET OF (tag 17). Default false keeps the BER-permissive behaviour. Set it before reading (the length checks fire during read), and it propagates to #children.

Not (yet) covered: an implicitly context-tagged SET OF (indistinguishable from a SEQUENCE without a schema), GeneralizedTime/UTCTime canonical form, BIT STRING padding bits, and the primitive-vs-constructed rule for strings.

Source
strict?

When set, reject non-canonical (DER) encodings: non-minimal / indefinite length, non-{00,FF} / multi-byte BOOLEAN, empty / non-minimal INTEGER, non-minimal OID, embedded-NUL strings, and an out-of-order universal SET OF (tag 17). Default false keeps the BER-permissive behaviour. Set it before reading (the length checks fire during read), and it propagates to #children.

Not (yet) covered: an implicitly context-tagged SET OF (indistinguishable from a SEQUENCE without a schema), GeneralizedTime/UTCTime canonical form, BIT STRING padding bits, and the primitive-vs-constructed rule for strings.

Source
tag

The universal tag as a UniversalTags enum. Raises unless this is a universal-class element.

Source
tag_class
Source
tag_class=(tag : TagClass)
Source
tag_number
Source
tag_number=(tag_type : Int | UniversalTags)
Source
write(io : IO)

Writes the fields of this instance to io in declaration order. Raises BinData::WriteError (or BinData::VerificationException) on failure.

Source

Nested types