x690.util

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(*values)

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

class x690.util.LengthInfo(length, offset)
length : int

Alias for field number 0

offset : int

Alias for field number 1

class x690.util.TypeClass(*values)
class x690.util.TypeInfo(cls: TypeClass, nature: TypeNature, tag: int)

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 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: int | bytes) → TypeInfo

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(*values)
class x690.util.ValueMetaData(bounds, next_value_index)
bounds : slice

Alias for field number 0

next_value_index : int

Alias for field number 1

x690.util.decode_length(data: bytes, index: int = 0) → LengthInfo

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: int) → bytes

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) → 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: bytes) → str

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.