Import Base64 implementation from old repo
This commit is contained in:
parent
e4735e2dfb
commit
f9c6e9e7cb
8 changed files with 266 additions and 1 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -1,4 +1,7 @@
|
|||
# ---> C++
|
||||
# Build dirs
|
||||
build/
|
||||
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
|
@ -31,4 +34,3 @@
|
|||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
|
|
8
CMake/Catch2.cmake
Normal file
8
CMake/Catch2.cmake
Normal 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
11
CMakeLists.txt
Normal 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
11
Garbage/CMakeLists.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
add_library(Garbage INTERFACE)
|
||||
|
||||
target_compile_features(Garbage
|
||||
INTERFACE
|
||||
cxx_std_20
|
||||
)
|
||||
|
||||
target_include_directories(Garbage
|
||||
INTERFACE
|
||||
"Include"
|
||||
)
|
143
Garbage/Include/Garbage/Base64.hpp
Normal file
143
Garbage/Include/Garbage/Base64.hpp
Normal 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
|
17
Tests/Base64/CMakeLists.txt
Normal file
17
Tests/Base64/CMakeLists.txt
Normal 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
70
Tests/Base64/Tests.cpp
Normal 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
3
Tests/CMakeLists.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
include(${PROJECT_SOURCE_DIR}/CMake/Catch2.cmake)
|
||||
|
||||
add_subdirectory(Base64)
|
Loading…
Add table
Add a link
Reference in a new issue