Iconvex Telecom

Pure Elixir telecom encodings for 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:

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.

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.

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:

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_CODEC_INVENTORY.csv, CONFORMANCE.md, and BENCHMARKS.md.

Telecom transports

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.