defmodule Base32H do @digits [ '0Oo', '1Ii', '2', '3', '4', '5Ss', '6', '7', '8', '9', 'Aa', 'Bb', 'Cc', 'Dd', 'Ee', 'Ff', 'Gg', 'Hh', 'Jj', 'Kk', 'Ll', 'Mm', 'Nn', 'Pp', 'Qq', 'Rr', 'Tt', 'VvUu', 'Ww', 'Xx', 'Yy', 'Zz' ] @encode_map @digits |> Enum.with_index() |> Enum.map(fn {[c | _], n} -> {n, c} end) |> Enum.into(%{}) @decode_map @digits |> Enum.with_index() |> Enum.map(fn {chars, n} -> chars |> Enum.map(fn c -> {c, n} end) end) |> List.flatten() |> Enum.into(%{}) @doc ~S""" Encodes the given integer. iex> Base32H.encode(17_854_910) "H0WDY" """ @spec encode(non_neg_integer()) :: String.t() def encode(num) when is_integer(num) and num >= 0 do num |> Stream.unfold(fn n -> if n == 0, do: nil, else: {rem(n, 32), div(n, 32)} end) |> Enum.to_list() |> Enum.reverse() |> case do [] -> [0] l -> l end |> Enum.map(&Map.fetch!(@encode_map, &1)) |> Enum.into('') |> to_string() end @doc ~S""" Encodes the given binary. iex> Base32H.encode_bin(<<227, 169, 72, 131, 141, 245, 213, 150, 217, 217>>) "WELLH0WDYPARDNER" """ @spec encode_bin(binary()) :: String.t() def encode_bin(<> = bin) do padded = add_padding(bin, 5, 0) for <> do for <>, do: digit end |> List.flatten() |> Enum.map(&Map.fetch!(@encode_map, &1)) |> Enum.into('') |> to_string() end @doc ~S""" Decodes the given string to an integer. iex> Base32H.decode("88pzd") 8_675_309 """ @spec decode(String.t()) :: non_neg_integer() def decode(<> = str) do str |> String.to_charlist() |> Enum.reduce(0, fn c, acc -> acc * 32 + Map.fetch!(@decode_map, c) end) end @doc ~S""" Decodes the given string to a bitstring. iex> Base32H.decode_bin("2060W2G6009") <<0, 0, 0, 8, 6, 7, 5, 3, 0, 9>> """ @spec decode_bin(String.t()) :: binary() def decode_bin(<> = str) do binary_size = (div(String.length(str) - 1, 8) + 1) * 5 <> end defp add_padding(<>, unit_size, padding) do pad_size = Integer.mod(unit_size - byte_size(bin), unit_size) <> end end