Base32
RFC 4648 base32 encode/decode. Pure Kotlin (no platform codecs), so it works in commonMain across every target.
Port of libtorrent's base32decode / base32encode_i2p (src/escape_string.cpp). In BitTorrent, base32 shows up in two places:
v1 magnet links historically allowed the 20-byte info-hash to be encoded as a 32-character base32 string ("xt=urn:btih:" followed by base32), and v2 magnet links / well-known network identifiers also use base32.
i2p addresses use a non-standard lowercase variant of base32.
Two important fidelity notes about the libtorrent source this is ported from:
libtorrent's encode equivalent only exists as
base32encode_i2p, which uses the lowercase alphabeta-z2-7and emits no padding (i2p convention). RFC 4648, which is what v2 magnet links and most of the world expect, uses the uppercase alphabetA-Z2-7and pads the final group to a multiple of 8 characters with=. This object implements the RFC 4648 behaviour (uppercase + padding). It reuses the exact same bit-packing asbase32encode_i2p; only the alphabet table and the trailing=padding differ. See encodeI2p for the verbatim i2p behaviour.libtorrent's
base32decodeis already standard-alphabet (A-Z2-7) and is deliberately lenient: it upper-cases input first (so lowercase decodes too), and it maps the ambiguous digit'1'to the letter'I'. Both quirks are preserved here. On any other unexpected character it returns an empty result, exactly like the C++ (which returnsstd::string()); decodeOrNull surfaces that asnullwhile decode returns an empty array, matching the C++ return.
Functions
Like decode, but returns null (instead of an empty array) when the input contains a character outside the base32 alphabet, so callers can distinguish "decoded to nothing" from "malformed".