Completely re-write base64 logic, make API suck less in doing so

This commit is contained in:
Daniel J. Hofmann 2016-03-14 19:08:41 +01:00 committed by Patrick Niklaus
parent 735b325d74
commit a7aa27c87c
2 changed files with 90 additions and 58 deletions

View File

@ -1,93 +1,125 @@
#ifndef OSRM_BASE64_HPP #ifndef OSRM_BASE64_HPP
#define OSRM_BASE64_HPP #define OSRM_BASE64_HPP
#include <boost/assert.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <algorithm>
#include <iterator>
#include <string> #include <string>
#include <vector> #include <iterator>
#include <type_traits>
#include <cstdint> #include <cstddef>
#include <climits> #include <climits>
#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/range/algorithm/copy.hpp>
// RFC 4648 "The Base16, Base32, and Base64 Data Encodings" // RFC 4648 "The Base16, Base32, and Base64 Data Encodings"
// See: https://tools.ietf.org/html/rfc4648 // See: https://tools.ietf.org/html/rfc4648
// Implementation adapted from: http://stackoverflow.com/a/28471421
// The C++ standard guarantees none of this by default, but we need it in the following.
static_assert(CHAR_BIT == 8u, "we assume a byte holds 8 bits");
static_assert(sizeof(char) == 1u, "we assume a char is one byte large");
namespace osrm namespace osrm
{ {
namespace engine namespace engine
{ {
namespace detail // Encoding Implementation
// Encodes a chunk of memory to Base64.
inline std::string encodeBase64(const unsigned char *first, std::size_t size)
{ {
static_assert(CHAR_BIT == 8u, "we assume a byte holds 8 bits"); using namespace boost::archive::iterators;
static_assert(sizeof(char) == 1u, "we assume a char is one byte large");
using Base64FromBinary = boost::archive::iterators::base64_from_binary< const std::string bytes{first, first + size};
boost::archive::iterators::transform_width<const char *, // sequence of chars
6, // get view of 6 bit
8 // from sequence of 8 bit
>>;
using BinaryFromBase64 = boost::archive::iterators::transform_width< using Iter = base64_from_binary<transform_width<std::string::const_iterator, 6, 8>>;
boost::archive::iterators::binary_from_base64<std::string::const_iterator>,
8, // get a view of 8 bit
6 // from a sequence of 6 bit
>;
} // ns detail
template <typename T> std::string encodeBase64(const T &x) Iter view_first{begin(bytes)};
{ Iter view_last{end(bytes)};
#if not defined __GNUC__ or __GNUC__ > 4
static_assert(std::is_trivially_copyable<T>::value, "requires a trivially copyable type");
#endif
std::vector<unsigned char> bytes{reinterpret_cast<const char *>(&x), std::string encoded{view_first, view_last};
reinterpret_cast<const char *>(&x) + sizeof(T)};
BOOST_ASSERT(!bytes.empty());
std::size_t bytes_to_pad{0}; return encoded.append((3 - size % 3) % 3, '=');
while (bytes.size() % 3 != 0)
{
bytes_to_pad += 1;
bytes.push_back(0);
}
BOOST_ASSERT(bytes_to_pad == 0 || bytes_to_pad == 1 || bytes_to_pad == 2);
BOOST_ASSERT_MSG(0 == bytes.size() % 3, "base64 input data size is not a multiple of 3");
std::string encoded{detail::Base64FromBinary{bytes.data()},
detail::Base64FromBinary{bytes.data() + (bytes.size() - bytes_to_pad)}};
std::replace(begin(encoded), end(encoded), '+', '-');
std::replace(begin(encoded), end(encoded), '/', '_');
return encoded;
} }
template <typename T> T decodeBase64(std::string encoded) // C++11 standard 3.9.1/1: Plain char, signed char, and unsigned char are three distinct types
// Overload for signed char catches (not only but also) C-string literals.
inline std::string encodeBase64(const signed char *first, std::size_t size)
{
return encodeBase64(reinterpret_cast<const unsigned char *>(first), size);
}
// Overload for char catches (not only but also) C-string literals.
inline std::string encodeBase64(const char *first, std::size_t size)
{
return encodeBase64(reinterpret_cast<const unsigned char *>(first), size);
}
// Convenience specialization, encoding from string instead of byte-dumping it.
inline std::string encodeBase64(const std::string &x) { return encodeBase64(x.data(), x.size()); }
// Encode any sufficiently trivial object to Base64.
template <typename T> std::string encodeBase64Bytewise(const T &x)
{ {
#if not defined __GNUC__ or __GNUC__ > 4 #if not defined __GNUC__ or __GNUC__ > 4
static_assert(std::is_trivially_copyable<T>::value, "requires a trivially copyable type"); static_assert(std::is_trivially_copyable<T>::value, "requires a trivially copyable type");
#endif #endif
std::replace(begin(encoded), end(encoded), '-', '+'); return encodeBase64(reinterpret_cast<const unsigned char *>(&x), sizeof(T));
std::replace(begin(encoded), end(encoded), '_', '/'); }
T rv; // Decoding Implementation
std::copy(detail::BinaryFromBase64{begin(encoded)}, // Decodes into a chunk of memory that is at least as large as the input.
detail::BinaryFromBase64{begin(encoded) + encoded.length()}, template <typename OutputIter> void decodeBase64(const std::string &encoded, OutputIter out)
reinterpret_cast<char *>(&rv)); {
using namespace boost::archive::iterators;
using namespace boost::algorithm;
using Iter = transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>;
Iter view_first{begin(encoded)};
Iter view_last{end(encoded)};
const auto null = [](const unsigned char c)
{
return c == '\0';
};
const auto bytes = trim_right_copy_if(std::string{view_first, view_last}, null);
boost::copy(bytes, out);
}
// Convenience specialization, filling string instead of byte-dumping into it.
inline std::string decodeBase64(const std::string &encoded)
{
std::string rv;
decodeBase64(encoded, std::back_inserter(rv));
return rv; return rv;
} }
// Decodes from Base 64 to any sufficiently trivial object.
template <typename T> T decodeBase64Bytewise(const std::string &encoded)
{
#if not defined __GNUC__ or __GNUC__ > 4
static_assert(std::is_trivially_copyable<T>::value, "requires a trivially copyable type");
#endif
T x;
decodeBase64(encoded, reinterpret_cast<unsigned char *>(&x));
return x;
}
} // ns engine } // ns engine
} // ns osrm } // ns osrm

View File

@ -18,13 +18,13 @@ bool Hint::IsValid(const util::Coordinate new_input_coordinates,
facade.GetCheckSum() == data_checksum; facade.GetCheckSum() == data_checksum;
} }
std::string Hint::ToBase64() const { return encodeBase64(*this); } std::string Hint::ToBase64() const { return encodeBase64Bytewise(*this); }
Hint Hint::FromBase64(const std::string &base64Hint) Hint Hint::FromBase64(const std::string &base64Hint)
{ {
BOOST_ASSERT_MSG(base64Hint.size() == ENCODED_HINT_SIZE, "Hint has invalid size"); BOOST_ASSERT_MSG(base64Hint.size() == ENCODED_HINT_SIZE, "Hint has invalid size");
return decodeBase64<Hint>(base64Hint); return decodeBase64Bytewise<Hint>(base64Hint);
} }
} }
} }