iyi

Standard library, 0.18.0

std/base32

Base32, RFC 4648 section 6, and Base32hex, section 7, written in iyi.

import std/base32 to use it. Source: src/std/base32.iyi at v0.18.0.

  import std/base32::{Base32, Base32Hex, Base32Error}
  Base32.encode("foobar")                  # "MZXW6YTBOI======"
  Base32.encode("foobar", padding: false)  # "MZXW6YTBOI"
  Base32.decode("mzxw6ytboi")              # the six bytes, or a Base32Error
  Base32Hex.encode("foobar")               # "CPNMUOJ1E8======"

What is encoded is a String, whose bytes are taken, or an Array(UInt8); what is decoded is an Array(UInt8), because a base32 text is most often a key (an otpauth:// secret, a DNSSEC NSEC3 hash in Base32hex), and a key is bytes and seldom UTF-8.

encode writes the upper-case alphabet, padded with = to a multiple of eight characters unless padding: false is asked for (section 3.2 lets a specification leave it out, and the Key URI format TOTP secrets are handed around in does). decode reads either case, and padding is optional, but where there is any it must be the whole of it: the text then a multiple of eight characters long and the last group closed by exactly the = its data characters leave over. What is not base32 is a Base32Error naming what and where, never bytes made up or dropped:

* a byte outside the alphabet (section 3.3: "MUST reject"), and that
  includes whitespace and line breaks, which this does not skip;
* data after the padding;
* padding that is too short, too long, or closes no group;
* a last group of 1, 3 or 6 characters, which ends on no whole byte;
* a last character that sets bits past the data (section 3.5): "MB"
  and "MA" would both be "`f`" if those bits were ignored, and a
  decoder that ignores them lets one byte string have many encodings.
  The sentence names the canonical character.

Python's base64.b32decode agrees with the first four (with casefold=True for lower case, and the text padded) and ignores the fifth; the gate holds that line exactly.

A character at a time each way: the texts this is for are tens of bytes, and the word-at-a-time loops std/base64 needed for megabytes would be code nothing here measures.

Pure computation: no platform, no clock, no randomness.

Base32struct

RFC 4648 section 6, the alphabet authenticator secrets are written in.

std/base32iyi doc src/std/base32.iyi
pub struct Base32
  def self.decode(text : String) : Array(UInt8) | Base32Error
  def self.encode(data : String | Array(UInt8), padding : Bool = true) : String
  def initialize
end

Base32Errorstruct

What decode answers for a text that is not base32.

std/base32iyi doc src/std/base32.iyi
pub struct Base32Error
  def initialize(message : String)
  def to_s : String
end

Base32Hexstruct

RFC 4648 section 7, the extended-hex alphabet: what DNSSEC's NSEC3 records (RFC 5155) write their hashed names in.

std/base32iyi doc src/std/base32.iyi
pub struct Base32Hex
  def self.decode(text : String) : Array(UInt8) | Base32Error
  def self.encode(data : String | Array(UInt8), padding : Bool = true) : String
  def initialize
end

Error for Base32Errorimpl

std/base32iyi doc src/std/base32.iyi
impl Error for Base32Error
  def message : String
end