Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to our own Base64 implementation #13

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 153 additions & 14 deletions include/mumble/Base64.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,170 @@
// that can be found in the LICENSE file at the root of the
// Mumble source tree or at <https://www.mumble.info/LICENSE>.

// Inspired by https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp.

#ifndef MUMBLE_BASE64_HPP
#define MUMBLE_BASE64_HPP

#include "Macros.hpp"
#include "NonCopyable.hpp"
#include "Types.hpp"

#include <memory>
#include <array>
#include <cmath>
#include <limits>
#include <stdexcept>

#include <gsl/span>

namespace mumble {
class MUMBLE_EXPORT Base64 : NonCopyable {
public:
class P;
namespace base64 {
using StrView = gsl::span< char >;
using StrViewConst = gsl::span< const char >;
Krzmbrzl marked this conversation as resolved.
Show resolved Hide resolved

static constexpr char PAD_CHAR = '=';

static constexpr std::byte operator+(const std::byte lhs, const std::byte rhs) {
const auto ret = std::to_integer< uint8_t >(lhs) + std::to_integer< uint8_t >(rhs);
return std::byte(ret);
}

static constexpr std::byte charToByte(const char ch) {
if (ch >= 'A' && ch <= 'Z')
return std::byte(ch - 'A');
if (ch >= 'a' && ch <= 'z')
return std::byte(ch - 'a' + ('Z' - 'A') + 1);
if (ch >= '0' && ch <= '9')
return std::byte(ch - '0' + ('Z' - 'A') + ('z' - 'a') + 2);
if (ch == '+' || ch == '-')
return std::byte(62);
if (ch == '/' || ch == '_')
return std::byte(63);

throw std::range_error(std::string() + ch + " is not a valid character!");
}

static constexpr char byteToChar(const std::byte byte) {
const auto num = std::to_integer< char >(byte);

if (num <= 25)
return num + 'A';
if (num >= 26 && num <= 51)
return num + 'a' - ('Z' - 'A') - 1;
if (num >= 52 && num <= 61)
return num + '0' - ('Z' - 'A') - ('z' - 'a') - 2;
if (num == 62)
return '+';
if (num == 63)
return '/';

throw std::range_error(std::to_string(+num)
+ " is not a valid byte that can be translated into Base64 alphabet!");
}

static inline bool isValid(const char ch) {
try {
charToByte(ch);
} catch (const std::range_error &) {
return false;
}

return true;
}

static inline bool isValid(const StrViewConst in) {
for (const char ch : in) {
if (!isValid(ch)) {
return false;
}
}

return true;
}

static constexpr size_t decodedSize(const StrViewConst in) {
size_t size = (in.size() / 4) * 3;
// Base64 can have up to 2 pad characters, in order to make the string a multiple of 4.
// In order to calculate the exact output size we have to ignore padding.
if (auto iter = in.rbegin(); *iter == PAD_CHAR) {
size -= *++iter == PAD_CHAR ? 2U : 1U;
}

return size;
}

static constexpr size_t encodedSize(const BufViewConst in) {
// ceil((float) a / 3) == (a + 2) / 3, assuming a is an integer.
return ((in.size() + 2) / 3) * 4;
}

static constexpr size_t decode(BufView out, StrViewConst in) {
if (in.empty()) {
return 0;
}

if (in.size() % 4 != 0) {
throw std::invalid_argument("Input is not a multiple of 4!");
}

const size_t size = decodedSize(in);
if (out.size() < size) {
throw std::invalid_argument("Insufficient output buffer size!");
}

for (; !in.empty(); out = out.subspan(3), in = in.subspan(4)) {
out[0] = (charToByte(in[0]) << 2) + ((charToByte(in[1]) & std::byte(0x30)) >> 4);

if (in[2] == '=') {
break;
}

out[1] = ((charToByte(in[1]) & std::byte(0x0f)) << 4) + ((charToByte(in[2]) & std::byte(0x3c)) >> 2);

if (in[3] == '=') {
break;
}

out[2] = ((charToByte(in[2]) & std::byte(0x03)) << 6) + charToByte(in[3]);
}

return size;
}

static constexpr size_t encode(StrView out, BufViewConst in) {
if (in.empty()) {
return 0;
}

const size_t size = encodedSize(in);
if (out.size() < size) {
throw std::invalid_argument("Insufficient output buffer size!");
}

for (; !in.empty(); out = out.subspan(4)) {
out[0] = byteToChar((in[0] & std::byte(0xfc)) >> 2);

Base64();
virtual ~Base64();
if (in.size() >= 2) {
out[1] = byteToChar(((in[0] & std::byte(0x03)) << 4) + ((in[1] & std::byte(0xf0)) >> 4));

virtual explicit operator bool();
if (in.size() >= 3) {
out[2] = byteToChar(((in[1] & std::byte(0x0f)) << 2) + ((in[2] & std::byte(0xc0)) >> 6));
out[3] = byteToChar(in[2] & std::byte(0x3f));
} else {
out[2] = byteToChar((in[1] & std::byte(0x0f)) << 2);
out[3] = PAD_CHAR;
}
} else {
out[1] = byteToChar((in[0] & std::byte(0x03)) << 4);
out[2] = PAD_CHAR;
out[3] = PAD_CHAR;
}

virtual size_t decode(const BufView out, const BufViewConst in);
static size_t encode(const BufView out, const BufViewConst in);
// All data blocks are guaranteed to be 3 bytes except the last one.
in = in.subspan(std::min(size_t{ 3 }, in.size()));
}
Comment on lines +144 to +165
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Analogous to the decode function, a comment textually explaining how this stuff works would be good.


private:
std::unique_ptr< P > m_p;
};
return size;
}
}; // namespace base64
} // namespace mumble

#endif
98 changes: 0 additions & 98 deletions src/Base64.cpp

This file was deleted.

26 changes: 0 additions & 26 deletions src/Base64.hpp

This file was deleted.

2 changes: 0 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ target_include_directories(mumble_library

target_sources(mumble_library
PRIVATE
"Base64.cpp"
"Base64.hpp"
"Cert.cpp"
"Cert.hpp"
"Connection.cpp"
Expand Down
24 changes: 7 additions & 17 deletions tests/TestBase64/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,45 +23,35 @@ static constexpr size_t iterations = 1000000;

using namespace mumble;

static std::string decode(Base64 &base64, const Data::Entry &entry) {
const BufViewConst in(reinterpret_cast< const std::byte * >(entry.second.data()), entry.second.size());

static std::string decode(const Data::Entry &entry) {
std::string ret;
ret.resize(base64.decode({}, in));
ret.resize(base64::decodedSize(entry.second));

const BufView out(reinterpret_cast< std::byte * >(ret.data()), ret.size());

const auto written = base64.decode(out, in);
const auto written = base64::decode(out, entry.second);
if (!written) {
return {};
}

ret.resize(written);

return ret;
}

static std::string encode(const Data::Entry &entry) {
const BufViewConst in(reinterpret_cast< const std::byte * >(entry.first.data()), entry.first.size());

std::string ret;
ret.resize(Base64::encode({}, in) - 1);

const BufView out(reinterpret_cast< std::byte * >(ret.data()), ret.size());
ret.resize(base64::encodedSize(in));

const auto written = Base64::encode(out, in);
const auto written = base64::encode(ret, in);
if (!written) {
return {};
}

ret.resize(written - 1);

return ret;
}

static uint8_t thread() {
Base64 base64;

std::random_device device;
std::mt19937 algorithm(device());
std::uniform_int_distribution< size_t > gen(0, std::tuple_size< Data::Table >() - 1);
Expand All @@ -73,7 +63,7 @@ static uint8_t thread() {

auto entry = Data::ascii[gen(algorithm)];

auto decoded = decode(base64, entry);
auto decoded = decode(entry);
if (decoded != entry.first) {
return 1;
}
Expand All @@ -85,7 +75,7 @@ static uint8_t thread() {

entry = Data::unicode[gen(algorithm)];

decoded = decode(base64, entry);
decoded = decode(entry);
if (decoded != entry.first) {
return 3;
}
Expand Down