Files
n8n-openai-adapter/lib/elixir/lib/base.ex
T

1288 lines
37 KiB
Elixir

defmodule Base do
import Bitwise
@moduledoc """
This module provides data encoding and decoding functions
according to [RFC 4648](https://tools.ietf.org/html/rfc4648).
This document defines the commonly used base 16, base 32, and base
64 encoding schemes.
## Base 16 alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|:---------|------:|:---------|------:|:---------|------:|:---------|
| 0 | 0 | 4 | 4 | 8 | 8 | 12 | C |
| 1 | 1 | 5 | 5 | 9 | 9 | 13 | D |
| 2 | 2 | 6 | 6 | 10 | A | 14 | E |
| 3 | 3 | 7 | 7 | 11 | B | 15 | F |
## Base 32 alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|:---------|------:|:---------|------:|:---------|------:|:---------|
| 0 | A | 9 | J | 18 | S | 27 | 3 |
| 1 | B | 10 | K | 19 | T | 28 | 4 |
| 2 | C | 11 | L | 20 | U | 29 | 5 |
| 3 | D | 12 | M | 21 | V | 30 | 6 |
| 4 | E | 13 | N | 22 | W | 31 | 7 |
| 5 | F | 14 | O | 23 | X | | |
| 6 | G | 15 | P | 24 | Y | (pad) | = |
| 7 | H | 16 | Q | 25 | Z | | |
| 8 | I | 17 | R | 26 | 2 | | |
## Base 32 (extended hex) alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|:---------|------:|:---------|------:|:---------|------:|:---------|
| 0 | 0 | 9 | 9 | 18 | I | 27 | R |
| 1 | 1 | 10 | A | 19 | J | 28 | S |
| 2 | 2 | 11 | B | 20 | K | 29 | T |
| 3 | 3 | 12 | C | 21 | L | 30 | U |
| 4 | 4 | 13 | D | 22 | M | 31 | V |
| 5 | 5 | 14 | E | 23 | N | | |
| 6 | 6 | 15 | F | 24 | O | (pad) | = |
| 7 | 7 | 16 | G | 25 | P | | |
| 8 | 8 | 17 | H | 26 | Q | | |
## Base 64 alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|:----------|------:|:---------|------:|:---------|------:|:---------|
| 0 | A | 17 | R | 34 | i | 51 | z |
| 1 | B | 18 | S | 35 | j | 52 | 0 |
| 2 | C | 19 | T | 36 | k | 53 | 1 |
| 3 | D | 20 | U | 37 | l | 54 | 2 |
| 4 | E | 21 | V | 38 | m | 55 | 3 |
| 5 | F | 22 | W | 39 | n | 56 | 4 |
| 6 | G | 23 | X | 40 | o | 57 | 5 |
| 7 | H | 24 | Y | 41 | p | 58 | 6 |
| 8 | I | 25 | Z | 42 | q | 59 | 7 |
| 9 | J | 26 | a | 43 | r | 60 | 8 |
| 10 | K | 27 | b | 44 | s | 61 | 9 |
| 11 | L | 28 | c | 45 | t | 62 | + |
| 12 | M | 29 | d | 46 | u | 63 | / |
| 13 | N | 30 | e | 47 | v | | |
| 14 | O | 31 | f | 48 | w | (pad) | = |
| 15 | P | 32 | g | 49 | x | | |
| 16 | Q | 33 | h | 50 | y | | |
## Base 64 (URL and filename safe) alphabet
| Value | Encoding | Value | Encoding | Value | Encoding | Value | Encoding |
|------:|:---------|------:|:---------|------:|:---------|------:|:---------|
| 0 | A | 17 | R | 34 | i | 51 | z |
| 1 | B | 18 | S | 35 | j | 52 | 0 |
| 2 | C | 19 | T | 36 | k | 53 | 1 |
| 3 | D | 20 | U | 37 | l | 54 | 2 |
| 4 | E | 21 | V | 38 | m | 55 | 3 |
| 5 | F | 22 | W | 39 | n | 56 | 4 |
| 6 | G | 23 | X | 40 | o | 57 | 5 |
| 7 | H | 24 | Y | 41 | p | 58 | 6 |
| 8 | I | 25 | Z | 42 | q | 59 | 7 |
| 9 | J | 26 | a | 43 | r | 60 | 8 |
| 10 | K | 27 | b | 44 | s | 61 | 9 |
| 11 | L | 28 | c | 45 | t | 62 | - |
| 12 | M | 29 | d | 46 | u | 63 | _ |
| 13 | N | 30 | e | 47 | v | | |
| 14 | O | 31 | f | 48 | w | (pad) | = |
| 15 | P | 32 | g | 49 | x | | |
| 16 | Q | 33 | h | 50 | y | | |
"""
b16_alphabet = '0123456789ABCDEF'
b64_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
b64url_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'
b32_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
b32hex_alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUV'
defmacrop encode_pair(alphabet, case, value) do
quote do
case unquote(value) do
unquote(encode_pair_clauses(alphabet, case))
end
end
end
defp encode_pair_clauses(alphabet, case) when case in [:sensitive, :upper] do
shift = shift(alphabet)
alphabet
|> Enum.with_index()
|> encode_clauses(shift)
end
defp encode_pair_clauses(alphabet, :lower) do
shift = shift(alphabet)
alphabet
|> Stream.map(fn c -> if c in ?A..?Z, do: c - ?A + ?a, else: c end)
|> Enum.with_index()
|> encode_clauses(shift)
end
defp shift(alphabet) do
alphabet
|> length()
|> :math.log2()
|> round()
end
defp encode_clauses(alphabet, shift) do
for {encoding1, value1} <- alphabet,
{encoding2, value2} <- alphabet do
encoding = bsl(encoding1, 8) + encoding2
value = bsl(value1, shift) + value2
[clause] = quote(do: (unquote(value) -> unquote(encoding)))
clause
end
end
defmacrop decode_char(alphabet, case, encoding) do
quote do
case unquote(encoding) do
unquote(decode_char_clauses(alphabet, case))
end
end
end
defp decode_char_clauses(alphabet, case) when case in [:sensitive, :upper] do
clauses =
alphabet
|> Enum.with_index()
|> decode_clauses()
clauses ++ bad_digit_clause()
end
defp decode_char_clauses(alphabet, :lower) do
{uppers, rest} =
alphabet
|> Stream.with_index()
|> Enum.split_with(fn {encoding, _} -> encoding in ?A..?Z end)
lowers = Enum.map(uppers, fn {encoding, value} -> {encoding - ?A + ?a, value} end)
if length(uppers) > length(rest) do
decode_mixed_clauses(lowers, rest)
else
decode_mixed_clauses(rest, lowers)
end
end
defp decode_char_clauses(alphabet, :mixed) when length(alphabet) == 16 do
alphabet = Enum.with_index(alphabet)
lowers =
alphabet
|> Stream.filter(fn {encoding, _} -> encoding in ?A..?Z end)
|> Enum.map(fn {encoding, value} -> {encoding - ?A + ?a, value} end)
decode_mixed_clauses(alphabet, lowers)
end
defp decode_char_clauses(alphabet, :mixed) when length(alphabet) == 32 do
clauses =
alphabet
|> Stream.with_index()
|> Enum.flat_map(fn {encoding, value} = pair ->
if encoding in ?A..?Z do
[pair, {encoding - ?A + ?a, value}]
else
[pair]
end
end)
|> decode_clauses()
clauses ++ bad_digit_clause()
end
defp decode_mixed_clauses(first, second) do
first_clauses = decode_clauses(first)
second_clauses = decode_clauses(second) ++ bad_digit_clause()
join_clause =
quote do
encoding ->
case encoding do
unquote(second_clauses)
end
end
first_clauses ++ join_clause
end
defp decode_clauses(alphabet) do
for {encoding, value} <- alphabet do
[clause] = quote(do: (unquote(encoding) -> unquote(value)))
clause
end
end
defp bad_digit_clause() do
quote do
c ->
raise ArgumentError,
"non-alphabet digit found: #{inspect(<<c>>, binaries: :as_strings)} (byte #{c})"
end
end
defp maybe_pad(body, "", _, _), do: body
defp maybe_pad(body, tail, false, _), do: body <> tail
defp maybe_pad(body, tail, _, group_size) do
case group_size - rem(byte_size(tail), group_size) do
^group_size -> body <> tail
6 -> body <> tail <> "======"
5 -> body <> tail <> "====="
4 -> body <> tail <> "===="
3 -> body <> tail <> "==="
2 -> body <> tail <> "=="
1 -> body <> tail <> "="
end
end
@doc """
Encodes a binary string into a base 16 encoded string.
## Options
The accepted options are:
* `:case` - specifies the character case to use when encoding
The values for `:case` can be:
* `:upper` - uses upper case characters (default)
* `:lower` - uses lower case characters
## Examples
iex> Base.encode16("foobar")
"666F6F626172"
iex> Base.encode16("foobar", case: :lower)
"666f6f626172"
"""
@spec encode16(binary, keyword) :: binary
def encode16(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
do_encode16(case, data)
end
@doc """
Decodes a base 16 encoded string into a binary string.
## Options
The accepted options are:
* `:case` - specifies the character case to accept when decoding
The values for `:case` can be:
* `:upper` - only allows upper case characters (default)
* `:lower` - only allows lower case characters
* `:mixed` - allows mixed case characters
## Examples
iex> Base.decode16("666F6F626172")
{:ok, "foobar"}
iex> Base.decode16("666f6f626172", case: :lower)
{:ok, "foobar"}
iex> Base.decode16("666f6F626172", case: :mixed)
{:ok, "foobar"}
"""
@spec decode16(binary, keyword) :: {:ok, binary} | :error
def decode16(string, opts \\ []) do
{:ok, decode16!(string, opts)}
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 16 encoded string into a binary string.
## Options
The accepted options are:
* `:case` - specifies the character case to accept when decoding
The values for `:case` can be:
* `:upper` - only allows upper case characters (default)
* `:lower` - only allows lower case characters
* `:mixed` - allows mixed case characters
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
iex> Base.decode16!("666F6F626172")
"foobar"
iex> Base.decode16!("666f6f626172", case: :lower)
"foobar"
iex> Base.decode16!("666f6F626172", case: :mixed)
"foobar"
"""
@spec decode16!(binary, keyword) :: binary
def decode16!(string, opts \\ [])
def decode16!(string, opts) when is_binary(string) and rem(byte_size(string), 2) == 0 do
case = Keyword.get(opts, :case, :upper)
do_decode16(case, string)
end
def decode16!(string, _opts) when is_binary(string) do
raise ArgumentError, "odd-length string"
end
@doc """
Encodes a binary string into a base 64 encoded string.
Accepts `padding: false` option which will omit padding from
the output string.
## Examples
iex> Base.encode64("foobar")
"Zm9vYmFy"
iex> Base.encode64("foob")
"Zm9vYg=="
iex> Base.encode64("foob", padding: false)
"Zm9vYg"
"""
@spec encode64(binary, keyword) :: binary
def encode64(data, opts \\ []) when is_binary(data) do
pad? = Keyword.get(opts, :padding, true)
do_encode64(data, pad?)
end
@doc """
Decodes a base 64 encoded string into a binary string.
Accepts `ignore: :whitespace` option which will ignore all the
whitespace characters in the input string.
Accepts `padding: false` option which will ignore padding from
the input string.
## Examples
iex> Base.decode64("Zm9vYmFy")
{:ok, "foobar"}
iex> Base.decode64("Zm9vYmFy\\n", ignore: :whitespace)
{:ok, "foobar"}
iex> Base.decode64("Zm9vYg==")
{:ok, "foob"}
iex> Base.decode64("Zm9vYg", padding: false)
{:ok, "foob"}
"""
@spec decode64(binary, keyword) :: {:ok, binary} | :error
def decode64(string, opts \\ []) when is_binary(string) do
{:ok, decode64!(string, opts)}
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 64 encoded string into a binary string.
Accepts `ignore: :whitespace` option which will ignore all the
whitespace characters in the input string.
Accepts `padding: false` option which will ignore padding from
the input string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
iex> Base.decode64!("Zm9vYmFy")
"foobar"
iex> Base.decode64!("Zm9vYmFy\\n", ignore: :whitespace)
"foobar"
iex> Base.decode64!("Zm9vYg==")
"foob"
iex> Base.decode64!("Zm9vYg", padding: false)
"foob"
"""
@spec decode64!(binary, keyword) :: binary
def decode64!(string, opts \\ []) when is_binary(string) do
pad? = Keyword.get(opts, :padding, true)
string |> remove_ignored(opts[:ignore]) |> do_decode64(pad?)
end
@doc """
Encodes a binary string into a base 64 encoded string with URL and filename
safe alphabet.
Accepts `padding: false` option which will omit padding from
the output string.
## Examples
iex> Base.url_encode64(<<255, 127, 254, 252>>)
"_3_-_A=="
iex> Base.url_encode64(<<255, 127, 254, 252>>, padding: false)
"_3_-_A"
"""
@spec url_encode64(binary, keyword) :: binary
def url_encode64(data, opts \\ []) when is_binary(data) do
pad? = Keyword.get(opts, :padding, true)
do_encode64url(data, pad?)
end
@doc """
Decodes a base 64 encoded string with URL and filename safe alphabet
into a binary string.
Accepts `ignore: :whitespace` option which will ignore all the
whitespace characters in the input string.
Accepts `padding: false` option which will ignore padding from
the input string.
## Examples
iex> Base.url_decode64("_3_-_A==")
{:ok, <<255, 127, 254, 252>>}
iex> Base.url_decode64("_3_-_A==\\n", ignore: :whitespace)
{:ok, <<255, 127, 254, 252>>}
iex> Base.url_decode64("_3_-_A", padding: false)
{:ok, <<255, 127, 254, 252>>}
"""
@spec url_decode64(binary, keyword) :: {:ok, binary} | :error
def url_decode64(string, opts \\ []) when is_binary(string) do
{:ok, url_decode64!(string, opts)}
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 64 encoded string with URL and filename safe alphabet
into a binary string.
Accepts `ignore: :whitespace` option which will ignore all the
whitespace characters in the input string.
Accepts `padding: false` option which will ignore padding from
the input string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Examples
iex> Base.url_decode64!("_3_-_A==")
<<255, 127, 254, 252>>
iex> Base.url_decode64!("_3_-_A==\\n", ignore: :whitespace)
<<255, 127, 254, 252>>
iex> Base.url_decode64!("_3_-_A", padding: false)
<<255, 127, 254, 252>>
"""
@spec url_decode64!(binary, keyword) :: binary
def url_decode64!(string, opts \\ []) when is_binary(string) do
pad? = Keyword.get(opts, :padding, true)
string |> remove_ignored(opts[:ignore]) |> do_decode64url(pad?)
end
@doc """
Encodes a binary string into a base 32 encoded string.
## Options
The accepted options are:
* `:case` - specifies the character case to use when encoding
* `:padding` - specifies whether to apply padding
The values for `:case` can be:
* `:upper` - uses upper case characters (default)
* `:lower` - uses lower case characters
The values for `:padding` can be:
* `true` - pad the output string to the nearest multiple of 8 (default)
* `false` - omit padding from the output string
## Examples
iex> Base.encode32("foobar")
"MZXW6YTBOI======"
iex> Base.encode32("foobar", case: :lower)
"mzxw6ytboi======"
iex> Base.encode32("foobar", padding: false)
"MZXW6YTBOI"
"""
@spec encode32(binary, keyword) :: binary
def encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
pad? = Keyword.get(opts, :padding, true)
do_encode32(case, data, pad?)
end
@doc """
Decodes a base 32 encoded string into a binary string.
## Options
The accepted options are:
* `:case` - specifies the character case to accept when decoding
* `:padding` - specifies whether to require padding
The values for `:case` can be:
* `:upper` - only allows upper case characters (default)
* `:lower` - only allows lower case characters
* `:mixed` - allows mixed case characters
The values for `:padding` can be:
* `true` - requires the input string to be padded to the nearest multiple of 8 (default)
* `false` - ignores padding from the input string
## Examples
iex> Base.decode32("MZXW6YTBOI======")
{:ok, "foobar"}
iex> Base.decode32("mzxw6ytboi======", case: :lower)
{:ok, "foobar"}
iex> Base.decode32("mzXW6ytBOi======", case: :mixed)
{:ok, "foobar"}
iex> Base.decode32("MZXW6YTBOI", padding: false)
{:ok, "foobar"}
"""
@spec decode32(binary, keyword) :: {:ok, binary} | :error
def decode32(string, opts \\ []) do
{:ok, decode32!(string, opts)}
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 32 encoded string into a binary string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Options
The accepted options are:
* `:case` - specifies the character case to accept when decoding
* `:padding` - specifies whether to require padding
The values for `:case` can be:
* `:upper` - only allows upper case characters (default)
* `:lower` - only allows lower case characters
* `:mixed` - allows mixed case characters
The values for `:padding` can be:
* `true` - requires the input string to be padded to the nearest multiple of 8 (default)
* `false` - ignores padding from the input string
## Examples
iex> Base.decode32!("MZXW6YTBOI======")
"foobar"
iex> Base.decode32!("mzxw6ytboi======", case: :lower)
"foobar"
iex> Base.decode32!("mzXW6ytBOi======", case: :mixed)
"foobar"
iex> Base.decode32!("MZXW6YTBOI", padding: false)
"foobar"
"""
@spec decode32!(binary, keyword) :: binary
def decode32!(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
pad? = Keyword.get(opts, :padding, true)
do_decode32(case, string, pad?)
end
@doc """
Encodes a binary string into a base 32 encoded string with an
extended hexadecimal alphabet.
## Options
The accepted options are:
* `:case` - specifies the character case to use when encoding
* `:padding` - specifies whether to apply padding
The values for `:case` can be:
* `:upper` - uses upper case characters (default)
* `:lower` - uses lower case characters
The values for `:padding` can be:
* `true` - pad the output string to the nearest multiple of 8 (default)
* `false` - omit padding from the output string
## Examples
iex> Base.hex_encode32("foobar")
"CPNMUOJ1E8======"
iex> Base.hex_encode32("foobar", case: :lower)
"cpnmuoj1e8======"
iex> Base.hex_encode32("foobar", padding: false)
"CPNMUOJ1E8"
"""
@spec hex_encode32(binary, keyword) :: binary
def hex_encode32(data, opts \\ []) when is_binary(data) do
case = Keyword.get(opts, :case, :upper)
pad? = Keyword.get(opts, :padding, true)
do_encode32hex(case, data, pad?)
end
@doc """
Decodes a base 32 encoded string with extended hexadecimal alphabet
into a binary string.
## Options
The accepted options are:
* `:case` - specifies the character case to accept when decoding
* `:padding` - specifies whether to require padding
The values for `:case` can be:
* `:upper` - only allows upper case characters (default)
* `:lower` - only allows lower case characters
* `:mixed` - allows mixed case characters
The values for `:padding` can be:
* `true` - requires the input string to be padded to the nearest multiple of 8 (default)
* `false` - ignores padding from the input string
## Examples
iex> Base.hex_decode32("CPNMUOJ1E8======")
{:ok, "foobar"}
iex> Base.hex_decode32("cpnmuoj1e8======", case: :lower)
{:ok, "foobar"}
iex> Base.hex_decode32("cpnMuOJ1E8======", case: :mixed)
{:ok, "foobar"}
iex> Base.hex_decode32("CPNMUOJ1E8", padding: false)
{:ok, "foobar"}
"""
@spec hex_decode32(binary, keyword) :: {:ok, binary} | :error
def hex_decode32(string, opts \\ []) do
{:ok, hex_decode32!(string, opts)}
rescue
ArgumentError -> :error
end
@doc """
Decodes a base 32 encoded string with extended hexadecimal alphabet
into a binary string.
An `ArgumentError` exception is raised if the padding is incorrect or
a non-alphabet character is present in the string.
## Options
The accepted options are:
* `:case` - specifies the character case to accept when decoding
* `:padding` - specifies whether to require padding
The values for `:case` can be:
* `:upper` - only allows upper case characters (default)
* `:lower` - only allows lower case characters
* `:mixed` - allows mixed case characters
The values for `:padding` can be:
* `true` - requires the input string to be padded to the nearest multiple of 8 (default)
* `false` - ignores padding from the input string
## Examples
iex> Base.hex_decode32!("CPNMUOJ1E8======")
"foobar"
iex> Base.hex_decode32!("cpnmuoj1e8======", case: :lower)
"foobar"
iex> Base.hex_decode32!("cpnMuOJ1E8======", case: :mixed)
"foobar"
iex> Base.hex_decode32!("CPNMUOJ1E8", padding: false)
"foobar"
"""
@spec hex_decode32!(binary, keyword) :: binary
def hex_decode32!(string, opts \\ []) when is_binary(string) do
case = Keyword.get(opts, :case, :upper)
pad? = Keyword.get(opts, :padding, true)
do_decode32hex(case, string, pad?)
end
defp remove_ignored(string, nil), do: string
defp remove_ignored(string, :whitespace) do
for <<char::8 <- string>>, char not in '\s\t\r\n', into: <<>>, do: <<char::8>>
end
enc16 = [upper: :enc16_upper, lower: :enc16_lower]
for {case, fun} <- enc16 do
defp unquote(fun)(char) do
encode_pair(unquote(b16_alphabet), unquote(case), char)
end
end
defp do_encode16(_, <<>>), do: <<>>
for {case, fun} <- enc16 do
defp do_encode16(unquote(case), data) do
split = 8 * div(byte_size(data), 8)
<<main::size(split)-binary, rest::binary>> = data
main =
for <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8 <- main>>, into: <<>> do
<<
unquote(fun)(c1)::16,
unquote(fun)(c2)::16,
unquote(fun)(c3)::16,
unquote(fun)(c4)::16,
unquote(fun)(c5)::16,
unquote(fun)(c6)::16,
unquote(fun)(c7)::16,
unquote(fun)(c8)::16
>>
end
case rest do
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> ->
<<
main::binary,
unquote(fun)(c1)::16,
unquote(fun)(c2)::16,
unquote(fun)(c3)::16,
unquote(fun)(c4)::16,
unquote(fun)(c5)::16,
unquote(fun)(c6)::16,
unquote(fun)(c7)::16
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8>> ->
<<
main::binary,
unquote(fun)(c1)::16,
unquote(fun)(c2)::16,
unquote(fun)(c3)::16,
unquote(fun)(c4)::16,
unquote(fun)(c5)::16,
unquote(fun)(c6)::16
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8>> ->
<<
main::binary,
unquote(fun)(c1)::16,
unquote(fun)(c2)::16,
unquote(fun)(c3)::16,
unquote(fun)(c4)::16,
unquote(fun)(c5)::16
>>
<<c1::8, c2::8, c3::8, c4::8>> ->
<<
main::binary,
unquote(fun)(c1)::16,
unquote(fun)(c2)::16,
unquote(fun)(c3)::16,
unquote(fun)(c4)::16
>>
<<c1::8, c2::8, c3::8>> ->
<<main::binary, unquote(fun)(c1)::16, unquote(fun)(c2)::16, unquote(fun)(c3)::16>>
<<c1::8, c2::8>> ->
<<main::binary, unquote(fun)(c1)::16, unquote(fun)(c2)::16>>
<<c1::8>> ->
<<main::binary, unquote(fun)(c1)::16>>
<<>> ->
main
end
end
end
dec16 = [upper: :dec16_upper, lower: :dec16_lower, mixed: :dec16_mixed]
for {case, fun} <- dec16 do
defp unquote(fun)(encoding) do
decode_char(unquote(b16_alphabet), unquote(case), encoding)
end
end
defp do_decode16(_, <<>>), do: <<>>
for {case, fun} <- dec16 do
defp do_decode16(unquote(case), string) do
split = 8 * div(byte_size(string), 8)
<<main::size(split)-binary, rest::binary>> = string
main =
for <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8 <- main>>, into: <<>> do
<<
unquote(fun)(c1)::4,
unquote(fun)(c2)::4,
unquote(fun)(c3)::4,
unquote(fun)(c4)::4,
unquote(fun)(c5)::4,
unquote(fun)(c6)::4,
unquote(fun)(c7)::4,
unquote(fun)(c8)::4
>>
end
case rest do
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8>> ->
<<
main::bits,
unquote(fun)(c1)::4,
unquote(fun)(c2)::4,
unquote(fun)(c3)::4,
unquote(fun)(c4)::4,
unquote(fun)(c5)::4,
unquote(fun)(c6)::4
>>
<<c1::8, c2::8, c3::8, c4::8>> ->
<<
main::bits,
unquote(fun)(c1)::4,
unquote(fun)(c2)::4,
unquote(fun)(c3)::4,
unquote(fun)(c4)::4
>>
<<c1::8, c2::8>> ->
<<main::bits, unquote(fun)(c1)::4, unquote(fun)(c2)::4>>
<<_::8>> ->
raise ArgumentError, "odd-length string"
<<>> ->
main
end
end
end
for {base, alphabet} <- ["64": b64_alphabet, "64url": b64url_alphabet] do
pair = :"enc#{base}_pair"
char = :"enc#{base}_char"
do_encode = :"do_encode#{base}"
defp unquote(pair)(value) do
encode_pair(unquote(alphabet), :sensitive, value)
end
defp unquote(char)(value) do
value
|> unquote(pair)()
|> band(0x00FF)
end
defp unquote(do_encode)(<<>>, _), do: <<>>
defp unquote(do_encode)(data, pad?) do
split = 6 * div(byte_size(data), 6)
<<main::size(split)-binary, rest::binary>> = data
main =
for <<c1::12, c2::12, c3::12, c4::12 <- main>>, into: <<>> do
<<
unquote(pair)(c1)::16,
unquote(pair)(c2)::16,
unquote(pair)(c3)::16,
unquote(pair)(c4)::16
>>
end
tail =
case rest do
<<c1::12, c2::12, c3::12, c::4>> ->
<<
unquote(pair)(c1)::16,
unquote(pair)(c2)::16,
unquote(pair)(c3)::16,
unquote(char)(bsl(c, 2))::8
>>
<<c1::12, c2::12, c3::8>> ->
<<unquote(pair)(c1)::16, unquote(pair)(c2)::16, unquote(pair)(bsl(c3, 4))::16>>
<<c1::12, c2::12>> ->
<<unquote(pair)(c1)::16, unquote(pair)(c2)::16>>
<<c1::12, c2::4>> ->
<<unquote(pair)(c1)::16, unquote(char)(bsl(c2, 2))::8>>
<<c1::8>> ->
<<unquote(pair)(bsl(c1, 4))::16>>
<<>> ->
<<>>
end
maybe_pad(main, tail, pad?, 4)
end
end
for {base, alphabet} <- ["64": b64_alphabet, "64url": b64url_alphabet] do
fun = :"dec#{base}"
do_decode = :"do_decode#{base}"
defp unquote(fun)(encoding) do
decode_char(unquote(alphabet), :sensitive, encoding)
end
defp unquote(do_decode)(<<>>, _), do: <<>>
defp unquote(do_decode)(string, pad?) do
segs = div(byte_size(string) + 7, 8) - 1
<<main::size(segs)-binary-unit(64), rest::binary>> = string
main =
for <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8 <- main>>, into: <<>> do
<<
unquote(fun)(c1)::6,
unquote(fun)(c2)::6,
unquote(fun)(c3)::6,
unquote(fun)(c4)::6,
unquote(fun)(c5)::6,
unquote(fun)(c6)::6,
unquote(fun)(c7)::6,
unquote(fun)(c8)::6
>>
end
case rest do
<<c1::8, c2::8, ?=, ?=>> ->
<<main::bits, unquote(fun)(c1)::6, bsr(unquote(fun)(c2), 4)::2>>
<<c1::8, c2::8, c3::8, ?=>> ->
<<main::bits, unquote(fun)(c1)::6, unquote(fun)(c2)::6, bsr(unquote(fun)(c3), 2)::4>>
<<c1::8, c2::8, c3::8, c4::8>> ->
<<
main::bits,
unquote(fun)(c1)::6,
unquote(fun)(c2)::6,
unquote(fun)(c3)::6,
unquote(fun)(c4)::6
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, ?=, ?=>> ->
<<
main::bits,
unquote(fun)(c1)::6,
unquote(fun)(c2)::6,
unquote(fun)(c3)::6,
unquote(fun)(c4)::6,
unquote(fun)(c5)::6,
bsr(unquote(fun)(c6), 4)::2
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
<<
main::bits,
unquote(fun)(c1)::6,
unquote(fun)(c2)::6,
unquote(fun)(c3)::6,
unquote(fun)(c4)::6,
unquote(fun)(c5)::6,
unquote(fun)(c6)::6,
bsr(unquote(fun)(c7), 2)::4
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
<<
main::bits,
unquote(fun)(c1)::6,
unquote(fun)(c2)::6,
unquote(fun)(c3)::6,
unquote(fun)(c4)::6,
unquote(fun)(c5)::6,
unquote(fun)(c6)::6,
unquote(fun)(c7)::6,
unquote(fun)(c8)::6
>>
<<c1::8, c2::8>> when not pad? ->
<<main::bits, unquote(fun)(c1)::6, bsr(unquote(fun)(c2), 4)::2>>
<<c1::8, c2::8, c3::8>> when not pad? ->
<<main::bits, unquote(fun)(c1)::6, unquote(fun)(c2)::6, bsr(unquote(fun)(c3), 2)::4>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8>> when not pad? ->
<<
main::bits,
unquote(fun)(c1)::6,
unquote(fun)(c2)::6,
unquote(fun)(c3)::6,
unquote(fun)(c4)::6,
unquote(fun)(c5)::6,
bsr(unquote(fun)(c6), 4)::2
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> when not pad? ->
<<
main::bits,
unquote(fun)(c1)::6,
unquote(fun)(c2)::6,
unquote(fun)(c3)::6,
unquote(fun)(c4)::6,
unquote(fun)(c5)::6,
unquote(fun)(c6)::6,
bsr(unquote(fun)(c7), 2)::4
>>
_ ->
raise ArgumentError, "incorrect padding"
end
end
end
for {base, alphabet} <- ["32": b32_alphabet, "32hex": b32hex_alphabet],
case <- [:upper, :lower] do
pair = :"enc#{base}_#{case}_pair"
char = :"enc#{base}_#{case}_char"
do_encode = :"do_encode#{base}"
defp unquote(pair)(value) do
encode_pair(unquote(alphabet), unquote(case), value)
end
defp unquote(char)(value) do
value
|> unquote(pair)()
|> band(0x00FF)
end
defp unquote(do_encode)(_, <<>>, _), do: <<>>
defp unquote(do_encode)(unquote(case), data, pad?) do
split = 5 * div(byte_size(data), 5)
<<main::size(split)-binary, rest::binary>> = data
main =
for <<c1::10, c2::10, c3::10, c4::10 <- main>>, into: <<>> do
<<
unquote(pair)(c1)::16,
unquote(pair)(c2)::16,
unquote(pair)(c3)::16,
unquote(pair)(c4)::16
>>
end
tail =
case rest do
<<c1::10, c2::10, c3::10, c4::2>> ->
<<
unquote(pair)(c1)::16,
unquote(pair)(c2)::16,
unquote(pair)(c3)::16,
unquote(char)(bsl(c4, 3))::8
>>
<<c1::10, c2::10, c3::4>> ->
<<unquote(pair)(c1)::16, unquote(pair)(c2)::16, unquote(char)(bsl(c3, 1))::8>>
<<c1::10, c2::6>> ->
<<unquote(pair)(c1)::16, unquote(pair)(bsl(c2, 4))::16>>
<<c1::8>> ->
<<unquote(pair)(bsl(c1, 2))::16>>
<<>> ->
<<>>
end
maybe_pad(main, tail, pad?, 8)
end
end
for {base, alphabet} <- ["32": b32_alphabet, "32hex": b32hex_alphabet],
case <- [:upper, :lower, :mixed] do
fun = :"dec#{base}_#{case}"
do_decode = :"do_decode#{base}"
defp unquote(fun)(encoding) do
decode_char(unquote(alphabet), unquote(case), encoding)
end
defp unquote(do_decode)(_, <<>>, _), do: <<>>
defp unquote(do_decode)(unquote(case), string, pad?) do
segs = div(byte_size(string) + 7, 8) - 1
<<main::size(segs)-binary-unit(64), rest::binary>> = string
main =
for <<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8 <- main>>, into: <<>> do
<<
unquote(fun)(c1)::5,
unquote(fun)(c2)::5,
unquote(fun)(c3)::5,
unquote(fun)(c4)::5,
unquote(fun)(c5)::5,
unquote(fun)(c6)::5,
unquote(fun)(c7)::5,
unquote(fun)(c8)::5
>>
end
case rest do
<<c1::8, c2::8, ?=, ?=, ?=, ?=, ?=, ?=>> ->
<<main::bits, unquote(fun)(c1)::5, bsr(unquote(fun)(c2), 2)::3>>
<<c1::8, c2::8, c3::8, c4::8, ?=, ?=, ?=, ?=>> ->
<<
main::bits,
unquote(fun)(c1)::5,
unquote(fun)(c2)::5,
unquote(fun)(c3)::5,
bsr(unquote(fun)(c4), 4)::1
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, ?=, ?=, ?=>> ->
<<
main::bits,
unquote(fun)(c1)::5,
unquote(fun)(c2)::5,
unquote(fun)(c3)::5,
unquote(fun)(c4)::5,
bsr(unquote(fun)(c5), 1)::4
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, ?=>> ->
<<
main::bits,
unquote(fun)(c1)::5,
unquote(fun)(c2)::5,
unquote(fun)(c3)::5,
unquote(fun)(c4)::5,
unquote(fun)(c5)::5,
unquote(fun)(c6)::5,
bsr(unquote(fun)(c7), 3)::2
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8, c8::8>> ->
<<
main::bits,
unquote(fun)(c1)::5,
unquote(fun)(c2)::5,
unquote(fun)(c3)::5,
unquote(fun)(c4)::5,
unquote(fun)(c5)::5,
unquote(fun)(c6)::5,
unquote(fun)(c7)::5,
unquote(fun)(c8)::5
>>
<<c1::8, c2::8>> when not pad? ->
<<main::bits, unquote(fun)(c1)::5, bsr(unquote(fun)(c2), 2)::3>>
<<c1::8, c2::8, c3::8, c4::8>> when not pad? ->
<<
main::bits,
unquote(fun)(c1)::5,
unquote(fun)(c2)::5,
unquote(fun)(c3)::5,
bsr(unquote(fun)(c4), 4)::1
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8>> when not pad? ->
<<
main::bits,
unquote(fun)(c1)::5,
unquote(fun)(c2)::5,
unquote(fun)(c3)::5,
unquote(fun)(c4)::5,
bsr(unquote(fun)(c5), 1)::4
>>
<<c1::8, c2::8, c3::8, c4::8, c5::8, c6::8, c7::8>> when not pad? ->
<<
main::bits,
unquote(fun)(c1)::5,
unquote(fun)(c2)::5,
unquote(fun)(c3)::5,
unquote(fun)(c4)::5,
unquote(fun)(c5)::5,
unquote(fun)(c6)::5,
bsr(unquote(fun)(c7), 3)::2
>>
_ ->
raise ArgumentError, "incorrect padding"
end
end
end
end