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.
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_encodereturns a barestr. Decoding takes input the program did not produce — a query string, a header, a token — so every decoder returnsresult[_, str], and the message names the byte offset it gave up at. "invalid base64" about a 400-character token is not a diagnosis. url_*andform_*differ only in+, and that is exactly why they have separate names. Inapplication/x-www-form-urlencodeda space is+; in a URI it is%20. Decoding a form body withurl_decodeleaves 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.%00is an error, not a truncation.url_decodeandform_decodereturnstr, which is NUL-terminated, so a decoded zero byte would silently cut the value short. They refuse it and say so.hex_decodeandbase64_decodereturnbytes, 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_leandfloat64_from_lepreserve the IEEE-754 binary64 bit pattern, including signed zero and NaN payload bits.float64_from_leaccepts exactly eight bytes and returns an error for any other length. query_getisopt, andquery_keysis a list. A missing parameter is absent data, not bad data, so it isopt[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 reasonos.environis a list.query_gettakes the first value; a bare?debugis present with an empty value, not absent.json_tapeis 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_bytesover the span).templatereads its data this way; usejson.decodewhen the shape is known.- base32 decodes leniently and strictly at once.
base32_decodetakes 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 acceptsMZ======; 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]#