mirror of
https://github.com/tobiaslocker/base64.git
synced 2024-12-15 08:59:57 +08:00
Merge pull request #6 from tvercaut/master
Ported unit tests from https://github.com/matheusgomes28/base64pp
This commit is contained in:
commit
d354224643
@ -29,3 +29,29 @@ target_include_directories(roundtrip_test PRIVATE include)
|
||||
|
||||
enable_testing()
|
||||
add_test(NAME roundtrip_test COMMAND roundtrip_test)
|
||||
|
||||
# Add some more tests
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG 750d67d809700ae8fca6d610f7b41b71aa161808
|
||||
SYSTEM
|
||||
)
|
||||
# For Windows: Prevent overriding the parent project's compiler/linker settings
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
|
||||
set_target_properties(gtest PROPERTIES CXX_CLANG_TIDY "")
|
||||
set_target_properties(gtest_main PROPERTIES CXX_CLANG_TIDY "")
|
||||
set_target_properties(gmock PROPERTIES CXX_CLANG_TIDY "")
|
||||
set_target_properties(gmock_main PROPERTIES CXX_CLANG_TIDY "")
|
||||
|
||||
add_executable(base64_tests test/base64_tests.cpp)
|
||||
target_include_directories(base64_tests PRIVATE include)
|
||||
|
||||
target_link_libraries(base64_tests PRIVATE GTest::gtest GTest::gtest_main)
|
||||
|
||||
add_test(NAME base64_tests COMMAND base64_tests)
|
||||
|
||||
|
||||
|
10
README.md
10
README.md
@ -18,6 +18,12 @@ int main() {
|
||||
```
|
||||
|
||||
## Notes
|
||||
This library relies on C++17.
|
||||
This library relies on C++17.
|
||||
|
||||
A different, unrelated C++20 library for base64 encoding/decoding can be found at https://github.com/matheusgomes28/base64pp
|
||||
A benchmark of various c/c++ base64 implementations can be found at https://github.com/gaspardpetit/base64/
|
||||
|
||||
There are many implementations available and it may be worth looking at those. For example, a different, unrelated, C++20 library for base64 encoding/decoding can be found at https://github.com/matheusgomes28/base64pp
|
||||
|
||||
There is also an implementation that works with older C++ versions available at https://github.com/ReneNyffenegger/cpp-base64
|
||||
|
||||
There are also some more generic libraries available such as https://github.com/azawadzki/base-n
|
||||
|
@ -70,7 +70,17 @@ inline OutputBuffer decode_into(std::string_view data) {
|
||||
size_t counter = 0;
|
||||
uint32_t bit_stream = 0;
|
||||
OutputBuffer decoded;
|
||||
decoded.reserve(std::size(data));
|
||||
const size_t encoded_size = std::size(data);
|
||||
if ((encoded_size % 4) != 0) {
|
||||
throw std::runtime_error{
|
||||
"Invalid base64 encoded data - Size not divisible by 4"};
|
||||
}
|
||||
const size_t numlasteqs = std::count(data.rbegin(), data.rbegin() + 4, '=');
|
||||
if (numlasteqs > 2) {
|
||||
throw std::runtime_error{
|
||||
"Invalid base64 encoded data - Found more than 2 padding signs"};
|
||||
}
|
||||
decoded.reserve(encoded_size);
|
||||
for (std::string_view::value_type c : data) {
|
||||
auto const num_val = base64_chars.find(c);
|
||||
if (num_val != std::string::npos) {
|
||||
@ -87,7 +97,8 @@ inline OutputBuffer decode_into(std::string_view data) {
|
||||
bit_stream = 0;
|
||||
}
|
||||
} else if (c != '=') {
|
||||
throw std::runtime_error{"Invalid base64 encoded data"};
|
||||
throw std::runtime_error{
|
||||
"Invalid base64 encoded data - Found invalid character"};
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
|
330
test/base64_tests.cpp
Normal file
330
test/base64_tests.cpp
Normal file
@ -0,0 +1,330 @@
|
||||
// Test suite ported from https://github.com/matheusgomes28/base64pp
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "../include/base64.hpp"
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Encode, EncodesEmpty) {
|
||||
std::string const expected{};
|
||||
std::string const actual{base64::to_base64({})};
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Encode, EncodesThreeBytesZeros) {
|
||||
std::array<std::uint8_t, 3> const input{0x00, 0x00, 0x00};
|
||||
auto const expected{"AAAA"};
|
||||
auto const actual{base64::encode_into<std::string>(begin(input), end(input))};
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Encode, EncodesThreeBytesRandom) {
|
||||
std::array<std::uint8_t, 3> const input{0xFE, 0xE9, 0x72};
|
||||
auto const expected{"/uly"};
|
||||
auto const actual{base64::encode_into<std::string>(begin(input), end(input))};
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Encode, EncodesTwoBytes) {
|
||||
std::array<std::uint8_t, 2> const input{0x00, 0x00};
|
||||
auto const expected{"AAA="};
|
||||
auto const actual{base64::encode_into<std::string>(begin(input), end(input))};
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Encode, EncodesOneByte) {
|
||||
std::array<std::uint8_t, 1> const input{0x00};
|
||||
auto const expected{"AA=="};
|
||||
auto const actual{base64::encode_into<std::string>(begin(input), end(input))};
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Encode, EncodesFourBytes) {
|
||||
std::array<std::uint8_t, 4> const input{0x74, 0x68, 0x65, 0x20};
|
||||
auto const expected{"dGhlIA=="};
|
||||
auto const actual{base64::encode_into<std::string>(begin(input), end(input))};
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Encode, EncodesFiveBytes) {
|
||||
std::array<std::uint8_t, 5> const input{0x20, 0x62, 0x72, 0x6f, 0x77};
|
||||
auto const expected{"IGJyb3c="};
|
||||
auto const actual{base64::encode_into<std::string>(begin(input), end(input))};
|
||||
ASSERT_EQ(actual, expected);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Encode, EncodesSixBytes) {
|
||||
std::array<std::uint8_t, 6> const input{0x20, 0x6a, 0x75, 0x6d, 0x70, 0x73};
|
||||
auto const expected{"IGp1bXBz"};
|
||||
auto const actual{base64::encode_into<std::string>(begin(input), end(input))};
|
||||
ASSERT_EQ(actual, expected);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Encode, EncodesBrownFox) {
|
||||
std::array<std::uint8_t, 43> const input{
|
||||
0x74, 0x68, 0x65, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x20, 0x62,
|
||||
0x72, 0x6f, 0x77, 0x6e, 0x20, 0x66, 0x6f, 0x78, 0x20, 0x6a, 0x75,
|
||||
0x6d, 0x70, 0x73, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x74, 0x68,
|
||||
0x65, 0x20, 0x6c, 0x61, 0x7a, 0x79, 0x20, 0x64, 0x6f, 0x67};
|
||||
|
||||
auto const expected{
|
||||
"dGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="};
|
||||
auto const actual{base64::encode_into<std::string>(begin(input), end(input))};
|
||||
ASSERT_EQ(actual, expected);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Encode, EncodesBrownFastFoxNullInMiddle) {
|
||||
std::array<std::uint8_t, 45> const input{
|
||||
0x74, 0x68, 0x65, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x21, 0x20, 0x62,
|
||||
0x72, 0x6f, 0x77, 0x6e, 0x20, 0x66, 0x6f, 0x78, 0x20, 0x6a, 0x75, 0x6d,
|
||||
0x70, 0x73, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x00,
|
||||
0x20, 0x6c, 0x61, 0x7a, 0x79, 0x20, 0x64, 0x6f, 0x67};
|
||||
|
||||
auto const expected{
|
||||
"dGhlIHF1aWNrISBicm93biBmb3gganVtcHMgb3ZlciB0aGUAIGxhenkgZG9n"};
|
||||
auto const actual{base64::encode_into<std::string>(begin(input), end(input))};
|
||||
ASSERT_EQ(actual, expected);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, FailDecodeOneString) {
|
||||
std::string const input{"1"};
|
||||
ASSERT_THROW(base64::from_base64(input), std::runtime_error);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, FailDecodeOneStringPadded) {
|
||||
std::string const input{"1==="};
|
||||
ASSERT_THROW(base64::from_base64(input), std::runtime_error);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, FailDecodeOneCharRemaining) {
|
||||
std::string const input{"something"};
|
||||
ASSERT_THROW(base64::from_base64(input), std::runtime_error);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, FailDecodeNonSize4Bigger) {
|
||||
std::string const input{"SomethingEntirelyDifferent"};
|
||||
ASSERT_THROW(base64::from_base64(input), std::runtime_error);
|
||||
// For the record - expected decoding if relaxed checks
|
||||
// std::vector<std::uint8_t> const expected{0x4A, 0x89, 0x9E, 0xB6, 0x18,
|
||||
// 0xA7, 0x80, 0x49, 0xED, 0x8A, 0xB7, 0xA5,
|
||||
// 0xC8, 0x38, 0x9F, 0x7D, 0xEA, 0xDE, 0x9E};
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, FailDecodeNonBase64Short) {
|
||||
std::string const input{"a aa"};
|
||||
ASSERT_THROW(base64::from_base64(input), std::runtime_error);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, FailDecodeNonBase64Longer) {
|
||||
std::string const input{"aaa`aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"};
|
||||
ASSERT_THROW(base64::from_base64(input), std::runtime_error);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, DecodesMissingTwoPads0) {
|
||||
std::string const input{"12"};
|
||||
ASSERT_THROW(base64::from_base64(input), std::runtime_error);
|
||||
// For the record - expected decoding if relaxed checks
|
||||
// std::vector<std::uint8_t> const expected{0xD7};
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, DecodesMissingTwoPads1) {
|
||||
std::string const input = "AA";
|
||||
ASSERT_THROW(base64::from_base64(input), std::runtime_error);
|
||||
// For the record - expected decoding if relaxed checks
|
||||
// std::vector<std::uint8_t> const expected{0x00};
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, DecodesMissingOnePad0) {
|
||||
std::string const input = "AAA";
|
||||
ASSERT_THROW(base64::from_base64(input), std::runtime_error);
|
||||
// For the record - expected decoding if relaxed checks
|
||||
// std::vector<std::uint8_t> const expected{0x00, 0x00};
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, DecodesMissingOnePad1) {
|
||||
std::string const input{"12a"};
|
||||
ASSERT_THROW(base64::from_base64(input), std::runtime_error);
|
||||
// For the record - expected decoding if relaxed checks
|
||||
// std::vector<std::uint8_t> const expected{0xD7, 0x66};
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, DecodesMissingIssueExample) {
|
||||
std::string const input = "eyJuYW1lIjoiSm9obiBEb2UifQ";
|
||||
ASSERT_THROW(base64::from_base64(input), std::runtime_error);
|
||||
// For the record - expected decoding if relaxed checks
|
||||
// std::string const expected_str = R"({"name":"John Doe"})";
|
||||
// See https://github.com/matheusgomes28/base64pp/issues/84
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, DecodesEmptyString) {
|
||||
std::string const input{};
|
||||
std::string expected{};
|
||||
auto const actual{base64::from_base64("")};
|
||||
|
||||
ASSERT_EQ(expected, actual);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, DecodesZeroArray) {
|
||||
std::string const input{"AAAA"};
|
||||
std::vector<std::uint8_t> const expected{0x00, 0x00, 0x00};
|
||||
auto const actual{base64::from_base64(input)};
|
||||
|
||||
ASSERT_EQ(actual, std::string(expected.begin(), expected.end()));
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, DecodesZeroArrayTwice) {
|
||||
std::string const input{"AAAAAAAA"};
|
||||
std::vector<std::uint8_t> const expected{0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
auto const actual{base64::from_base64(input)};
|
||||
|
||||
ASSERT_EQ(actual, std::string(expected.begin(), expected.end()));
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, DecodesZeroArrayOneByte) {
|
||||
std::string const input{"AA=="};
|
||||
std::vector<std::uint8_t> const expected{0x00};
|
||||
auto const actual{base64::from_base64(input)};
|
||||
|
||||
ASSERT_EQ(actual, std::string(expected.begin(), expected.end()));
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, DecodesZeroArrayTwoBytes) {
|
||||
std::string const input{"AAA="};
|
||||
std::vector<std::uint8_t> const expected{0x00, 0x00};
|
||||
auto const actual{base64::from_base64(input)};
|
||||
|
||||
ASSERT_EQ(actual, std::string(expected.begin(), expected.end()));
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64Decode, DecodesQuickFox) {
|
||||
std::string const input{
|
||||
"VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="};
|
||||
std::vector<std::uint8_t> const expected{
|
||||
0x54, 0x68, 0x65, 0x20, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x20, 0x62,
|
||||
0x72, 0x6f, 0x77, 0x6e, 0x20, 0x66, 0x6f, 0x78, 0x20, 0x6a, 0x75,
|
||||
0x6d, 0x70, 0x73, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x20, 0x74, 0x68,
|
||||
0x65, 0x20, 0x6c, 0x61, 0x7a, 0x79, 0x20, 0x64, 0x6f, 0x67};
|
||||
auto const actual{base64::from_base64(input)};
|
||||
ASSERT_EQ(actual, std::string(expected.begin(), expected.end()));
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64RoundTripTests, AllPossibleBytes) {
|
||||
std::vector<std::uint8_t> all_possible_bytes;
|
||||
for (std::size_t i = 0; i <= 255; ++i) {
|
||||
all_possible_bytes.push_back(static_cast<std::uint8_t>(i));
|
||||
}
|
||||
|
||||
auto const encode_string = base64::encode_into<std::string>(
|
||||
begin(all_possible_bytes), end(all_possible_bytes));
|
||||
auto const decoded_bytes = base64::from_base64(encode_string);
|
||||
// ASSERT_TRUE(decoded_bytes);
|
||||
ASSERT_EQ(std::string(all_possible_bytes.begin(), all_possible_bytes.end()),
|
||||
decoded_bytes);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64RoundTripTests, ExhaustiveTests) {
|
||||
std::vector<std::string> const base64_strings = {
|
||||
"YW55IGNhcm5hbCBwbGVhcw==",
|
||||
"bGVnYWwgcGFzcw==",
|
||||
"dGVzdCBzdHJpbmc=",
|
||||
"bGVnYWwgcHJvdmlkZXI=",
|
||||
"ZW5vdWdoIHRoZSBzYW1lIG9mIHRoZSBwbGFjZQ==",
|
||||
"YW5vdGhlciB0aGUgc3RyYWlnaHQ=",
|
||||
"d2FzIG1lIGFkZHJlc3MgcHJvdmlkZXI=",
|
||||
"YWJvdXQgdGhlIG1hc3RlciBvZiB0aGUgZGFtYWdl",
|
||||
"ZW50aXJlIHRoYXQgYnJvdWdodCBvZiB0aGUgbW9uZXk=",
|
||||
"bGVnYWwgc2VjdXJpdHk=",
|
||||
"YmFzaWMgZ29vZCBvZiB0aGUgcGFkIHN0cmluZw==",
|
||||
"ZGVsZXRlIHN0cmluZyBvZiB0aGUgc3RyYWlnaHQ=",
|
||||
"YnJvdWdodCBvZiB0aGUgcGFkIGZvbGRlciBvZiB0aGUgZGFtYWdl",
|
||||
"aW50ZXJmYWNlIHN0cmluZw==",
|
||||
"Y29uc29sZS1tZS1jb21wYW55",
|
||||
"aW5mb3JtYXRpb24tbWVkaWE=",
|
||||
"c3RhdHVzLXNlY3VyZQ==",
|
||||
"Y3JlYXRlLWNvbXBhbnktc3RyaW5n",
|
||||
"b3JkZXItbGVhZGVy",
|
||||
"Y2F0YWxvZy1wcm9maWxl",
|
||||
"dGVzdC1jb25zdWx0aW5n",
|
||||
"YnJvdWdodC1sZWFkZXI=",
|
||||
"YXNzaWduLW1lY2hhbmlzbQ==",
|
||||
"bGVnYWwtY29udGFpbmVy",
|
||||
"ZW1haWwtY29udGFpbmVy",
|
||||
"aW5zdGFuY2UtY29udGFpbmVy",
|
||||
"dGVzdC1jb21wYW55LWFuZC1wcm9maWxl",
|
||||
"YmFzZTY0LWJhc2U=",
|
||||
"cGFzc3dvcmQ=",
|
||||
"Zm9vYmFy",
|
||||
"Y29vbC1iYXNl",
|
||||
"YmFzZTY0LXNlY3VyZQ==",
|
||||
"aW50ZXJ2YWw=",
|
||||
"dGhlLW1hc3Rlci1vZi10aGUtZGFtYWdl",
|
||||
"c2FtZS1wbGFjZS1vZi10aGUtZGFtYWdl",
|
||||
"aGFzaC1zb21ldGhpbmc="};
|
||||
|
||||
for (auto const& b64_string : base64_strings) {
|
||||
auto const decoded = base64::from_base64(b64_string);
|
||||
// ASSERT_TRUE(decoded);
|
||||
|
||||
auto const encoded_round_trip =
|
||||
base64::encode_into<std::string>(begin(decoded), end(decoded));
|
||||
ASSERT_EQ(encoded_round_trip, b64_string);
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE
|
||||
TEST(Base64OverloadTests, EncodesString1) {
|
||||
std::array<std::pair<std::string, std::string>, 11> const test_cases = {
|
||||
{{"", ""},
|
||||
{"Hello, World!", "SGVsbG8sIFdvcmxkIQ=="},
|
||||
{"abcdefghijklmnopqrstuvwxyz0123456789\\`!\"£$%^&*()_+",
|
||||
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5XGAhIsKjJCVeJiooKV8r"},
|
||||
{"Base64 encoding", "QmFzZTY0IGVuY29kaW5n"},
|
||||
{"I love coding", "SSBsb3ZlIGNvZGluZw=="},
|
||||
{"C++23 is awesome", "QysrMjMgaXMgYXdlc29tZQ=="},
|
||||
{"This is a sample", "VGhpcyBpcyBhIHNhbXBsZQ=="},
|
||||
{"Base64 is useful", "QmFzZTY0IGlzIHVzZWZ1bA=="},
|
||||
{"Encode and decode", "RW5jb2RlIGFuZCBkZWNvZGU="},
|
||||
{"Data encryption", "RGF0YSBlbmNyeXB0aW9u"},
|
||||
{"Th3 Quickk Br0wn f0x", "VGgzIFF1aWNrayAgQnIwd24gZjB4"}}};
|
||||
|
||||
for (auto const& [input, expected] : test_cases) {
|
||||
auto const actual = base64::to_base64(input);
|
||||
ASSERT_EQ(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
Loading…
Reference in New Issue
Block a user