slang

encoding

Package encoding.

Markdown version · llms.txt · api.json

Hex, base64, base64url, base32, percent-encoding and query strings — the ways arbitrary bytes travel through a channel that only carries text. The package also converts binary64 floats to and from little-endian bytes for binary protocols such as BSON.

import "encoding";

encoding.hex_encode(crypto.sha256(b"abc"));   // "ba7816bf8f01cfea..."
encoding.base64_encode(b"aladdin:opensesame");// HTTP Basic credentials
encoding.base64url_encode(sig);               // a JWT segment: -_ alphabet, no padding
encoding.url_encode("a b&c");                 // "a%20b%26c"
encoding.form_encode("a b&c");                // "a+b%26c"
encoding.float64_to_le(1.5);                   // exact IEEE-754 bytes, low byte first
encoding.json_tape("{\"a\":[1,2]}");        // ok([5,5,1,0,11, 7,2,0,2,3, 4,5,2,5,10, ...]): see below
encoding.xor_mask(head, payload, key);         // head ++ (payload XOR key, repeated): a WebSocket frame
encoding.base32_encode(secret);               // "JBSWY3DPEHPK3PXP" -- a TOTP secret
encoding.base32_decode("jbswy3dpehpk3pxp");   // either case, padded or not
encoding.crockford_encode(id_bytes);          // "7ZQ4M1X0" -- a code people read aloud
encoding.crockford_decode("7zq-4ml-xo");      // hyphens skipped, l read as 1, o as 0

let q = "/search?q=hello+world&page=2";
guard let term = encoding.query_get(q, "q") else { return; }   // "hello world"
encoding.query_keys(q);                                        // ["q", "page"]
FunctionSignature
encoding.hex_encode(b: bytes) -> str
encoding.hex_decode(s: str) -> result[bytes, str]
encoding.base64_encode(b: bytes) -> str
encoding.base64_decode(s: str) -> result[bytes, str]
encoding.base64url_encode(b: bytes) -> str
encoding.base64url_decode(s: str) -> result[bytes, str]
encoding.url_encode(s: str) -> str
encoding.url_decode(s: str) -> result[str, str]
encoding.form_encode(s: str) -> str
encoding.form_decode(s: str) -> result[str, str]
encoding.query_get(url: str, key: str) -> opt[str]
encoding.query_keys(url: str) -> [str]
encoding.float64_to_le(value: float) -> bytes
encoding.float64_from_le(bytes: bytes) -> result[float, str]
encoding.json_tape(text: str) -> result[[int], str]
encoding.xor_mask(head: bytes, data: bytes, key: bytes) -> bytes
encoding.base32_encode(b: bytes) -> str
encoding.base32_decode(s: str) -> result[bytes, str]
encoding.base32hex_encode(b: bytes) -> str
encoding.base32hex_decode(s: str) -> result[bytes, str]
encoding.crockford_encode(b: bytes) -> str
encoding.crockford_decode(s: str) -> result[bytes, str]
encoding.csv_record(data: bytes, from: int, sep: int, quote: int, flags: int) -> [int]

Like regex, strings and os — and unlike crypto and sql — this is pure computation, so importing it adds no link flag.

Five things worth knowing:

  • Encoders are infallible; decoders are not. Any byte string has a hex form, so hex_encode returns a bare str. Decoding takes input the program did not produce — a query string, a header, a token — so every decoder returns result[_, str], and the message names the byte offset it gave up at. "invalid base64" about a 400-character token is not a diagnosis.
  • url_* and form_* differ only in +, and that is exactly why they have separate names. In application/x-www-form-urlencoded a space is +; in a URI it is %20. Decoding a form body with url_decode leaves literal + where every space belongs, and nothing reports it — the failure surfaces later as a lookup that does not match. One function with a flag would make that the default mistake.
  • %00 is an error, not a truncation. url_decode and form_decode return str, which is NUL-terminated, so a decoded zero byte would silently cut the value short. They refuse it and say so. hex_decode and base64_decode return bytes, which carries an explicit length, so a zero byte there is ordinary data and round-trips exactly.
  • Float bytes are exact and little-endian. float64_to_le and float64_from_le preserve the IEEE-754 binary64 bit pattern, including signed zero and NaN payload bits. float64_from_le accepts exactly eight bytes and returns an error for any other length.
  • query_get is opt, and query_keys is a list. A missing parameter is absent data, not bad data, so it is opt[str] — the README rule above. Keys come back as a list rather than a map because a query may legally repeat a key and a map would have to drop one, the same reason os.environ is a list. query_get takes the first value; a bare ?debug is present with an empty value, not absent.
  • json_tape is where things are, not a copy of them. It validates a JSON document (RFC 8259, nesting up to 512) in one C pass and returns five ints per value, in document order: kind (0 null, 1 bool, 2 number, 3 string, 4 list, 5 object, 7 key; plus 8 for a string or key holding an escape), the entry after the value's subtree, its child count, and its byte span (a string's inside, between the quotes). A reader walks it, skips a subtree in one step, and materialises only the strings it reads (strings.from_bytes over the span). template reads its data this way; use json.decode when the shape is known.
  • base32 decodes leniently and strictly at once. base32_decode takes either case, with or without = padding, because TOTP secrets arrive in every combination. It refuses a tail of 1, 3 or 6 characters, which holds no whole byte, and a tail whose leftover bits are not zero. That input is a mistyped or truncated secret, and decoding it anyway would yield a different key that fails later and less clearly. Python's decoder accepts MZ======; this one does not. base32hex (RFC 4648 §7) sorts in the same order as the bytes it encodes. Crockford's alphabet leaves out I, L, O and U so a code read aloud cannot be misheard; its decoder reads I and L as 1 and O as 0, and skips hyphens. Strip spaces from a secret shown in groups before decoding it.

Percent-escapes are emitted uppercase (RFC 3986 §2.1) and hex digests lowercase (what sha256sum, git and every API that returns one use). Both decoders accept either case.

API#

encoding.hex_encode(bytes) -> str#
encoding.hex_decode(str) -> result[bytes,str]#
encoding.base64_encode(bytes) -> str#
encoding.base64_decode(str) -> result[bytes,str]#
encoding.base64url_encode(bytes) -> str#
encoding.base64url_decode(str) -> result[bytes,str]#
encoding.url_encode(str) -> str#
encoding.url_decode(str) -> result[str,str]#
encoding.form_encode(str) -> str#
encoding.form_decode(str) -> result[str,str]#
encoding.query_get(str, str) -> opt[str]#
encoding.query_keys(str) -> [str]#
encoding.float64_to_le(float) -> bytes#
encoding.float64_from_le(bytes) -> result[float,str]#
encoding.json_tape(str) -> result[[int],str]#
encoding.xor_mask(bytes, bytes, bytes) -> bytes#
encoding.base32_encode(bytes) -> str#
encoding.base32_decode(str) -> result[bytes,str]#
encoding.base32hex_encode(bytes) -> str#
encoding.base32hex_decode(str) -> result[bytes,str]#
encoding.crockford_encode(bytes) -> str#
encoding.crockford_decode(str) -> result[bytes,str]#
encoding.csv_record(bytes, int, int, int, int) -> [int]#