ASN.1 Python Compiler Documentation

General Considerations

When a codec is generated for an ASN.1 schema, it is usually accompanied by a sample file that contains an API usage example. The sample will contain mock-up messages in JSON format that are used to demonstrate the capabilities of the codec and the way its API is meant to be used: to encode, decode, or validate.

Using the Public API

Types in the codec file follow the same structure as the ASN.1 schema. All types provided by the codec are static and do not need to be instantiated.

Depending on the option selected in the code generation step (either Python native types or class-based), the public API methods will accept as input or return either a Python native type value or an instance of a class-based binding, as shown below.

Each type in the public API contains three methods that correspond to the functionality they provide:

Method Description Example
Encode Encode accepts two arguments:
  • The first argument is the desired encoding rule as a string.
  • The second argument is the value to be encoded as a Python native type value or an instance of a class-based binding.
It returns a "bytearray" that contains the encoding.
>>> encoding = World-Schema.Rocket.encode("DER", value)
Decode Decode accepts two arguments:
  • The first argument must be the encoding rule as a string.
  • The second argument can be either a "bytearray", an "io.BufferReader", or an "io.BytesIO" that holds the encoding.
It returns the decoded value as a Python native type value or an instance of a class-based binding.
>>> value = WorldSchema.Rocket.decode("DER", stream)
Validate Validate accepts a Python native type value or an instance of a class-based binding. It returns a list of constraint violations or an empty list when no violations are encountered.
Each entry in the list holds a reference to the component that violates a constraint. The reference can be used to access and change the value using a tool that implements the JSON pointer specification. It also holds a copy of the value that violates the constraint and a sub-list that has more details about the violation.
>>> violations = WorldSchema.Rocket.validate(value)

Exceptions

NotImplementedError

This exception is raised when attempting to encode/decode a type or use an encoding rule that is currently unimplemented.

TypeError

This exception is raised when the type of the input value does not correspond to the type the codec is expecting, e.g., passing a 'dict' value instead of a 'str'.

ValueError

This exception is raised when an error with the input value, such as improper format, type, etc., is encountered while encoding a value.

Known Limitations

Unsupported Types:
  • TIME
  • TIME-OF-DAY
  • DATE-TIME
  • DURATION
Unsupported Constraints:

Constraint validation is currently unsupported for the following constraints:

  • COMPONENT RELATION constraint (Open Types)
  • PROPERTY SETTING constraint (TIME, TIME-OF-DAY, DATE-TIME, DURATION)
  • USER DEFINED constraint

Codec data bindings

There are two alternatives for representing input/output values that are acceptable:

  • The codec can use native type bindings as binding for your schema types, which are referenced by name as ModuleName.TypeName. You can easily instantiate native bindings from a JSON string by calling the json.loads() function, which is imported from the Python built-in JSON package.
  • As an alternative, the codec can also work with instances of classes used to represent the schema types, called Class-Based Bindings. The advantage of using the latter is that they allow for easy creation and modification of messages. You can find more details in the Class-Based Bindings section.

Native Type Bindings

Handling of Unknown Encodings

Open Types

There are situations where the full content of a message cannot be decoded. The resulting message will contain ``undecoded`` parts. These undecoded parts are represented by the ``_unknown_encoding`` field, which holds the hexadecimal string that represents the undecoded part of a message. The ``_unknown_encoding`` field can also be used to embed a previously encoded message into a value that is about to be encoded.

SEQUENCE/SET

Values of SEQUENCE and SET types can contain unknown extensions. The ``_unknown_extensions`` component identifier is used to contain the hexadecimal string representation of the undecoded value.

CHOICE

Values of the CHOICE type can also have unknown extensions. These can be accessed by using the ``_unknown_extension`` component identifier.

Input Value Types

BIT STRING

Input values of the ASN.1 BIT STRING type are represented in two ways:

  • As a hexadecimal string (native 'str' type), if it's a fixed-size value (e.g., BIT STRING (SIZE(10))).
  • As a dictionary that contains two fields:
    • 'length' - a number that indicates the length of the value in bits
    • 'value' - a valid hexadecimal string

This format must be followed when specifying values of the BIT STRING type in order for the encoding to succeed.

Class-Based Bindings

General Considerations

Class-based bindings are Python classes that are used to easily create, modify, and manipulate messages described by an ASN.1 specification. This is made easier by IDE Intellisense features like autocomplete and type hints. The messages can then be used as input to the encode, decode, and validate API methods.

By default, the classes are located within an import module called bindings, under a "flat" (ASN.1 schema module name agnostic) namespace. Each class corresponds to an ASN.1 type from the input schema.

Messages, as instances of these classes, can be created from the Python dictionary representation of a message, which can be obtained from a JSON string. Messages can be created either by calling the from_native_type() static method of a particular message type:

>>> from bindings import *
    >>> jencstr = '{"range": 0,"name": " ","message": "","fuel": "solid","speed":{"mph": 0},"payload":[""]}'
    >>> jencdict = json.loads(jencstr)
    >>> msgObj = Rocket.from_native_type(jencdict)

or by calling the constructor of a message type:

>>> msgObj = Rocket()

In the latter case, the message components will be created recursively: simple types are initialized to some default value (INTEGERs to 0, BOOLEANs to False, OCTET/BIT STRING types to empty strings, etc.), SEQUENCE types contain the mandatory components, CHOICE types contain the first alternative, and SEQUENCE OF types contain one element.

Calling the from_native_type() method is referred to as message deserialization. To reverse this process and serialize messages back into their Python dictionary representation, call the to_native_type() method:

>>> sjencstr = msgObj.to_native_typess()

To serialize messages to a JSON string, call str on the message object:

>>> sjencstr = str(msgObj)

The characteristics of each message or message component type, such as including CHOICE alternatives, SEQUENCE components, etc., are exposed as object property attributes (or as plain methods for SEQUENCE OF types). The names of these attributes are derived from the field identifier names when possible (or from the type of a field, in some cases). This makes it easy to get/set them without the need to manually input each key-value pair of the underlying Python dictionary representation, particularly for constructed types, as required for native type bindings. A qualified dot notation can then be used to reference and get/set a field at a particular level of nestedness.

>>> msgObj = Rocket()
>>> msgObj.range = 0

The getter/setter properties provide type hints, which makes it clear what the expected return/input type is.

Additionally, the setter properties provide type checking and will raise an exception when an unexpected type is assigned to a property attribute.

>>> badtype = OSS_String("")
>>> msgObj.fuel = badtype
InvalidTypeParameter: Invalid type for value, expected: Rocket.Fuel

The following sections describe how ASN.1 types are mapped to their corresponding Python classes. As a general rule, the constructor of a constructed ASN.1 type is invoked without passing any arguments. On the other hand, the constructor of a simple ASN.1 type can take primitive or non-primitive built-in Python types as arguments, as explained below.

You must instantiate simple ASN.1 type values before assigning them to a field of that type. There is an exception for regular (unnamed) INTEGER or BOOLEAN fields, where you can assigned native int or boolean values directly.

SEQUENCE Types

SEQUENCE types inherit from the OSS_Sequence type.

The constructor of a SEQUENCE type creates a value with all mandatory components present and all optional components absent.

Mandatory components have setter and getter property attributes.

Optional components have an additional deleter property, which can be used to remove optional components from a SEQUENCE.

When a SEQUENCE type has components of nested types, classes for these types will be generated as inner classes inside the parent SEQUENCE type. The inner classes will be named after the ASN.1 component identifier.

If a DEFAULT field is not present in the input data when creating a message from its Python dict representation (by calling from_native_type() or by direct instantiation), the resulting object will still contain that field set to its default value. Conversely, if a field of the object representation is set to its default value, the resulting serialized JSON string will still contain that field set to its default value (the field is not removed on serialization).

CHOICE Types

CHOICE types inherit from the OSS_Choice type.

The constructor of a CHOICE type creates a value with the first alternative that is chosen.

The getter property of a CHOICE alternative will return either its value or None, if another alternative is chosen. The setter property of a CHOICE alternative will set a value for that alternative, which will replace any previously set alternative/value.

For each CHOICE alternative, there is a has{alternativeIdentifier} method that returns True when that alternative is chosen.

If a CHOICE type has alternatives of nested types, classes for these types will be generated as inner classes inside the parent CHOICE type. The inner classes will be named after the ASN.1 alternative identifier.

SEQUENCE OF Types

SEQUENCE OF types inherit from the OSS_SequenceOf type.

By default, the constructor of a SEQUENCE OF type creates a value with a single component.

SEQUENCE OF types are list-like data types that allow the use of square bracket indexing [] to set and get the value at a specified index, as well as passing an instance of this type to the built-in len function to get the number of elements. Additionally, the append method can add an element to the end of the list. Both the append method and setting a value using square bracket indexing [] provide type checking, and will raise an exception when an element of the wrong type is added or set.

OpenType Types

OpenType types inherit from the OSS_OpenType type.

The constructor of an OpenType type that is constrained by a non-empty object set creates a value of the type of the "first" entry in the constraining object set.

The getter and setter property attributes of an OpenType correspond to the possible resolved types of that OpenType. A getter property will return either a value or None, if the effective type of the OpenType is of a different type. A setter property will set the value of the OpenType to a value of that effective type, which will replace any previously set value.

For each possible effective type of an OpenType, there is a has{typeIdentifier} method that returns True when the OpenType value is of that type. If an OpenType type has possible effective types that are nested types, classes for these types will be generated as inner classes inside the parent OpenType type.

ENUMERATED Types

ENUMERATED types inherit from the OSS_Enum type. ENUMERATED types have string class attributes that represent the valid enumerators for that particular ENUMERATED type.

By default, the constructor of an ENUMERATED type creates a value set to the first enumerator.

ENUMERATED type values must be explicitly instantiated before being assigned to a field of that type:

>>> msgObj = Rocket()
>>> msgObj.fuel = Rocket.Fuel(Rocket.Fuel.solid) # OK

Directly assigning a str enum identifier to an ENUMERATED type field will cause an InvalidTypeParameter exception to be raised:

>>> msgObj.fuel = Rocket.Fuel.solid # not OK
InvalidTypeParameter: Invalid type for value, expected: Rocket.Fuel

INTEGER Types

Named INTEGER types inherit from the OSS_Integer type, while regular (unnamed) INTEGER types are mapped to Python's built-in int type. Therefore, Python native integer values can be assigned directly to regular INTEGER type fields.

By default, the constructor of an INTEGER type creates a value set to 0.

Consider the following SEQUENCE type definition that has a named INTEGER type component:

EUTRA-PhysCellId ::=  INTEGER (0..503)
    EUTRA-PhysCellIdRange ::=  SEQUENCE {
            start  EUTRA-PhysCellId
    }

Similar to ENUMERATED type values, named INTEGER type values must be explicitly instantiated before being assigned to a field of that type:

>>> msgObj = EUTRA_PhysCellIdRange()
>>> msgObj.start = EUTRA_PhysCellId(503) # OK

Directly assigning an int value to a named INTEGER type field will cause an InvalidTypeParameter exception to be raised:

>>> msgObj.start = 503
InvalidTypeParameter: Invalid type for value, expected:   EUTRA_PhysCellId

BOOLEAN Types

Named BOOLEAN types inherit from the OSS_Boolean type, while regular (unnamed) BOOLEAN types are mapped to Python's built-in bool type. Therefore, Python native boolean values can be directly assigned to regular BOOLEAN type fields.

By default, the constructor of a BOOLEAN type creates a value set to False.

OCTET STRING Types

All OCTET STRING types, whether they are named or regular, will inherit from OSS_OctetString.

By default, the constructor of an OCTET STRING type creates a value set to an empty string. The constructor does not do a sanity check on the format of its argument.

BIT STRING Types

All BIT STRING types, whether they are named or nested, will inherit from OSS_BitString.

BIT STRING type definitions that include named bit lists will have corresponding string class attributes that represent the identifiers of the bits from the BIT STRING's named bit list.

By default, the constructor of a BIT STRING type creates a value set to an empty string. The constructor of a BIT STRING type can take as argument a value in one of the following formats:

  • A dict that contains two items, where the value corresponding to the length' key is a number that indicates the length of the value in bits and the 'value' key is a valid hexadecimal string
  • A str that can be either:
    • An "H" string: a string enclosed by simple ' and suffixed with H, which contains hexadecimal digits
    • A "B" string: a string enclosed by simple ' and suffixed with B, which contains binary digits
    • A plain string value with hexadecimal digits
  • A tuple, that can either:
    • Contain two elements: the first is an int that indicates the length of the value in bits and the second is a bytes value, which represents the value itself
    • Contain one or more string bit identifiers (such as BIT STRING types that include named bit lists)

Similar to ENUMERATED and named INTEGER type values, BIT STRING type values must be explicitly instantiated before they are assigned to a field of the corresponding type. When a value is directly assigned to a BIT STRING type field, an InvalidTypeParameter exception is raised. BIT STRING values allow the use of square bracket indexing [] to set and get the value of a bit at a specified index.

Consider the following BIT STRING type definition:

NamedBitString ::= BIT STRING { bitOne(1), bitTwo(2), bitTen(10) }

An object of this type can be instantiated as:

 >>> mybs = NamedBitString((NamedBitString.bitOne, NamedBitString.bitTen, ))

One can check the value of the first bit and set the value of the second bit:

>>> mybs[1]
1
>>> mybs[2]
0
>>> mybs[2] = 1
>>> mybs[2]
1

OBJECT IDENTIFIER Types

All OBJECT IDENTIFIER types, whether they are named or nested, will inherit from OSS_ObjectIdentifier.

By default, the constructor of an OBJECT IDENTIFIER type creates a value set to an empty string. The constructor does not do a sanity check on the format of its argument.

RELATIVE-OID Types

All RELATIVE-OID types, whether they are named or nested, will inherit from OSS_RelativeOid.

By default, the constructor of a RELATIVE-OID type creates a value set to an empty string. The constructor does not do a sanity check on the format of its argument.

REAL Types

All REAL types, whether they are named or nested, will inherit from OSS_Real.

By default, the constructor of a REAL type creates a value set to 0.0. The constructor does not do a sanity check on the format of its argument.

GeneralizedTime Types

All GeneralizedTime types, whether they are named or nested, will inherit from OSS_GeneralizedTime.

By default, the constructor of a GeneralizedTime type creates a value set to an empty string. The constructor does not do a sanity check on the format of its argument.

UTCTime Types

All UTCTime types, whether they are named or nested, will inherit from OSS_UTCTime.

By default, the constructor of a UTCTime type creates a value set to an empty string. The constructor does not do a sanity check on the format of its argument.

NULL Type

The OSS_Null global object is used to represent the ASN.1 NULL value.

Limitations and Known Issues

Instantiation of class-based bindings, either directly or by deserialization, could fail for (but is not limited to) schemas that

  • Have context-specific tags and recursive types.
  • Contain, in some rare cases, SET or SEQUENCE components constrained by component relation constraints, where the referenced component value belongs to a value set.
  • Contain certain parameterized type constructs, particularly in cases where the parameter is a referencing field.

Unsupported types

In addition to the types listed in the Codec API Known Limitations section, the class-based bindings lack support for the following types:

  • EXTERNAL
  • EMBEDDED PDV
  • CHARACTER STRING
  • IRI