x690 package

Submodules

x690.exc module

This module contains exceptions for the x.690 protocol

exception x690.exc.IncompleteDecoding(message: str, remainder: bytes)

Bases: x690.exc.X690Error

Raised when decoding did not consume all bytes.

The junk bytes are stored in the “remainder” attribute

exception x690.exc.UnexpectedType

Bases: x690.exc.X690Error

Raised when decoding resulted in an unexpected type.

exception x690.exc.X690Error

Bases: Exception

Top-Level exception for everything related to the X690 protocol

x690.types module

Overview

This module contains the encoding/decoding logic for data types as defined in X.690.

Each type is made available via a registry dictionary on X690Type and can be retrieved via get().

Additionally, given a bytes object, the decode() function can be used to parse the bytes object and return a typed instance from it. See decode() for details about it’s behaviour!

Note

The individual type classes in this module do not contain any additional documentation. The bulk of this module is documented in X690Type.

For the rest, the type classes simply define the type identifier tag.

Supporting Additional Classes

Just by subclassing X690Type and setting correct TAG and TYPECLASS values, most of the basic functionality will be covered by the superclass. X690Type detection, and addition to the registry is automatic. Subclassing is enough.

By default, a new type which does not override any methods will have it’s value reported as bytes objects. You may want to override at least decode_raw() to convert the raw-bytes into your own data-type.

Example

Let’s assume you want to decode/encode a “Person” object with a first-name, last-name and age. Let’s also assume it will be an application-specific type of a “constructed” nature with our application-local tag 1. Let’s further assume that the value will be a UTF-8 encoded JSON string inside the x690 stream.

We specify the metadata as class-level variables TYPECLASS, NATURE and TAG. The decoding is handled by implementing a static-method decode_raw which gets the data-object containing the value and a slice defining at which position the data is located. The encoding is handled by implementing the instance-method encode_raw. The instance contains the Python value in self.pyvalue.

So we can implement this as follows (including a named-tuple as our local type):

from typing import NamedTuple
from x690.types import X690Type
from json import loads, dumps

class Person(NamedTuple):
    first_name: str
    last_name: str
    age: int

class PersonType(X690Type[Person]):
    TYPECLASS = TypeClass.APPLICATION
    NATURE = [TypeNature.CONSTRUCTED]
    TAG = 1

    @staticmethod
    def decode_raw(data: bytes, slc: slice = slice(None)) -> Person:
        values = loads(data[slc].decode("utf8"))
        return Person(
            values["first_name"], values["last_name"], values["age"]
        )

    def encode_raw(self) -> bytes:
        return dumps(self.pyvalue._asdict()).encode("utf8")
class x690.types.BitString(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 3
pyvalue
class x690.types.BmpString(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 30
pyvalue
class x690.types.Boolean(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>]
TAG = 1
static decode_raw(data: bytes, slc: slice = slice(None, None, None)) → bool

Converts the raw byte-value (without type & length header) into a pure Python type

Overrides decode_raw()

encode_raw() → bytes

Overrides X690Type.encode_raw()

pyvalue
classmethod validate(data: bytes) → None

Overrides X690Type.validate()

class x690.types.CharacterString(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

TAG = 29
pyvalue
class x690.types.EOC(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>]
TAG = 0
pyvalue
class x690.types.EmbeddedPdv(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

TAG = 11
pyvalue
class x690.types.Enumerated(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>]
TAG = 10
pyvalue
class x690.types.External(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

TAG = 8
pyvalue
class x690.types.GeneralString(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 27
pyvalue
class x690.types.GeneralizedTime(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 24
pyvalue
class x690.types.GraphicString(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 25
static decode_raw(data: bytes, slc: slice = slice(None, None, None)) → str

Converts the raw byte-value (without type & length header) into a pure Python type

Overrides decode_raw()

pyvalue
class x690.types.IA5String(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 22
pyvalue
class x690.types.Integer(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>]
SIGNED = True
TAG = 2
classmethod decode_raw(data: bytes, slc: slice = slice(None, None, None)) → int

Converts the raw byte-value (without type & length header) into a pure Python type

Overrides decode_raw()

encode_raw() → bytes

Overrides X690Type.encode_raw()

pyvalue
class x690.types.Null(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>]
TAG = 5
static decode_raw(data: bytes, slc: slice = slice(None, None, None)) → None

Converts the raw byte-value (without type & length header) into a pure Python type

Overrides decode_raw()

encode_raw() → bytes

Overrides X690Type.encode_raw()

>>> Null().encode_raw()
b'\x00'
pyvalue
classmethod validate(data: bytes) → None

Overrides X690Type.validate()

class x690.types.NumericString(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 18
pyvalue
class x690.types.ObjectDescriptor(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 7
pyvalue
class x690.types.ObjectIdentifier(value: Union[str, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

Represents an OID.

Instances of this class support containment checks to determine if one OID is a sub-item of another:

>>> ObjectIdentifier("1.2.3.4.5") in ObjectIdentifier("1.2.3")
True

>>> ObjectIdentifier("1.2.4.5.6") in ObjectIdentifier("1.2.3")
False
NATURE = [<TypeNature.PRIMITIVE: 'primitive'>]
TAG = 6
childof(other: x690.types.ObjectIdentifier) → bool

Convenience method to check whether this OID is a child of another OID

collapse_identifiers()

Meld the first two octets into one octet as defined by x.690

In x.690 ObjectIdentifiers are a sequence of numbers. In the byte-representation the first two of those numbers are stored in the first byte.

This function takes a “human-readable” OID tuple and returns a new tuple with the first two elements merged (collapsed) together.

>>> ObjectIdentifier("1.3.6.1.4.1").collapse_identifiers()
(43, 6, 1, 4, 1)
>>> ObjectIdentifier().collapse_identifiers()
()
static decode_large_value()

If we encounter a value larger than 127, we have to consume from the stram until we encounter a value below 127 and recombine them.

See: https://msdn.microsoft.com/en-us/library/bb540809(v=vs.85).aspx

static decode_raw(data: bytes, slc: slice = slice(None, None, None)) → str

Converts the raw byte-value (without type & length header) into a pure Python type

Overrides decode_raw()

static encode_large_value()

Inverse function of decode_large_value()

encode_raw() → bytes

Overrides X690Type.encode_raw()

property nodes

Returns the numerical nodes for this instance as tuple

>>> ObjectIdentifier("1.2.3").nodes
(1, 2, 3)
>>> ObjectIdentifier().nodes
()
parentof(other: x690.types.ObjectIdentifier) → bool

Convenience method to check whether this OID is a parent of another OID

pyvalue
class x690.types.OctetString(value: Union[str, bytes, x690.types._SENTINEL_UNINITIALISED] = b'')

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 4
pretty(depth: int = 0) → str

Returns a prettified string with depth levels of indentation

See pretty()

pyvalue
class x690.types.PrintableString(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 19
pyvalue
class x690.types.Real(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>]
TAG = 9
pyvalue
class x690.types.RelativeOid(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>]
TAG = 13
pyvalue
class x690.types.Sequence(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

Represents an X.690 sequence type. Instances of this class are iterable and indexable.

TAG = 16
static decode_raw()

Converts the raw byte-value (without type & length header) into a pure Python type

Overrides decode_raw()

encode_raw() → bytes

Overrides X690Type.encode_raw()

pretty(depth: int = 0) → str

Returns a prettified string with depth levels of indentation

See pretty()

pythonize()

Overrides pythonize()

pyvalue
class x690.types.Set(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

TAG = 17
pyvalue
class x690.types.T61String(value: Union[str, bytes] = '')

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 20
static decode_raw(data: bytes, slc: slice = slice(None, None, None)) → str

Converts the raw byte-value (without type & length header) into a pure Python type

Overrides decode_raw()

encode_raw() → bytes

Overrides X690Type.encode_raw()

pyvalue
x690.types.UNINITIALISED = <x690.types._SENTINEL_UNINITIALISED object>

sentinel value for uninitialised objects (used for lazy decoding)

class x690.types.UniversalString(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 28
pyvalue
class x690.types.UnknownType(value: bytes = b'', tag: int = -1)

Bases: x690.types.X690Type

A fallback type for anything not in X.690.

Instances of this class contain the raw information as parsed from the bytes as the following attributes:

  • value: The value without leading metadata (as bytes value)

  • tag: The unparsed “tag”. This is the type ID as defined in the reference document. See TypeInfo for details.

  • typeinfo: unused (derived from tag and only here for consistency with __repr__ of this class).

TAG = 153
pretty(depth: int = 0) → str

Returns a prettified string with depth levels of indentation

See pretty()

pyvalue
class x690.types.UtcTime(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 23
pyvalue
class x690.types.Utf8String(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 12
pyvalue
class x690.types.VideotexString(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 21
pyvalue
class x690.types.VisibleString(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: x690.types.X690Type

NATURE = [<TypeNature.PRIMITIVE: 'primitive'>, <TypeNature.CONSTRUCTED: 'constructed'>]
TAG = 26
pyvalue
class x690.types.X690Type(value: Union[TWrappedPyType, x690.types._SENTINEL_UNINITIALISED] = <x690.types._SENTINEL_UNINITIALISED object>)

Bases: typing.Generic

The superclass for all supported types.

NATURE = [<TypeNature.CONSTRUCTED: 'constructed'>]

The x690 “private/constructed” information

TAG = -1

The x690 identifier for the type

TYPECLASS = 'universal'

The x690 type-class (universal, application or context)

static all()

Returns all registered classes

bounds = slice(None, None, None)

The location of the value within “raw_bytes”

classmethod decode(data: bytes) → TConcreteType

This method takes a bytes object which contains the raw content octets of the object. That means, the octets without the type information and length.

This function must be overridden by the concrete subclasses.

static decode_raw(data: bytes, slc: slice = slice(None, None, None)) → TWrappedPyType

Converts the raw byte-value (without type & length header) into a pure Python type

>>> Integer.decode_raw(b"\x05")
5
Parameters
  • data – A data-block containing the byte-information

  • slc – A slice of the data-block that contains the exact raw-bytes.

Returns

The value that should be wrapped by the current x690 type.

encode_raw() → bytes

Convert this instance into raw x690 bytes (excluding the type and length header)

>>> import x690.types as t
>>> Integer(5).encode_raw()
b'\x05'
>>> Boolean(True).encode_raw()
b'\x01'
>>> X690Type(t.UNINITIALISED).encode_raw()
b''
classmethod from_bytes(data: bytes, slc: slice = slice(None, None, None)) → TConcreteType

Creates a new x690.types.X690Type instance from raw-bytes (without type nor length bytes)

>>> Integer.from_bytes(b"\x01")
Integer(1)
>>> OctetString.from_bytes(b"hello-world")
OctetString(b'hello-world')
>>> Boolean.from_bytes(b"\x00")
Boolean(False)
static get()

Retrieve a Python class by x690 type information

Classes can be registered by subclassing x690.types.X690Type

property length

Return the x690 byte-length of this instance

pretty(depth: int = 0) → str

Returns a readable representation (possibly multiline) of the value.

The value is indented by depth levels of indentation

By default this simply returns the string representation. But more complex values may override this.

pythonize() → TWrappedPyType

Convert this instance to an appropriate pure Python object.

pyvalue

The decoded (or to-be encoded) Python value

property raw_bytes
classmethod validate(data: bytes) → None

Given a bytes object, checks if the given class cls supports decoding this object. If not, raises a ValueError.

property value

Returns the value as a pure Python type

x690.types.decode()

Convert a X.690 bytes object into a Python instance, and the location of the next object.

Given a bytes object and any start-index, inspects and parses the octets starting at the given index (as many as required) to determine variable type (and corresponding Python class), and length. That class is then used to parse the object located in data at the given index. The location of the start of the next (subsequent) object is also determined.

The return value is a tuple with the decoded object and the start-index of the next object.

Example:

>>> data = b'\x02\x01\x05\x11'
>>> decode(data)
(Integer(5), 3)
>>> data = b'some-skippable-bytes\x02\x01\x05\x11'
>>> decode(data, 20)
(Integer(5), 23)

x690.util module

Utility functions for working with the X.690 and related standards.

x690.util.INDENT_STRING = ' '

String to be used for indenting nested items during “pretty()” calls

class x690.util.Length

Bases: str, enum.Enum

A simple “namespace” to avoid magic values for indefinite lengths.

INDEFINITE = 'indefinite'
class x690.util.LengthInfo(length, offset)

Bases: tuple

length

Alias for field number 0

offset

Alias for field number 1

class x690.util.TypeClass

Bases: str, enum.Enum

An enumeration.

APPLICATION = 'application'
CONTEXT = 'context'
PRIVATE = 'private'
UNIVERSAL = 'universal'
class x690.util.TypeInfo(cls: x690.util.TypeClass, nature: x690.util.TypeNature, tag: int)

Bases: object

Decoded structure for an X.690 “type” octet. Example:

>>> TypeInfo.from_bytes(b'\x30')
TypeInfo(cls=<TypeClass.UNIVERSAL: 'universal'>, nature=<TypeNature.CONSTRUCTED: 'constructed'>, tag=16)

The structure contains 3 fields:

cls

The typeclass (a value taken from TypeClass)

nature

A value taken from :py:`~.TypeNature`

tag

The actual type identifier.

The instance also keeps the raw value as it was seen in the _raw_value attribute.

static from_bytes(data)

Given one octet, extract the separate fields and return a TypeInfo instance:

>>> TypeInfo.from_bytes(b'\x30')
TypeInfo(cls=<TypeClass.UNIVERSAL: 'universal'>, nature=<TypeNature.CONSTRUCTED: 'constructed'>, tag=16)
class x690.util.TypeNature

Bases: str, enum.Enum

An enumeration.

CONSTRUCTED = 'constructed'
PRIMITIVE = 'primitive'
class x690.util.ValueMetaData(bounds, next_value_index)

Bases: tuple

bounds

Alias for field number 0

next_value_index

Alias for field number 1

x690.util.decode_length(data, index=0)

Given a bytes object, which starts with the length information of a TLV value, returns a namedtuple with the length and the number of bytes which contained the length information. So, given a TLV value, this function takes the “LV” part as input, parses the length information and returns the length plus the number of bytes which need to be skipped to arrive at the beginning of the value.

For values which are longer than 127 bytes, the length must be encoded into an unknown amount of “length” bytes. This function reads as many bytes as needed for the length. Consuming bytes for the value must therefore start after the bytes containing the length info. The second value returned from this function includes this information.

The second argument (index) tells the function at which position to look for the length information.

Examples:

>>> # length > 127, consume multiple length bytes
>>> decode_length(b'\x81\xc8...')
LengthInfo(length=200, offset=2)

>>> # length <= 127, consume one length byte
>>> decode_length(b'\x10...')
LengthInfo(length=16, offset=1)
TODO: Upon rereading this, I wonder if it would not make more sense to take

the complete TLV content as input.

x690.util.encode_length(value)

This function encodes the length of a variable into bytes conforming to the rules defined in X.690: The “length” field must be specially encoded for values above 127. Additionally, from X.690:

8.1.3.2 A sender shall:

  1. use the definite form (see 8.1.3.3) if the encoding is primitive;

  2. use either the definite form (see 8.1.3.3) or the indefinite form (see 8.1.3.6), a sender’s option, if the encoding is constructed and all immediately available;

  3. use the indefinite form (see 8.1.3.6) if the encoding is constructed and is not all immediately available.

See also: https://en.wikipedia.org/wiki/X.690#Length_octets

Example:

>>> encode_length(16)    # no need for special encoding.
b'\x10'
>>> encode_length(200)   # > 127, needs to be specially encoded.
b'\x81\xc8'
x690.util.get_value_slice(data: bytes, index: int = 0) → x690.util.ValueMetaData

Helper method to extract lightweight information about value locations in a data-stream.

The function returns both a slice at which a value can be found, and the index at which the next value can be found.

x690.util.visible_octets(data)

Returns a geek-friendly (hexdump) output of a bytes object.

Developer note:

This is not super performant. But it’s not something that’s supposed to be run during normal operations (mostly for testing and debugging). So performance should not be an issue, and this is less obfuscated than existing solutions.

Example:

>>> from os import urandom
>>> print(visible_octets(urandom(40)))  
99 1f 56 a9 25 50 f7 9b  95 7e ff 80 16 14 88 c5   ..V.%P...~......
f3 b4 83 d4 89 b2 34 b4  71 4e 5a 69 aa 9f 1d f8   ......4.qNZi....
1d 33 f9 8e f1 b9 12 e9                            .3......
x690.util.wrap(text: str, header: str, depth: int) → str

Wraps text in a border with a header and indented by indent.

Module contents

x690 is a library allowing to decode and encode values according to the ITU X.690 standard.

For decoding, use the function decode provided by this module. When used as-is on a bytes object, it will return the first decoded value in the blob and the position of the next value:

>>> from x690 import decode
>>> data, next_index = decode(b"")
>>> data
Integer(1)
>>> next_index
3

For a stream of data with an unknown number of items, you can use the next-index to move over the data:

>>> from x690 import decode
>>> data = b"\x02\x01\x01\x02\x01\x02\x02\x01\x03"
>>> item, next_index = decode(data)
>>> item, next_index
(Integer(1), 3)
>>> while next_index < len(data):
...     item, next_index = decode(data, next_index)
...     item, next_index
(Integer(2), 6)
(Integer(3), 9)

If you expect exactly one element, it is possible to pass the “strict” argument. This will raise an error if the stream contains any “junk” data.

Finally, a x690 data-stream may contain various types. For this reason, the “decode” function is type-hinted to return a vague, imprecise type. If you know what your are decoding, you can pass the expected type into the function:

>>> from x690 import decode
>>> from x690.types import Integer
>>> decode(b"", enforce_type=Integer)
(Integer(1), 3)

This will do two things:

  • It runs a type-check upon decoding and will raise a x690.exc.UnexpectedType error if it does not match.

  • Inform the type-checker of the return-type, improving type-checker output.

x690.decode()

Convert a X.690 bytes object into a Python instance, and the location of the next object.

Given a bytes object and any start-index, inspects and parses the octets starting at the given index (as many as required) to determine variable type (and corresponding Python class), and length. That class is then used to parse the object located in data at the given index. The location of the start of the next (subsequent) object is also determined.

The return value is a tuple with the decoded object and the start-index of the next object.

Example:

>>> data = b'\x02\x01\x05\x11'
>>> decode(data)
(Integer(5), 3)
>>> data = b'some-skippable-bytes\x02\x01\x05\x11'
>>> decode(data, 20)
(Integer(5), 23)