Custom types¶
Protocols built on ASN.1 define their own types in the application, context
and private classes. x690 decodes them as UnknownType
until it knows them. Subclass X690Type, or one of its
subclasses, to add a type: subclassing registers it with
decode().
Describe the type¶
Three class attributes identify a type:
TYPECLASSA
TypeClass:APPLICATION,CONTEXTorPRIVATEfor protocol types.UNIVERSALbelongs to the standard.NATUREA list of
TypeNaturevalues:PRIMITIVEfor single values,CONSTRUCTEDfor values that contain other values.TAGThe tag number, unique within the class.
pretty() prints these three values for every
unknown type it finds, so they can be copied from a dump of real data.
Reuse an existing type¶
When the new type is encoded like an existing one, subclass that type and
change only the attributes. The SNMP GetRequest PDU from
Usage is a sequence in the context class with tag 0:
>>> from x690 import decode
>>> from x690.types import Integer, Null
>>> from x690.types import ObjectIdentifier
>>> from x690.types import OctetString, Sequence
>>> from x690.util import TypeClass
>>> class GetRequest(Sequence):
... TYPECLASS = TypeClass.CONTEXT
... TAG = 0
Once the class exists, the same bytes decode to a full structure:
>>> data = (
... b"0'\x02\x01\x01\x04\x06public"
... b"\xa0\x1a\x02\x02\x04\xd2\x02\x01\x00"
... b"\x02\x01\x000\x0e0\x0c\x06\x08+\x06"
... b"\x01\x02\x01\x01\x01\x00\x05\x00"
... )
>>> message, _ = decode(data)
>>> print(message[2].pretty())
GetRequest with 4 items:
⁃ Integer(1234)
⁃ Integer(0)
⁃ Integer(0)
⁃ Sequence with 1 items:
⁃ Sequence with 2 items:
⁃ 1.3.6.1.2.1.1.1.0
⁃ Null()
The class also encodes. This builds the same message from scratch:
>>> varbind = Sequence([
... ObjectIdentifier("1.3.6.1.2.1.1.1.0"),
... Null(),
... ])
>>> request = GetRequest([
... Integer(1234),
... Integer(0),
... Integer(0),
... Sequence([varbind]),
... ])
>>> message = Sequence([
... Integer(1),
... OctetString(b"public"),
... request,
... ])
>>> bytes(message) == data
True
Convert your own values¶
For a new encoding, override two methods:
decode_raw()A static method. It receives the data and the slice that holds the value, without the type and length header, and returns the Python value.
encode_raw()Returns the bytes of
self.pyvalue, without the header.
This type stores a person as UTF-8 encoded JSON in the application class:
>>> import json
>>> from typing import NamedTuple
>>> from x690.types import X690Type
>>> from x690.util import TypeNature
>>> class Person(NamedTuple):
... name: str
... age: int
>>> class PersonType(X690Type[Person]):
... TYPECLASS = TypeClass.APPLICATION
... NATURE = [TypeNature.PRIMITIVE]
... TAG = 1
...
... @staticmethod
... def decode_raw(data, slc=slice(None)):
... return Person(**json.loads(data[slc]))
...
... def encode_raw(self):
... value = self.pyvalue._asdict()
... return json.dumps(value).encode()
>>> data = bytes(PersonType(Person("Ada", 36)))
>>> data
b'A\x1a{"name": "Ada", "age": 36}'
>>> person, _ = decode(
... data, enforce_type=PersonType
... )
>>> person.value
Person(name='Ada', age=36)
Rules for custom types¶
The class needs a constructor that works without arguments: x690 creates the instance first and attaches the data after.
Import the module that defines the class before decoding. Types are registered when the class is created.
A custom type with a universal class and tag replaces the class x690 ships for that tag. Do this only on purpose, for example to give one of the raw-bytes types a Python value.