Write a BaseN codec

This section explains how to create BaseN codec variants (e.g. base64 with a different alphabet).

Define your encoding traits

To create your codec variant, you first need to define an encoding traits type, which must model base_n::encoding_traits.

Here are the base64 traits (simplified):

#include <algorithm>

#include <mgs/base_n/padding_policy.hpp>

using namespace mgs;

struct base64_encoding_traits
{
  using alphabet_t = char const[64];

  // using C++17 inline variables for simplicity
  inline static constexpr alphabet_t alphabet = {
      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
      'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
      'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
      'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

  inline static constexpr char padding_character = '=';
  inline static constexpr auto padding_policy =
      base_n::padding_policy::required;

  // must return the position of the parameter in the alphabet, or -1
  static constexpr int index_of(char c)
  {
    auto it = std::find(std::begin(alphabet), std::end(alphabet), c);
    if (it == std::end(alphabet))
      return -1;
    return it - std::begin(alphabet);
  }
};

Tip

For better performance, I recommend using a lookup-table instead of relying on std::find.

Create the codec variant

mgs uses a generic implementation for every BaseN codec, which can be parameterized with encoding traits:

// Header <mgs/base_n/basic_codec.hpp>

namespace mgs {
namespace base_n {

template <typename EncodingTraits,
          typename DecodingTraits = EncodingTraits>
class basic_codec { /* ... */ };

} // namespace base_n
} // namespace mgs

You have to instantiate it with your encoding traits:

#include <algorithm>
#include <iostream>

#include <mgs/base_n/basic_codec.hpp>
#include <mgs/base_n/padding_policy.hpp>

using namespace mgs;

struct base64_encoding_traits {
  using alphabet_t = char const[64];

  // using C++17 inline variables for simplicity
  inline static constexpr alphabet_t alphabet = {
      'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
      'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
      'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
      'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};

  inline static constexpr char padding_character = '=';
  inline static constexpr auto padding_policy =
      base_n::padding_policy::required;

  // must return the position of the parameter in the alphabet, or -1
  static int index_of(char c) {
    auto it = std::find(std::begin(alphabet), std::end(alphabet), c);
    if (it == std::end(alphabet)) return -1;
    return it - std::begin(alphabet);
  }
};

int main() {
  using custom_base64 = base_n::basic_codec<base64_encoding_traits>;

  std::cout << custom_base64::encode("Hello, World!") << std::endl;
}

Note

base_n::basic_codec second template parameter is useful if you want to have different behaviors when encoding and decoding (e.g. base64url_nopad has different padding policies).