Import Base64 implementation from old repo

This commit is contained in:
TennesseeTrash 2025-06-09 01:40:47 +02:00
parent e4735e2dfb
commit f9c6e9e7cb
8 changed files with 266 additions and 1 deletions

4
.gitignore vendored
View file

@ -1,4 +1,7 @@
# ---> C++
# Build dirs
build/
# Prerequisites
*.d
@ -31,4 +34,3 @@
*.exe
*.out
*.app

8
CMake/Catch2.cmake Normal file
View file

@ -0,0 +1,8 @@
include(FetchContent)
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.5.0
)
FetchContent_MakeAvailable(Catch2)

11
CMakeLists.txt Normal file
View file

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.21)
project(Garbage
LANGUAGES C CXX
)
add_subdirectory(Garbage)
if(PROJECT_IS_TOP_LEVEL)
add_subdirectory(Tests)
endif()

11
Garbage/CMakeLists.txt Normal file
View file

@ -0,0 +1,11 @@
add_library(Garbage INTERFACE)
target_compile_features(Garbage
INTERFACE
cxx_std_20
)
target_include_directories(Garbage
INTERFACE
"Include"
)

View file

@ -0,0 +1,143 @@
#ifndef GARBAGE_BASE64_HPP
#define GARBAGE_BASE64_HPP
#include <array>
#include <cstdint>
#include <span>
#include <string>
#include <string_view>
#include <vector>
namespace Garbage::Base64
{
namespace Internal
{
struct SmallBuffer
{
public:
[[nodiscard]] constexpr
SmallBuffer()
: mStoredBits(0), mData(0)
{}
constexpr
void Enqueue(std::uint8_t byte, std::size_t bits = 8)
{
std::uint8_t mask = (1u << bits) - 1;
mData = (mData << bits) | (byte & mask);
mStoredBits += bits;
}
[[nodiscard]] constexpr
std::uint8_t Dequeue(std::size_t bits = 8)
{
std::uint64_t mask = (1u << bits) - 1;
mStoredBits -= bits;
std::uint64_t out = (mData >> mStoredBits) & mask;
return out;
}
[[nodiscard]] constexpr
std::size_t Size()
{
return mStoredBits;
}
[[nodiscard]] constexpr
std::size_t Capacity()
{
return 64;
}
private:
std::size_t mStoredBits;
std::uint64_t mData;
};
static constexpr auto EncodeLUT = std::array {
'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', '+', '/',
};
static constexpr std::size_t DecodeOffset = '+';
static constexpr auto DecodeLUT = std::array {
0b111'110, /* + */ 0b000'000, /*---*/ 0b000'000, /*---*/ 0b000'000, /*---*/
0b111'111, /* / */ 0b110'100, /* 0 */ 0b110'101, /* 1 */ 0b110'110, /* 2 */
0b110'111, /* 3 */ 0b111'000, /* 4 */ 0b111'001, /* 5 */ 0b111'010, /* 6 */
0b111'011, /* 7 */ 0b111'100, /* 8 */ 0b111'101, /* 9 */ 0b000'000, /*---*/
0b000'000, /*---*/ 0b000'000, /*---*/ 0b000'000, /*---*/ 0b000'000, /*---*/
0b000'000, /*---*/ 0b000'000, /*---*/ 0b000'000, /* A */ 0b000'001, /* B */
0b000'010, /* C */ 0b000'011, /* D */ 0b000'100, /* E */ 0b000'101, /* F */
0b000'110, /* G */ 0b000'111, /* H */ 0b001'000, /* I */ 0b001'001, /* J */
0b001'010, /* K */ 0b001'011, /* L */ 0b001'100, /* M */ 0b001'101, /* N */
0b001'110, /* O */ 0b001'111, /* P */ 0b010'000, /* Q */ 0b010'001, /* R */
0b010'010, /* S */ 0b010'011, /* T */ 0b010'100, /* U */ 0b010'101, /* V */
0b010'110, /* W */ 0b010'111, /* X */ 0b011'000, /* Y */ 0b011'001, /* Z */
0b000'000, /*---*/ 0b000'000, /*---*/ 0b000'000, /*---*/ 0b000'000, /*---*/
0b000'000, /*---*/ 0b000'000, /*---*/ 0b011'010, /* a */ 0b011'011, /* b */
0b011'100, /* c */ 0b011'101, /* d */ 0b011'110, /* e */ 0b011'111, /* f */
0b100'000, /* g */ 0b100'001, /* h */ 0b100'010, /* i */ 0b100'011, /* j */
0b100'100, /* k */ 0b100'101, /* l */ 0b100'110, /* m */ 0b100'111, /* n */
0b101'000, /* o */ 0b101'001, /* p */ 0b101'010, /* q */ 0b101'011, /* r */
0b101'100, /* s */ 0b101'101, /* t */ 0b101'110, /* u */ 0b101'111, /* v */
0b110'000, /* w */ 0b110'001, /* x */ 0b110'010, /* y */ 0b110'011, /* z */
};
}
[[nodiscard]] constexpr
std::string Encode(std::span<std::uint8_t> data) {
std::string result;
result.reserve(data.size() + data.size() / 2);
Internal::SmallBuffer buffer;
std::size_t i = 0;
while (i < data.size()) {
while (buffer.Capacity() - buffer.Size() >= 8 && i < data.size()) {
buffer.Enqueue(data[i++]);
}
while (buffer.Size() >= 6) {
result.push_back(Internal::EncodeLUT[buffer.Dequeue(6)]);
}
}
if (buffer.Size() > 0) {
std::size_t remainingBits = buffer.Size();
std::uint8_t data = (buffer.Dequeue(remainingBits) << (6 - remainingBits));
result.push_back(Internal::EncodeLUT[data]);
}
if (data.size() % 3) {
result.append(std::string(4 - (result.size() % 4), '='));
}
return result;
}
[[nodiscard]] constexpr
std::vector<std::uint8_t> Decode(std::string_view data) {
std::vector<std::uint8_t> result;
result.reserve(data.size());
std::size_t end = data.size();
end -= (data.size() > 0 && data[data.size() - 1] == '=');
end -= (data.size() > 1 && data[data.size() - 2] == '=');
Internal::SmallBuffer buffer;
std::size_t i = 0;
while (i < end) {
while (buffer.Capacity() - buffer.Size() >= 6 && i < end) {
buffer.Enqueue(Internal::DecodeLUT[data[i++] - Internal::DecodeOffset], 6);
}
while (buffer.Size() >= 8) {
result.push_back(buffer.Dequeue(8));
}
}
return result;
}
}
#endif // GARBAGE_BASE64_HPP

View file

@ -0,0 +1,17 @@
add_executable(TestBase64)
target_compile_features(TestBase64
PRIVATE
cxx_std_20
)
target_link_libraries(TestBase64
PRIVATE
Garbage
Catch2::Catch2WithMain
)
target_sources(TestBase64
PRIVATE
Tests.cpp
)

70
Tests/Base64/Tests.cpp Normal file
View file

@ -0,0 +1,70 @@
#include <catch2/catch_test_macros.hpp>
#include <Garbage/Base64.hpp>
TEST_CASE("Base64 encoding/decoding tests")
{
SECTION("Empty span")
{
std::vector<std::uint8_t> data;
std::string encoded = Garbage::Base64::Encode(data);
REQUIRE(encoded.size() == 0);
std::vector<std::uint8_t> decoded = Garbage::Base64::Decode(encoded);
REQUIRE(encoded.size() == 0);
}
SECTION("Encoding short data")
{
std::string source("Many hands make light work.");
std::vector<std::uint8_t> data(source.begin(), source.end());
std::string encoded = Garbage::Base64::Encode(data);
REQUIRE(encoded == "TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu");
}
SECTION("Decoding short data")
{
std::string data("TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcmsu");
std::vector<std::uint8_t> decoded = Garbage::Base64::Decode(data);
std::string destination(decoded.begin(), decoded.end());
REQUIRE(destination == "Many hands make light work.");
}
SECTION("Encoding with a padding char")
{
std::string source("With a padding");
std::vector<std::uint8_t> data(source.begin(), source.end());
std::string encoded = Garbage::Base64::Encode(data);
REQUIRE(encoded == "V2l0aCBhIHBhZGRpbmc=");
}
SECTION("Decoding with a padding char")
{
std::string data("V2l0aCBhIHBhZGRpbmc=");
std::vector<std::uint8_t> decoded = Garbage::Base64::Decode(data);
std::string destination(decoded.begin(), decoded.end());
REQUIRE(destination == "With a padding");
}
SECTION("Encoding with two padding chars")
{
std::string source("With two padding");
std::vector<std::uint8_t> data(source.begin(), source.end());
std::string encoded = Garbage::Base64::Encode(data);
REQUIRE(encoded == "V2l0aCB0d28gcGFkZGluZw==");
}
SECTION("Decoding with two padding chars")
{
std::string data("V2l0aCB0d28gcGFkZGluZw==");
std::vector<std::uint8_t> decoded = Garbage::Base64::Decode(data);
std::string destination(decoded.begin(), decoded.end());
REQUIRE(destination == "With two padding");
}
}

3
Tests/CMakeLists.txt Normal file
View file

@ -0,0 +1,3 @@
include(${PROJECT_SOURCE_DIR}/CMake/Catch2.cmake)
add_subdirectory(Base64)