Skip to main content

Configurable TLV/LTV codec for payment protocol sub-fields (ISO 8583, acquirer-style)

Project description

pytlv-codec

CI PyPI Python License: MIT

Configurable TLV/LTV codec for payment protocol sub-fields (ISO 8583, acquirer-style).

Why

Most existing TLV libraries in Python target byte-oriented BER/DER (X.690, EMV chip cards). But many real-world payment protocols — especially ISO 8583 sub-fields and Latin American acquirer integrations — use string-based TLV/LTV encoding with configurable conventions:

  • Order: TLV vs LTV
  • Tag/length encoding: ASCII, BCD, HEX, binary
  • LTV variants where length includes the tag

pytlv-codec provides a flexible, configurable codec for these formats.

Status

🚧 Early development (v0.3.1). API may change before 1.0.

Install

pip install pytlv-codec

Quick start (simple ASCII TLV)

from pytlv_codec import Codec, CodecConfig

# Default: TLV order, ASCII everywhere, 2-char tags, 4-char lengths
codec = Codec()

encoded = codec.encode({"05": "12345", "62": "teste"})
# encoded == "05000512345620005teste"

decoded = codec.decode(encoded)
# decoded == {"05": "12345", "62": "teste"}

Real-world example (LTV, BCD, length includes tag)

A real Brazilian acquirer-style sub-field encoding: LTV order, BCD-encoded tag and length, length includes the tag bytes, and value is opaque binary data represented as a hex string.

from pytlv_codec import Codec, CodecConfig, Encoding, Order, ValueType

config = CodecConfig(
    order=Order.LTV,
    tag_size=2,
    tag_encoding=Encoding.BCD,
    length_size=4,
    length_encoding=Encoding.BCD,
    value_type=ValueType.BINARY,
    length_includes_tag=True,
)
codec = Codec(config)

# 74 hex chars = 37 bytes of opaque binary data
opaque_payload = (
    "0544970000010009650840000001000966"
    "00000100096655534431323334350212345678"
    "06"
)

encoded = codec.encode({"33": opaque_payload})
# encoded == "0038" + "33" + opaque_payload (80 chars total = 40 bytes when packed)
# Length is 38 bytes = 1 (tag, BCD) + 37 (value, binary)

decoded = codec.decode(encoded)
# decoded == {"33": opaque_payload}

Schema-driven payload (structured TLV values)

Many real-world TLV values are not opaque blobs — they are concatenations of multiple named subfields, each with its own type (BCD, ASCII, BINARY) and either fixed size or variable length with a length prefix. SubfieldSchema lets you describe the structure once and pack/unpack with named values.

from pytlv_codec import (
    Codec, CodecConfig, Encoding, Order, ValueType,
    SubfieldSchema, Subfield, SubfieldType,
    LengthPrefix, LengthPrefixEncoding,
)

# 1. Describe the structured payload
schema = SubfieldSchema([
    Subfield("acquirer_code", SubfieldType.BCD,    size_bytes=3),   # 3 bytes BCD
    Subfield("merchant_id",   SubfieldType.BCD,    size_bytes=6),
    Subfield("currency_code", SubfieldType.ASCII,  size_bytes=3),   # "USD"
    # variable-length: length prefix is 1 byte BCD (max 99 bytes)
    Subfield(
        "merchant_name",
        SubfieldType.ASCII,
        length_prefix=LengthPrefix(LengthPrefixEncoding.BCD, size_bytes=1),
    ),
])

# 2. Pack natural values into a hex payload string
payload_hex = schema.pack({
    "acquirer_code": "054497",        # BCD digits
    "merchant_id":   "000001000965",
    "currency_code": "USD",            # ASCII text
    "merchant_name": "STORE 01",       # any length up to 99 bytes
})

# 3. Wrap in a TLV envelope (acquirer-style LTV / BCD)
codec = Codec(CodecConfig(
    order=Order.LTV,
    tag_size=2, tag_encoding=Encoding.BCD,
    length_size=4, length_encoding=Encoding.BCD,
    value_type=ValueType.BINARY,
    length_includes_tag=True,
))
encoded = codec.encode({"33": payload_hex})

# 4. Round trip: decode the envelope, then unpack the schema
decoded_envelope = codec.decode(encoded)
fields = schema.unpack(decoded_envelope["33"])
# fields == {"acquirer_code": "054497", "merchant_id": "...", "currency_code": "USD", "merchant_name": "STORE 01"}

Configuration reference

from pytlv_codec import CodecConfig, Encoding, Order, ValueType

CodecConfig(
    order=Order.TLV,                          # Order.TLV | Order.LTV
    tag_size=2,                               # logical units (chars/digits)
    tag_encoding=Encoding.ASCII,              # ASCII | BCD | HEX | BINARY
    length_size=4,
    length_encoding=Encoding.ASCII,
    value_type=ValueType.ASCII,               # ASCII | BCD | HEX | BINARY
    length_includes_tag=False,                # LTV: length covers tag + value
    pad_char="0",
    allow_empty_value=True,
    allow_duplicate_tags=False,
    big_endian=True,
)

Length is always counted as bytes-on-wire (after binary serialization downstream).

How encodings affect bytes-on-wire

Encoding Bytes per logical unit
ASCII 1 char = 1 byte
BCD 2 digits packed in 1 byte
HEX 2 hex chars in 1 byte
BINARY 2 hex chars in 1 byte

For BCD/HEX/BINARY, the codec works with the hex string representation of the underlying bytes. Downstream conversion to actual binary is the responsibility of the caller (or a dedicated library like pyiso8583).

Custom exceptions

from pytlv_codec import (
    PytlvError,           # base
    EncodingError,        # invalid input on encode
    InvalidTLVError,      # malformed stream on decode
    UnsupportedConfigError,  # config combination not implemented
)

All custom exceptions also inherit from ValueError / NotImplementedError so existing generic handlers continue to work.

Development

git clone https://github.com/jacobdarrossi/pytlv-codec.git
cd pytlv-codec
pip install -e ".[dev]"
pytest

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytlv_codec-0.3.1.tar.gz (16.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pytlv_codec-0.3.1-py3-none-any.whl (11.6 kB view details)

Uploaded Python 3

File details

Details for the file pytlv_codec-0.3.1.tar.gz.

File metadata

  • Download URL: pytlv_codec-0.3.1.tar.gz
  • Upload date:
  • Size: 16.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pytlv_codec-0.3.1.tar.gz
Algorithm Hash digest
SHA256 069a0ec0c3a85bf7902a0f75ef3a61d653c11820bf7d8ac45af6f34a574f58a8
MD5 66e8d2e01f390491c6d95f837e0f904e
BLAKE2b-256 747ac2c6b3538e0b29217ea672e36ebc73b885b9bd24a998afe5c7e0a687aabd

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytlv_codec-0.3.1.tar.gz:

Publisher: publish.yml on jacobdarrossi/pytlv-codec

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pytlv_codec-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: pytlv_codec-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 11.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pytlv_codec-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a066eccdd74af9d4b9b282d7ceeb01506480577a1f3e772750bb809090716aec
MD5 b33a41d000d03bb79afe08fae45eef6f
BLAKE2b-256 a3a4925f91e6cfdeae3d953c9b24832006af707e2f0d05143672652e7237b657

See more details on using hashes here.

Provenance

The following attestation bundles were made for pytlv_codec-0.3.1-py3-none-any.whl:

Publisher: publish.yml on jacobdarrossi/pytlv-codec

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page