synta 0.2.0

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
# 12. `example_objectidentifier.py` — ObjectIdentifier constructors and operations

[← Example index](index.md) · [example_objectidentifier.py on Codeberg](https://codeberg.org/abbra/synta/src/branch/main/examples/example_objectidentifier.py)

Bindings: `ObjectIdentifier(str)`, `ObjectIdentifier.from_components`,
`ObjectIdentifier.from_der_value`, `ObjectIdentifier.components`,
`ObjectIdentifier.__eq__`, `ObjectIdentifier.__hash__`,
`Decoder.decode_oid`, `Encoder.encode_oid`, `Encoder.encode_oid_object`.

- Construct OIDs via all three constructors; verify they compare equal.
- Show `__eq__` working against a dotted string.
- Use OIDs as dict keys (demonstrate hashability).
- Round-trip through `encode_oid` / `decode_oid`.
- Show `from_der_value` with the raw content bytes from an implicit-tag context.

## Source

```python
#!/usr/bin/env python3
"""
Example 11: ObjectIdentifier constructors and operations.

Demonstrates: ObjectIdentifier(str), ObjectIdentifier.from_components,
ObjectIdentifier.from_der_value, ObjectIdentifier.components,
ObjectIdentifier.__eq__ (against string and OID), ObjectIdentifier.__hash__,
Decoder.decode_oid, Encoder.encode_oid, Encoder.encode_oid_object.
"""

import synta
import synta.oids as oids


def section(title):
    print(f"\n{'─' * 60}\n{title}\n{'─' * 60}")


def demo_constructors():
    section("Three constructors — all yield equivalent OIDs")
    # 1. From dotted-decimal string
    oid_str = synta.ObjectIdentifier("1.2.840.113549.1.1.11")  # sha256WithRSAEncryption

    # 2. From component list
    oid_comp = synta.ObjectIdentifier.from_components([1, 2, 840, 113549, 1, 1, 11])

    # 3. From DER value bytes (the OID content without tag/length)
    #    sha256WithRSAEncryption DER content: 2a 86 48 86 f7 0d 01 01 0b
    der_value = bytes.fromhex("2a864886f70d01010b")
    oid_der = synta.ObjectIdentifier.from_der_value(der_value)

    print(f"  from string:     {oid_str}")
    print(f"  from components: {oid_comp}")
    print(f"  from_der_value:  {oid_der}")

    assert oid_str == oid_comp == oid_der
    print("  All three are equal: OK")


def demo_components():
    section("components() — tuple of integer arcs")
    oid = synta.ObjectIdentifier("2.5.4.3")  # id-at-commonName
    comps = oid.components()
    print(f"  OID:        {oid}")
    print(f"  components: {comps}")
    assert list(comps) == [2, 5, 4, 3]


def demo_equality():
    section("__eq__ — compare against OID or dotted string")
    rsa_oid = oids.RSA_ENCRYPTION  # ObjectIdentifier("1.2.840.113549.1.1.1")

    # Compare OID to OID
    other = synta.ObjectIdentifier("1.2.840.113549.1.1.1")
    assert rsa_oid == other
    print(f"  {rsa_oid} == ObjectIdentifier(same): True")

    # Compare OID to dotted string
    assert rsa_oid == "1.2.840.113549.1.1.1"
    assert not (rsa_oid == "1.2.840.113549.1.1.11")
    print(f"  {rsa_oid} == '1.2.840.113549.1.1.1': True")
    print(f"  {rsa_oid} == '1.2.840.113549.1.1.11': False")


def demo_hashability():
    section("__hash__ — use OIDs as dict keys")
    alg_names = {
        oids.RSA_ENCRYPTION:  "RSA",
        oids.EC_PUBLIC_KEY:   "EC",
        oids.ED25519:         "Ed25519",
    }
    test_oid = synta.ObjectIdentifier("1.2.840.113549.1.1.1")
    print(f"  alg_names[RSA_ENCRYPTION] = {alg_names[test_oid]!r}")
    assert alg_names[test_oid] == "RSA"

    # hash is consistent with hash(str(oid))
    oid = oids.SHA256
    assert hash(oid) == hash(str(oid))
    print(f"  hash(oid) == hash(str(oid)) for {oid}: OK")


def demo_encode_decode_roundtrip():
    section("encode_oid / decode_oid round-trip")
    oid = synta.ObjectIdentifier("1.3.6.1.5.5.7.48.1.1")  # id-pkix-ocsp-basic

    enc = synta.Encoder(synta.Encoding.DER)
    enc.encode_oid(oid)
    encoded = enc.finish()
    print(f"  DER: {encoded.hex()}")

    dec = synta.Decoder(encoded, synta.Encoding.DER)
    decoded = dec.decode_oid()
    assert decoded == oid
    print(f"  Decoded: {decoded}")
    print("  encode_oid / decode_oid round-trip: OK")


def demo_encode_oid_object():
    section("encode_oid_object — encode an ObjectIdentifier instance")
    oid = oids.ED25519  # 1.3.101.112
    enc = synta.Encoder(synta.Encoding.DER)
    enc.encode_oid_object(oid)
    encoded = enc.finish()

    dec = synta.Decoder(encoded, synta.Encoding.DER)
    decoded = dec.decode_oid()
    assert decoded == oid
    print(f"  encode_oid_object({oid}) → {encoded.hex()}")
    print(f"  Decoded back: {decoded}")
    print("  encode_oid_object round-trip: OK")


def demo_from_der_value_implicit():
    section("from_der_value — decode OID in implicit-tag context")
    # In an implicit context (e.g. [0] IMPLICIT OID), the tag is replaced
    # but the content bytes remain standard OID encoding.
    # Content for id-ce-basicConstraints (2.5.29.19):
    content = bytes.fromhex("551d13")
    oid = synta.ObjectIdentifier.from_der_value(content)
    print(f"  content: {content.hex()}")
    print(f"  OID:     {oid}")
    assert oid == "2.5.29.19"

    # Also handles id-at-commonName (2.5.4.3 → 55 04 03)
    content_cn = bytes.fromhex("550403")
    oid_cn = synta.ObjectIdentifier.from_der_value(content_cn)
    print(f"  id-at-commonName: {oid_cn}")
    assert oid_cn == "2.5.4.3"


def main():
    print("=" * 60)
    print("Example 11: ObjectIdentifier constructors and operations")
    print("=" * 60)
    demo_constructors()
    demo_components()
    demo_equality()
    demo_hashability()
    demo_encode_decode_roundtrip()
    demo_encode_oid_object()
    demo_from_der_value_implicit()
    print("\nAll ObjectIdentifier examples completed.")


if __name__ == "__main__":
    main()
```