Search documentation of snmpkit

# SnmpKit.SnmpLib.ASN1 (snmpkit v1.2.0)

Comprehensive ASN.1 BER (Basic Encoding Rules) encoding and decoding utilities.

Provides low-level ASN.1 operations that are used by the PDU module and can be used for other ASN.1 encoding/decoding needs. This module offers improved performance and better error handling compared to basic implementations.

## Features

- Complete BER encoding/decoding support
- Optimized length handling for large values
- Comprehensive error reporting
- Support for constructed and primitive types
- Memory-efficient implementations
- Validation and constraint checking
- RFC-compliant OID multibyte encoding (values ≥ 128)

## ASN.1 Types Supported

- INTEGER: Signed integers with arbitrary precision
- OCTET STRING: Binary data of any length
- NULL: Null value representation
- OBJECT IDENTIFIER: Hierarchical object identifiers with multibyte subidentifiers
- SEQUENCE: Constructed type for complex structures
- Custom Tags: Application-specific and context-specific tags

## Important: OID Encoding

OID subidentifiers use 7-bit encoding where values ≥ 128 require multibyte encoding:

- Values 0-127: Single byte
- Values 128+: Multibyte with continuation bits
- Example: 200 → `[0x81, 0x48]` (not single byte `0xC8`)

## Examples

    # Integer encoding/decoding
    iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_integer(42)
    iex> {:ok, {value, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_integer(encoded)
    iex> value
    42

    # OCTET STRING encoding/decoding
    iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_octet_string("Hello")
    iex> {:ok, {value, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_octet_string(encoded)
    iex> value
    "Hello"

    # OID encoding/decoding with multibyte values
    iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_oid([1, 3, 6, 1, 4, 1, 200])
    iex> {:ok, {oid, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_oid(encoded)
    iex> oid
    [1, 3, 6, 1, 4, 1, 200]

    # Length encoding for large values
    iex> encoded_length = SnmpKit.SnmpLib.ASN1.encode_length(1000)
    iex> {:ok, {length, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_length(encoded_length)
    iex> length
    1000

    # NULL encoding
    iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_null()
    iex> {:ok, {value, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_null(encoded)
    iex> value
    :null

# Summary

## Types

content()
length()
oid()
tag()
tlv()

## Functions

calculate_ber_length(arg1)

Calculates the total length of a BER-encoded structure.

decode_integer(arg1)

Decodes an ASN.1 INTEGER value.

decode_length(arg1)

Decodes an ASN.1 length field.

decode_null(arg1)

Decodes an ASN.1 NULL value.

decode_octet_string(arg1)

Decodes an ASN.1 OCTET STRING value.

decode_oid(arg1)

Decodes an ASN.1 OBJECT IDENTIFIER value.

decode_sequence(arg1)

Decodes an ASN.1 SEQUENCE value.

decode_tlv(arg1)

Decodes a generic TLV structure.

encode_custom_tlv(tag, content)

Encodes a custom TLV (Tag-Length-Value) structure.

encode_integer(value)

Encodes an ASN.1 INTEGER value using BER (Basic Encoding Rules).

encode_length(length)

Encodes an ASN.1 length field.

encode_null()

Encodes an ASN.1 NULL value.

encode_octet_string(value)

Encodes an ASN.1 OCTET STRING value.

encode_oid(oid_list)

Encodes an ASN.1 OBJECT IDENTIFIER (OID) value using BER encoding.

encode_sequence(content)

Encodes an ASN.1 SEQUENCE with the given content.

parse_tag(tag_byte)

Parses an ASN.1 tag byte.

validate_ber_structure(data)

Validates the structure of BER-encoded data.

# Types

# content()

    @type content() :: binary()

# length()

    @type length() :: non_neg_integer()

# oid()

    @type oid() :: [non_neg_integer()]

# tag()

    @type tag() :: non_neg_integer()

# tlv()

    @type tlv() :: {tag(), length(), content()}

# Functions

# calculate_ber_length(arg1)

    @spec calculate_ber_length(binary()) :: {:ok, non_neg_integer()} | {:error, atom()}

Calculates the total length of a BER-encoded structure.

## Parameters

- `data`: BER-encoded binary data

## Returns

- `{:ok, total_length}` on success
- `{:error, reason}` on failure

## Examples

    {:ok, 10} = SnmpKit.SnmpLib.ASN1.calculate_ber_length(ber_data)

# decode_integer(arg1)

    @spec decode_integer(binary()) :: {:ok, {integer(), binary()}} | {:error, atom()}

Decodes an ASN.1 INTEGER value.

## Parameters

- `data`: Binary data starting with an INTEGER TLV

## Returns

- `{:ok, {value, remaining_data}}` on success
- `{:error, reason}` on failure

## Examples

    {:ok, {42, remaining}} = SnmpKit.SnmpLib.ASN1.decode_integer(<<2, 1, 42, 99, 100>>)
    # Returns {42, <<99, 100>>}

# decode_length(arg1)

    @spec decode_length(binary()) :: {:ok, {length(), binary()}} | {:error, atom()}

Decodes an ASN.1 length field.

## Parameters

- `data`: Binary data starting with a length field

## Returns

- `{:ok, {length, remaining_data}}` on success
- `{:error, reason}` on failure

## Examples

    {:ok, {42, remaining}} = SnmpKit.SnmpLib.ASN1.decode_length(<<42, 1, 2, 3>>)
    {:ok, {300, remaining}} = SnmpKit.SnmpLib.ASN1.decode_length(<<0x82, 1, 44, 1, 2, 3>>)

# decode_null(arg1)

    @spec decode_null(binary()) :: {:ok, {:null, binary()}} | {:error, atom()}

Decodes an ASN.1 NULL value.

## Examples

    {:ok, {:null, remaining}} = SnmpKit.SnmpLib.ASN1.decode_null(<<5, 0, 1, 2, 3>>)
    # Returns {:null, <<1, 2, 3>>}

# decode_octet_string(arg1)

    @spec decode_octet_string(binary()) :: {:ok, {binary(), binary()}} | {:error, atom()}

Decodes an ASN.1 OCTET STRING value.

## Examples

    {:ok, {"Hello", remaining}} = SnmpKit.SnmpLib.ASN1.decode_octet_string(encoded_data)

# decode_oid(arg1)

    @spec decode_oid(binary()) :: {:ok, {oid(), binary()}} | {:error, atom()}

Decodes an ASN.1 OBJECT IDENTIFIER value.

## Examples

    {:ok, {[1, 3, 6, 1], remaining}} = SnmpKit.SnmpLib.ASN1.decode_oid(encoded_data)

# decode_sequence(arg1)

    @spec decode_sequence(binary()) :: {:ok, {binary(), binary()}} | {:error, atom()}

Decodes an ASN.1 SEQUENCE value.

Returns the content of the sequence without further parsing.

## Examples

    {:ok, {sequence_content, remaining}} = SnmpKit.SnmpLib.ASN1.decode_sequence(encoded_data)

# decode_tlv(arg1)

    @spec decode_tlv(binary()) :: {:ok, {tag(), binary(), binary()}} | {:error, atom()}

Decodes a generic TLV structure.

## Parameters

- `data`: Binary data starting with a TLV

## Returns

- `{:ok, {tag, content, remaining}}` on success
- `{:error, reason}` on failure

## Examples

    {:ok, {tag, content, remaining}} = SnmpKit.SnmpLib.ASN1.decode_tlv(binary_data)

# encode_custom_tlv(tag, content)

    @spec encode_custom_tlv(tag(), binary()) :: {:ok, binary()}

Encodes a custom TLV (Tag-Length-Value) structure.

## Parameters

- `tag`: ASN.1 tag value
- `content`: Binary content

## Examples

    {:ok, tlv} = SnmpKit.SnmpLib.ASN1.encode_custom_tlv(0xA0, <<"custom_content">>)

# encode_integer(value)

    @spec encode_integer(integer()) :: {:ok, binary()}

Encodes an ASN.1 INTEGER value using BER (Basic Encoding Rules).

Supports arbitrary precision integers using two's complement representation. The encoded result includes the ASN.1 tag (0x02), length, and content bytes.

## Parameters

- `value`: Integer value to encode (any size)

## Returns

- `{:ok, encoded_bytes}` on success
- `{:error, reason}` on failure

## Examples

    # Positive integers
    iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_integer(42)
    iex> encoded
    <<2, 1, 42>>

    # Negative integers (two's complement)
    iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_integer(-1)
    iex> encoded
    <<2, 1, 255>>

    # Zero
    iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_integer(0)
    iex> encoded
    <<2, 1, 0>>

    # Large integers
    iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_integer(32767)
    iex> byte_size(encoded) > 3
    true

# encode_length(length)

    @spec encode_length(length()) :: binary()

Encodes an ASN.1 length field.

Supports both short form (< 128) and long form encoding.

## Parameters

- `length`: Length value to encode

## Returns

- Binary representation of the length

## Examples

    <<42>> = SnmpKit.SnmpLib.ASN1.encode_length(42)
    <<0x81, 200>> = SnmpKit.SnmpLib.ASN1.encode_length(200)
    <<0x82, 1, 44>> = SnmpKit.SnmpLib.ASN1.encode_length(300)

# encode_null()

    @spec encode_null() :: {:ok, binary()}

Encodes an ASN.1 NULL value.

## Examples

    {:ok, <<5, 0>>} = SnmpKit.SnmpLib.ASN1.encode_null()

# encode_octet_string(value)

    @spec encode_octet_string(binary()) :: {:ok, binary()}

Encodes an ASN.1 OCTET STRING value.

## Parameters

- `value`: Binary data to encode

## Examples

    {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_octet_string("Hello")
    {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_octet_string(<<1, 2, 3, 4>>)

# encode_oid(oid_list)

    @spec encode_oid(oid()) :: {:ok, binary()} | {:error, atom()}

Encodes an ASN.1 OBJECT IDENTIFIER (OID) value using BER encoding.

OIDs are hierarchical identifiers where each component is encoded using 7-bit subidentifiers. Values ≥ 128 require multibyte encoding with continuation bits, which is correctly handled by this implementation.

## Parameters

- `oid_list`: List of non-negative integers representing the OID (minimum 2 components)

## Returns

- `{:ok, encoded_bytes}` on success
- `{:error, reason}` on failure (invalid OID format)

## Encoding Rules

- First two components are combined: `first * 40 + second`
- Remaining components use 7-bit encoding with continuation bits
- Values 0-127: single byte
- Values 128+: multibyte with high bit indicating continuation

## Examples

    # Standard SNMP OID (sysDescr.0)
    iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_oid([1, 3, 6, 1, 2, 1, 1, 1, 0])
    iex> {:ok, {decoded, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_oid(encoded)
    iex> decoded
    [1, 3, 6, 1, 2, 1, 1, 1, 0]

    # OID with multibyte values (≥ 128)
    iex> {:ok, encoded} = SnmpKit.SnmpLib.ASN1.encode_oid([1, 3, 6, 1, 4, 1, 200])
    iex> {:ok, {decoded, <<>>}} = SnmpKit.SnmpLib.ASN1.decode_oid(encoded)
    iex> decoded
    [1, 3, 6, 1, 4, 1, 200]

    # Invalid OIDs
    iex> SnmpKit.SnmpLib.ASN1.encode_oid([])
    {:error, :invalid_oid}

    iex> SnmpKit.SnmpLib.ASN1.encode_oid([1])
    {:ok, <<6, 1, 1>>}

# encode_sequence(content)

    @spec encode_sequence(binary()) :: {:ok, binary()}

Encodes an ASN.1 SEQUENCE with the given content.

## Parameters

- `content`: Pre-encoded content for the sequence

## Examples

    content = encode_integer_content(42) <> encode_octet_string_content("test")
    {:ok, sequence} = SnmpKit.SnmpLib.ASN1.encode_sequence(content)

# parse_tag(tag_byte)

    @spec parse_tag(tag()) :: map()

Parses an ASN.1 tag byte.

Returns information about the tag including class, constructed bit, and tag number.

## Parameters

- `tag_byte`: Single byte representing the tag

## Returns

- Map with tag information

## Examples

    info = SnmpKit.SnmpLib.ASN1.parse_tag(0x30)
    # Returns %{class: :universal, constructed: true, tag_number: 16}

# validate_ber_structure(data)

    @spec validate_ber_structure(binary()) :: :ok | {:error, atom()}

Validates the structure of BER-encoded data.

Performs basic validation without full decoding.

## Parameters

- `data`: BER-encoded binary data

## Returns

- `:ok` if structure is valid
- `{:error, reason}` if structure is invalid

## Examples

    :ok = SnmpKit.SnmpLib.ASN1.validate_ber_structure(valid_ber_data)
    {:error, :invalid_length} = SnmpKit.SnmpLib.ASN1.validate_ber_structure(malformed_data)