# 17. `example_decoder_advanced.py` — Advanced Decoder operations
[← Example index](index.md) · [example_decoder_advanced.py on Codeberg](https://codeberg.org/abbra/synta/src/branch/main/examples/example_decoder_advanced.py)
Bindings: `Decoder.decode_set`, `Decoder.peek_tag`, `Decoder.decode_raw_tlv`,
`Decoder.remaining_bytes`, `Decoder.decode_implicit_tag`, `Decoder.position`,
`Decoder.remaining`, `Decoder.is_empty`, `Decoder.decode_any_str`.
- Build a hand-crafted SET and decode it with `decode_set`.
- Use `peek_tag` in a loop to drive CHOICE dispatch.
- Capture unknown elements with `decode_raw_tlv`.
- Use `decode_implicit_tag` + `remaining_bytes` to decode a primitive implicit type
(e.g. `[2] IMPLICIT IA5String` for a dNSName GeneralName).
- Show `position()` and `remaining()` advancing through multi-element input.
- Use `decode_any_str()` to decode a mixed-string SEQUENCE without tag inspection.
## Source
```python
#!/usr/bin/env python3
"""
Example 16: Advanced Decoder operations.
Demonstrates: Decoder.decode_set, Decoder.peek_tag, Decoder.decode_raw_tlv,
Decoder.remaining_bytes, Decoder.decode_implicit_tag, Decoder.position,
Decoder.remaining, Decoder.is_empty, Decoder.decode_any_str.
"""
import synta
def section(title):
print(f"\n{'─' * 60}\n{title}\n{'─' * 60}")
def demo_decode_set():
section("decode_set — decode a SET and iterate elements")
# Build a SET containing two PrintableStrings
inner = synta.Encoder(synta.Encoding.DER)
inner.encode_printable_string("Alice")
inner.encode_printable_string("Bob")
inner_bytes = inner.finish()
outer = synta.Encoder(synta.Encoding.DER)
outer.encode_set(inner_bytes)
data = outer.finish()
print(f" SET DER: {data.hex()}")
assert data[0] == 0x31 # SET tag
dec = synta.Decoder(data, synta.Encoding.DER)
child = dec.decode_set()
assert dec.is_empty()
s1 = child.decode_printable_string()
s2 = child.decode_printable_string()
assert child.is_empty()
print(f" Decoded: {s1.as_str()!r}, {s2.as_str()!r}")
def demo_peek_tag():
section("peek_tag — CHOICE dispatch without consuming")
# Encode INTEGER then BOOLEAN back to back
enc = synta.Encoder(synta.Encoding.DER)
enc.encode_integer(99)
enc.encode_boolean(True)
data = enc.finish()
dec = synta.Decoder(data, synta.Encoding.DER)
# First element: INTEGER (tag 2)
tag_no, tag_class, is_constructed = dec.peek_tag()
print(f" peek → ({tag_no}, {tag_class!r}, {is_constructed})")
assert tag_no == 2 and tag_class == "Universal"
# peek_tag does NOT advance the position
pos_before = dec.position()
dec.peek_tag()
assert dec.position() == pos_before
print(" peek_tag is non-consuming: OK")
val = dec.decode_integer()
assert val.to_int() == 99
# Second element: BOOLEAN (tag 1)
tag_no, _, _ = dec.peek_tag()
assert tag_no == 1
val2 = dec.decode_boolean()
assert val2.value() is True
assert dec.is_empty()
print(" CHOICE dispatch via peek_tag: OK")
def demo_choice_dispatch_loop():
section("peek_tag in a loop — parse heterogeneous SEQUENCE")
# Build SEQUENCE { INTEGER, BOOLEAN, UTF8String }
inner = synta.Encoder(synta.Encoding.DER)
inner.encode_integer(7)
inner.encode_boolean(False)
inner.encode_utf8_string("hello")
outer = synta.Encoder(synta.Encoding.DER)
outer.encode_sequence(inner.finish())
data = outer.finish()
dec = synta.Decoder(data, synta.Encoding.DER)
seq = dec.decode_sequence()
results = []
while not seq.is_empty():
tag_no, _, _ = seq.peek_tag()
if tag_no == 2: # INTEGER
results.append(seq.decode_integer().to_int())
elif tag_no == 1: # BOOLEAN
results.append(seq.decode_boolean().value())
elif tag_no == 12: # UTF8String
results.append(seq.decode_utf8_string().as_str())
else:
seq.decode_raw_tlv() # skip unknown
print(f" Parsed: {results}")
assert results == [7, False, "hello"]
def demo_decode_raw_tlv():
section("decode_raw_tlv — capture an element as raw TLV bytes")
enc = synta.Encoder(synta.Encoding.DER)
enc.encode_integer(42)
enc.encode_boolean(True)
data = enc.finish()
dec = synta.Decoder(data, synta.Encoding.DER)
# Capture first element (INTEGER 42) without parsing its type
raw = dec.decode_raw_tlv()
print(f" Captured TLV: {raw.hex()} (tag 0x{raw[0]:02x} = INTEGER)")
assert raw == b'\x02\x01\x2a'
# Re-parse the captured bytes
dec2 = synta.Decoder(raw, synta.Encoding.DER)
val = dec2.decode_integer()
assert val.to_int() == 42
print(" Re-parsed TLV: OK")
# Remaining byte: BOOLEAN
assert not dec.is_empty()
b = dec.decode_boolean()
assert b.value() is True
assert dec.is_empty()
def demo_decode_implicit_tag():
section("decode_implicit_tag + remaining_bytes — primitive implicit type")
# GeneralName dNSName [2] IMPLICIT IA5String "synta.example.com"
name = b"synta.example.com"
tlv = bytes([0x82, len(name)]) + name # 0x82 = Context[2] primitive
dec = synta.Decoder(tlv, synta.Encoding.DER)
child = dec.decode_implicit_tag(2, "Context")
assert dec.is_empty()
# remaining_bytes() returns value bytes without advancing position
raw = child.remaining_bytes()
assert raw == name
assert child.remaining() == len(name) # position unchanged
# Can also call remaining_bytes again (non-consuming)
raw2 = child.remaining_bytes()
assert raw2 == raw
print(f" dNSName: {raw.decode('ascii')!r}")
print(f" remaining_bytes() non-consuming: OK")
def demo_position_and_remaining():
section("position() and remaining() — track decoder progress")
enc = synta.Encoder(synta.Encoding.DER)
enc.encode_integer(1)
enc.encode_integer(2)
enc.encode_integer(3)
data = enc.finish()
print(f" Data: {data.hex()} ({len(data)} bytes)")
dec = synta.Decoder(data, synta.Encoding.DER)
assert dec.position() == 0
assert dec.remaining() == len(data)
print(f" Start: position={dec.position()} remaining={dec.remaining()}")
dec.decode_integer() # consume INTEGER 1 (3 bytes)
print(f" After 1st: position={dec.position()} remaining={dec.remaining()}")
assert dec.position() == 3
dec.decode_integer() # consume INTEGER 2
print(f" After 2nd: position={dec.position()} remaining={dec.remaining()}")
dec.decode_integer() # consume INTEGER 3
assert dec.is_empty()
assert dec.remaining() == 0
print(f" After 3rd: position={dec.position()} remaining={dec.remaining()} is_empty={dec.is_empty()}")
def demo_remaining_bytes_toplevel():
section("remaining_bytes() at top level — bytes left after partial decode")
enc = synta.Encoder(synta.Encoding.DER)
enc.encode_integer(10)
enc.encode_integer(20)
data = enc.finish()
dec = synta.Decoder(data, synta.Encoding.DER)
dec.decode_integer() # consume first INTEGER
# remaining_bytes returns the rest of the input intact (with TLV headers)
rest = dec.remaining_bytes()
print(f" Remaining after consuming first INTEGER: {rest.hex()}")
# The rest is the DER for INTEGER 20
dec2 = synta.Decoder(rest, synta.Encoding.DER)
assert dec2.decode_integer().to_int() == 20
print(" remaining_bytes() returned valid TLV for second INTEGER: OK")
def demo_decode_any_str():
section("decode_any_str — decode any ASN.1 string type as Python str")
# Build a SEQUENCE holding three different string types: UTF8String,
# PrintableString, and TeletexString (Latin-1 encoded).
inner = synta.Encoder(synta.Encoding.DER)
inner.encode_utf8_string("café") # UTF8String (tag 12)
inner.encode_printable_string("Hello") # PrintableString (tag 19)
inner.encode_teletex_string(b"caf\xe9") # TeletexString (tag 20) — Latin-1 bytes
outer = synta.Encoder(synta.Encoding.DER)
outer.encode_sequence(inner.finish())
data = outer.finish()
dec = synta.Decoder(data, synta.Encoding.DER)
seq = dec.decode_sequence()
# decode_any_str() handles all nine string types without caller needing
# to inspect the tag or choose among decode_utf8_string / decode_teletex etc.
s1 = seq.decode_any_str()
s2 = seq.decode_any_str()
s3 = seq.decode_any_str()
assert seq.is_empty()
print(f" UTF8String: {s1!r}")
print(f" PrintableString:{s2!r}")
print(f" TeletexString: {s3!r}")
assert s1 == "café"
assert s2 == "Hello"
assert s3 == "café" # Latin-1 b"caf\xe9" → U+00E9 = é
# EOFError when decoder is exhausted
try:
seq.decode_any_str()
assert False, "expected EOFError"
except EOFError:
print(" EOFError on empty decoder: OK")
# ValueError for non-string tag (INTEGER)
enc2 = synta.Encoder(synta.Encoding.DER)
enc2.encode_integer(1)
dec2 = synta.Decoder(enc2.finish(), synta.Encoding.DER)
try:
dec2.decode_any_str()
assert False, "expected ValueError"
except ValueError as e:
print(f" ValueError for INTEGER: {e}")
def main():
print("=" * 60)
print("Example 16: Advanced Decoder operations")
print("=" * 60)
demo_decode_set()
demo_peek_tag()
demo_choice_dispatch_loop()
demo_decode_raw_tlv()
demo_decode_implicit_tag()
demo_position_and_remaining()
demo_remaining_bytes_toplevel()
demo_decode_any_str()
print("\nAll advanced Decoder examples completed.")
if __name__ == "__main__":
main()
```