# encoding > Package encoding. 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. ```slang 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"] ``` | Function | Signature | |---|---| | `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]` --- slang is built and maintained by **Dolphlabs** (Dolph Tech Limited) — https://dolphlabs.com