synta 0.2.2

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# Encoder


## Constructor

```python
Encoder(encoding: Encoding)
```

Creates a new in-memory encoder.  Elements are appended in call order.
Call `finish()` to retrieve the accumulated bytes.

## Primitive encode methods — raw values

| Method | Signature | ASN.1 type |
|---|---|---|
| `encode_integer` | `(value: int)` | INTEGER — accepts any Python `int` (arbitrary magnitude via bigint path) |
| `encode_octet_string` | `(data: bytes)` | OCTET STRING |
| `encode_oid` | `(value: ObjectIdentifier)` | OBJECT IDENTIFIER |
| `encode_bit_string` | `(value: BitString)` | BIT STRING |
| `encode_boolean` | `(value: bool)` | BOOLEAN |
| `encode_utc_time` | `(value: UtcTime)` | UTCTime |
| `encode_generalized_time` | `(value: GeneralizedTime)` | GeneralizedTime |
| `encode_real` | `(value: float)` | REAL |
| `encode_null` | `()` | NULL |
| `encode_utf8_string` | `(value: str)` | UTF8String |
| `encode_printable_string` | `(value: str)` | PrintableString — raises `ValueError` for invalid chars |
| `encode_ia5_string` | `(value: str)` | IA5String — raises `ValueError` for non-ASCII |
| `encode_numeric_string` | `(value: str)` | NumericString — raises `ValueError` for non-digit/non-space |
| `encode_teletex_string` | `(data: bytes)` | TeletexString / T61String |
| `encode_visible_string` | `(value: str)` | VisibleString — raises `ValueError` for invalid chars |
| `encode_general_string` | `(data: bytes)` | GeneralString |
| `encode_universal_string` | `(value: str)` | UniversalString (UCS-4 BE) |
| `encode_bmp_string` | `(value: str)` | BMPString (UCS-2 BE) — raises `ValueError` for non-BMP code points |

## Primitive encode methods — typed objects

Each primitive type has an `_object` variant that accepts the corresponding
Python wrapper instead of a raw value.

| Method | Accepts |
|---|---|
| `encode_integer_object` | `Integer` |
| `encode_octet_string_object` | `OctetString` |
| `encode_oid_object` | `ObjectIdentifier` (alias for `encode_oid`) |
| `encode_bit_string_object` | `BitString` (alias for `encode_bit_string`) |
| `encode_boolean_object` | `Boolean` |
| `encode_utc_time_object` | `UtcTime` (alias for `encode_utc_time`) |
| `encode_generalized_time_object` | `GeneralizedTime` (alias for `encode_generalized_time`) |
| `encode_real_object` | `Real` |
| `encode_null_object` | `Null` |
| `encode_utf8_string_object` | `Utf8String` |
| `encode_printable_string_object` | `PrintableString` |
| `encode_ia5_string_object` | `IA5String` |
| `encode_numeric_string_object` | `NumericString` |
| `encode_teletex_string_object` | `TeletexString` |
| `encode_visible_string_object` | `VisibleString` |
| `encode_general_string_object` | `GeneralString` |
| `encode_universal_string_object` | `UniversalString` |
| `encode_bmp_string_object` | `BmpString` |

## Container / tagging encode methods

| Method | Signature | Description |
|---|---|---|
| `encode_sequence` | `(inner_bytes: bytes)` | Wrap `inner_bytes` with SEQUENCE TLV (tag `0x30`). |
| `encode_set` | `(inner_bytes: bytes)` | Wrap `inner_bytes` with SET TLV (tag `0x31`). |
| `encode_explicit_tag` | `(tag_num: int, tag_class: str, inner_bytes: bytes)` | Wrap `inner_bytes` in an explicit constructed tag. `tag_class`: `"Context"`, `"Application"`, `"Private"`. |
| `encode_implicit_tag` | `(tag_num: int, tag_class: str, is_constructed: bool, value_bytes: bytes)` | Emit an implicit tag over raw value bytes. Set `is_constructed=True` for SEQUENCE/SET underlying types. |

## Finalisation

| Method | Returns | Description |
|---|---|---|
| `finish()` | `bytes` | Consume the encoder and return the accumulated DER/BER bytes. The encoder is reset and can be reused (rarely needed). |

## Full class stub

```python
class Encoder:
    def __init__(self, encoding: Encoding) -> None: ...

    # Primitive types
    def encode_integer(self, value: int) -> None: ...
    # Accepts any Python int, including values beyond i64/i128 range (e.g.
    # 20-byte X.509 serial numbers up to 160 bits).
    def encode_integer_object(self, value: Integer) -> None: ...
    def encode_octet_string(self, data: bytes) -> None: ...
    def encode_octet_string_object(self, value: OctetString) -> None: ...
    def encode_oid(self, value: ObjectIdentifier) -> None: ...
    def encode_bit_string(self, value: BitString) -> None: ...
    def encode_boolean(self, value: bool) -> None: ...
    def encode_real(self, value: float) -> None: ...
    def encode_real_object(self, value: Real) -> None: ...
    def encode_null(self) -> None: ...
    def encode_null_object(self, value: Null) -> None: ...
    def encode_utc_time(self, value: UtcTime) -> None: ...
    def encode_generalized_time(self, value: GeneralizedTime) -> None: ...

    # String types
    def encode_utf8_string(self, value: str) -> None: ...
    def encode_utf8_string_object(self, value: Utf8String) -> None: ...
    def encode_printable_string(self, value: str) -> None: ...       # Raises ValueError for invalid chars
    def encode_printable_string_object(self, value: PrintableString) -> None: ...
    def encode_ia5_string(self, value: str) -> None: ...             # Raises ValueError for non-ASCII
    def encode_ia5_string_object(self, value: IA5String) -> None: ...
    def encode_numeric_string(self, value: str) -> None: ...         # Raises ValueError for non-digit/space
    def encode_numeric_string_object(self, value: NumericString) -> None: ...
    def encode_teletex_string(self, data: bytes) -> None: ...        # Raw bytes, tag 20
    def encode_teletex_string_object(self, value: TeletexString) -> None: ...
    def encode_visible_string(self, value: str) -> None: ...         # Raises ValueError for control chars
    def encode_visible_string_object(self, value: VisibleString) -> None: ...
    def encode_general_string(self, data: bytes) -> None: ...        # Raw bytes, tag 27
    def encode_general_string_object(self, value: GeneralString) -> None: ...
    def encode_universal_string(self, value: str) -> None: ...       # UCS-4 BE, tag 28
    def encode_universal_string_object(self, value: UniversalString) -> None: ...
    def encode_bmp_string(self, value: str) -> None: ...             # UCS-2 BE, tag 30; Raises ValueError if > U+FFFF
    def encode_bmp_string_object(self, value: BmpString) -> None: ...

    # Constructed / tagged
    def encode_sequence(self, inner_bytes: bytes) -> None: ...
    # Wraps pre-encoded bytes in a SEQUENCE TLV (tag 0x30).
    # Encode inner elements into a separate Encoder, call finish(), then pass
    # the result here.

    def encode_set(self, inner_bytes: bytes) -> None: ...
    # Wraps pre-encoded bytes in a SET TLV (tag 0x31).

    def encode_explicit_tag(self, tag_num: int, tag_class: str, inner_bytes: bytes) -> None: ...
    # Wraps pre-encoded bytes in an explicit tag TLV (always constructed).
    # tag_class must be "Context", "Application", or "Private".
    # Raises ValueError for any other value.

    def encode_implicit_tag(self, tag_num: int, tag_class: str,
                            is_constructed: bool, value_bytes: bytes) -> None: ...
    # Writes an implicit tag with the given value bytes (not a full TLV).
    # For implicit tagging the original type tag is replaced, so pass the raw
    # value content (not the original TLV).  Set is_constructed=True when the
    # underlying type is SEQUENCE, SET, or another constructed form.
    # tag_class must be "Context", "Application", or "Private".
    #
    # Example — [1] IMPLICIT INTEGER with raw value bytes b'\x2a':
    #   enc.encode_implicit_tag(1, "Context", False, b'\x2a')

    def finish(self) -> bytes: ...
```

## Usage examples

### Encoding ASN.1 data

```python
import synta

# Encode an INTEGER
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_integer(42)
output = encoder.finish()
print(output.hex())  # Output: 02012a

# Encode an OCTET STRING
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_octet_string(b'hello')
output = encoder.finish()
print(output.hex())  # Output: 040568656c6c6f

# Encode a NULL
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_null()
output = encoder.finish()
print(output.hex())  # Output: 0500

# Encode a REAL
import math
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_real(0.0)
output = encoder.finish()
print(output.hex())  # Output: 0900

encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_real(math.inf)
output = encoder.finish()
print(output.hex())  # Output: 090140

# Encode a UTF8String
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_utf8_string("Hello, world!")
output = encoder.finish()

# Encode a GeneralString (Kerberos realm, FreeIPA use-case)
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_general_string(b"EXAMPLE.COM")
output = encoder.finish()

# Encode a BMPString (Microsoft AD-CS template name)
encoder = synta.Encoder(synta.Encoding.DER)
encoder.encode_bmp_string("User")
output = encoder.finish()
```

### Building SEQUENCE structures

Encode inner elements into a separate `Encoder`, then wrap the output in a
`SEQUENCE` or `SET` using the outer `Encoder`.  Use `encode_explicit_tag` to
add explicit context tags.

```python
import synta

# Build SEQUENCE { INTEGER 42, BOOLEAN TRUE }
inner = synta.Encoder(synta.Encoding.DER)
inner.encode_integer(42)
inner.encode_boolean(True)

outer = synta.Encoder(synta.Encoding.DER)
outer.encode_sequence(inner.finish())
result = outer.finish()
# result == b'\x30\x06\x02\x01\x2a\x01\x01\xff'

# Build [1] EXPLICIT INTEGER 99 (explicit context tag)
inner = synta.Encoder(synta.Encoding.DER)
inner.encode_integer(99)

outer = synta.Encoder(synta.Encoding.DER)
outer.encode_explicit_tag(1, "Context", inner.finish())
result = outer.finish()
# First byte 0xa1 = context-specific constructed tag [1]
```