Packages

Pure Elixir telecom encodings for Iconvex

Current section

Files

Jump to
Raw

README.md

# Iconvex Telecom
Pure Elixir telecom encodings for [Iconvex](https://hex.pm/packages/iconvex), supplied through its
external-codec API. Its 61 named codecs include GSM 03.38 / 3GPP TS 23.038
with every national locking and single-shift table; ITA1, ITA2, ITA2-S2,
US-TTY, MTK-2, ITA3, ITA4, IA5/T.50, International Morse, CCIR 476, AIS6, and
the exact ARIB STD-B24 v6.4-E1 Shift-JIS broadcast profile, and two
source-qualified IBM Six-Bit Transcode tables; plus 3GPP TBCD and SIM/USIM
alpha identifiers. The archival set adds Murray's 1905 five-hole tape,
Hausmann's 1915 Wheatstone action tape, three Unicode 17 Japanese carrier emoji
overlays, and the U.S. Army POW tap-count matrix. A unified packed facade covers
all 53 fixed-width 3-, 5-, 6-, and 7-bit profiles in both MSB and LSB order;
family helpers retain CCIR FEC polarity and AIS AIVDM/AIVDO payload armoring.
The registered codecs use the unpacked representation used by SMPP: every GSM
septet occupies one octet and its high bit must be zero. TPDU bit-packing is a
separate explicit API because decoding needs a septet count and UDH alignment.
## Installation
Add both packages to `mix.exs`:
```elixir
def deps do
[
{:iconvex, "~> 0.1.1"},
{:iconvex_telecom, "~> 0.1.1"}
]
end
```
Starting `:iconvex_telecom` registers all 61 named codecs automatically.
They are installed as one atomic managed set whose cleanup token survives a
supervised Iconvex registry-worker restart. Telecom, Extras, Unicode, Standards,
Bibliographic, Retro, Platforms, current ICU, and the opt-in ICU Archive can
start in any order; the complete registry has 2,105 unique canonical names.
The generated full-stack codec comparison at `../ICONVEX_FULL_STACK_SUPPORT.md`
is workspace-level integration evidence for all 2,105 canonical names and GNU
libiconv 1.19. It is intentionally not included in this package artifact; use
the workspace-relative path only from the sibling-package source checkout.
The exact package-ownership authority—six registered codecs plus two packed
profiles migrated in this release—is shipped as
[`SURFACE_MANIFEST.tsv`](SURFACE_MANIFEST.tsv).
```elixir
Iconvex.convert("€Ğ", "UTF-8", "GSM0338-TURKISH")
# => {:ok, <<0x04, 0x1B, 0x47>>}
Iconvex.convert(<<0x04, 0x1B, 0x47>>, "GSM0338-TURKISH", "UTF-8")
# => {:ok, "€Ğ"}
Iconvex.convert("HELLO 123", "UTF-8", "ITA2")
# => {:ok, <<20, 1, 18, 18, 24, 4, 27, 23, 19, 1>>}
Iconvex.convert("HELLO 123", "UTF-8", "CCIR476")
# => {:ok, <<0x4B, 0x35, 0x53, 0x53, 0x47, 0x1D, 0x36, 0x3A, 0x72, 0x35>>}
Iconvex.convert("AIS 123?", "UTF-8", "AIS6")
# => {:ok, <<1, 9, 19, 32, 49, 50, 51, 63>>}
Iconvex.convert("ABCd", "UTF-8", "ITA2-S2")
# => {:ok, <<27, 31, 3, 25, 14, 31, 9>>}
Iconvex.convert("ABCD", "UTF-8", "IBM-2780-SIX-BIT-TRANSCODE-GA27-3005-3")
# => {:ok, <<1, 2, 3, 4>>}
Iconvex.convert("あ⛌", "UTF-8", "ARIB-STD-B24-SHIFT-JIS-6.4-E1")
# => {:ok, <<0x82, 0xA0, 0xED, 0x9F>>}
Iconvex.convert(<<0x82, 0xA0, 0xED, 0x9F>>, "ARIB-STD-B24-SHIFT-JIS-6.4-E1", "UTF-8")
# => {:ok, "あ⛌"}
Iconvex.convert("12345", "UTF-8", "TBCD")
# => {:ok, <<0x21, 0x43, 0xF5>>}
Iconvex.convert(<<0x81, 2, 0x08, 0x41, 0x90>>, "SIM-ALPHA", "UTF-8")
# => {:ok, "AА"}
Iconvex.convert("1A", "UTF-8", "MURRAY-1905-FIGURE-7-TAPE")
# => {:ok, <<27, 29, 31, 24>>}
Iconvex.convert(". -", "UTF-8", "WHEATSTONE-1915-HAUSMANN-THREE-KEY-ACTION-TAPE")
# => {:ok, <<0b111, 0b010, 0b110, 0b011>>}
Iconvex.convert("☀", "UTF-8", "UNICODE-17.0.0-EMOJISOURCES-DOCOMO-SHIFT-JIS")
# => {:ok, <<0xF8, 0x9F>>}
Iconvex.convert("CK", "UTF-8", "US-ARMY-GTA-31-70-001-TAP-CODE-PAIR-VALUES")
# => {:ok, <<1, 3, 1, 3>>}
```
The long canonical names are deliberate provenance boundaries. Murray is not
registered as generic `MURRAY` or `BAUDOT`; the Wheatstone profile represents
physical perforator actions rather than framed Morse characters; and the Army
profile serializes row/column tap counts as two numeric octets without claiming
every Tap Code variant.
The three carrier profiles overlay the exact DoCoMo, KDDI, or SoftBank
`EmojiSources.txt` byte pairs on CP932. Carrier bytes take precedence over the
displaced CP932 PUA mapping, while all unaffected CP932 mappings and canonical
inverses remain unchanged. See
[UNICODE17_CARRIER_EMOJI.md](UNICODE17_CARRIER_EMOJI.md).
SIM alpha identifiers use a framing-aware incremental decoder. An invalid
UCS-2 unit can be discarded or replaced without losing the leading `0x80`,
`0x81`, or `0x82` mode, reinterpreting the suffix as GSM, or changing callback
position. Conversely, once an invalid first default-alphabet octet is consumed,
recovery commits to GSM mode and never reinterprets later `0x80`/`0x81`/`0x82`
data as a framing header. The same result is guaranteed for one-shot conversion
and every lazy Stream split. Earlier invalid UCS-2 units take precedence over a
later odd tail. A short `0x81`/`0x82` payload reports its empty sequence at physical EOF;
plain discard retains any valid payload prefix without inventing a callback for
the absent byte. Physically present compressed payload errors take precedence
over that later EOF, and a trailing GSM escape reports its own byte when more
payload was declared. In accordance with the 255-octet linear-fixed-record
limit in ETSI TS 102 221 section 8.2, both helper and codec APIs reject a 256th
physical record byte. Streaming state counts consumed octets separately from
pending bytes and guarantees that their sum never exceeds 255; target state is
likewise capped. `SIMAlphaIdentifier.max_bytes/0` and
`SIMAlphaIdentifierCodec.max_bytes/0` expose the limit.
## Independent national tables
3GPP permits the locking and single-shift tables to name different languages.
The low-level API exposes every one of the 182 valid pairings:
```elixir
alias Iconvex.Telecom.GSM0338
options = [locking: :portuguese, single_shift: :bengali]
GSM0338.decode(<<0x04, 0x1B, 0x19>>, options)
# => {:ok, [0x00EA, 0x09E6]}
GSM0338.encode([0x00EA, 0x09E6], options)
# => {:ok, <<0x04, 0x1B, 0x19>>}
GSM0338.valid_pairs() |> length()
# => 182
```
Locking choices are `:default`, `:turkish`, `:portuguese`, `:bengali`,
`:gujarati`, `:hindi`, `:kannada`, `:malayalam`, `:oriya`, `:punjabi`,
`:tamil`, `:telugu`, and `:urdu`.
Single-shift choices contain the same set plus `:spanish`. Spanish has no
locking table in 3GPP, so `GSM0338-SPANISH` combines the default locking table
with the Spanish single-shift table.
`decode_utf8/2` and `encode_utf8/2` are the fastest direct paths. Ordinary
`Iconvex.convert/4` calls equivalent codec fast paths automatically.
See [SUPPORTED_ENCODINGS.md](SUPPORTED_ENCODINGS.md),
[SUPPORTED_CODEC_INVENTORY.csv](SUPPORTED_CODEC_INVENTORY.csv),
[CONFORMANCE.md](CONFORMANCE.md), and [BENCHMARKS.md](BENCHMARKS.md).
## Telecom transports
```elixir
alias Iconvex.Telecom.{AIS6, GSM0338.Packing, ITA2, SIMAlphaIdentifier, TBCD}
Packing.pack("hellohello")
# => {:ok, <<0xE8, 0x32, 0x9B, 0xFD, 0x46, 0x97, 0xD9, 0xEC, 0x37>>}
TBCD.encode("12345")
# => {:ok, <<0x21, 0x43, 0xF5>>}
SIMAlphaIdentifier.encode("AА")
# => {:ok, <<0x81, 2, 0x08, 0x41, 0x90>>}
{:ok, ita2_units} = ITA2.encode(~c"HELLO")
ITA2.Packing.pack(ita2_units)
# => {:ok, <<20::5, 1::5, 18::5, 18::5, 24::5>>}
{:ok, ais_units} = AIS6.encode(~c"AIS 123?")
AIS6.Armor.encode(ais_units)
# => {:ok, "19CPijkw"}
{:ok, gsm_packed} =
Iconvex.Telecom.Packed.encode_from_utf8("hellohello", "GSM0338")
Base.encode16(gsm_packed.data)
# => "E8329BFD4697D9EC37"
Iconvex.Telecom.Packed.decode_to_utf8(gsm_packed, "GSM0338")
# => {:ok, "hellohello"}
```
`Iconvex.Telecom.Packed.profiles/0` is the exact runtime list. Every profile
accepts `:standard`, `:msb`, or `:lsb`; the standard order is explicit in
`SUPPORTED_PACKED_CODEC_INVENTORY.csv`. The facade caches immutable metadata in
`persistent_term`, so hot calls do not rebuild the 53-profile catalog. IBM
Transcode defaults to its documented low-order-first packed transport; callers
can request the explicitly named MSB form through the same facade. Canonical and
alias names ending in `-PACKED-MSB` or `-PACKED-LSB` select that order when the
order argument is omitted; an explicitly conflicting order returns
`{:error, :bit_order_mismatch}`. LSB transports with a non-LSB `bit_order` tag
are rejected with the same typed error. Semantic decode failures from packed
input report physical bit offsets, not offsets in the temporary unpacked byte
buffer. MSB fragments retain their exact bit width; LSB fragments use the
failing unit integer, matching the core packed transport contracts.
## License
Original library code: `LGPL-2.1-or-later`, matching GNU libiconv libraries.
Imported Android/Apache and Unicode fixtures retain upstream licenses.
Public-domain historical publications and U.S. government source evidence keep
their attribution and package-boundary notes; see `LICENSE.APACHE-2.0`,
`LICENSE.UNICODE`, `SOURCE_PROVENANCE.md`, and `NOTICE`.