diff --git a/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj b/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj index 2d035579..e50b20b4 100644 --- a/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj +++ b/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj @@ -205,6 +205,7 @@ + @@ -315,6 +316,7 @@ + diff --git a/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj.filters b/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj.filters index f303ecdf..10e0c0c8 100644 --- a/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj.filters +++ b/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj.filters @@ -255,6 +255,9 @@ Source Files + + Source Files + Source Files @@ -581,6 +584,9 @@ Header Files + + Header Files + Header Files diff --git a/build/visualstudio/libtoolchain/libtoolchain.vcxproj b/build/visualstudio/libtoolchain/libtoolchain.vcxproj index 02ebb33e..7561e968 100644 --- a/build/visualstudio/libtoolchain/libtoolchain.vcxproj +++ b/build/visualstudio/libtoolchain/libtoolchain.vcxproj @@ -231,6 +231,8 @@ + + @@ -343,6 +345,7 @@ + diff --git a/build/visualstudio/libtoolchain/libtoolchain.vcxproj.filters b/build/visualstudio/libtoolchain/libtoolchain.vcxproj.filters index 3b315cbc..5e4631d0 100644 --- a/build/visualstudio/libtoolchain/libtoolchain.vcxproj.filters +++ b/build/visualstudio/libtoolchain/libtoolchain.vcxproj.filters @@ -178,6 +178,9 @@ Source Files\crypto\detail + + Source Files\io + Source Files\io @@ -570,6 +573,12 @@ Header Files\tc + + Header Files\tc\encode + + + Header Files\tc + Header Files\tc\io @@ -742,6 +751,9 @@ {07fdaf97-dd39-4b5e-8679-5f3a45ff300e} + + + {2b186524-afb8-4bab-bd5a-7fb9ee579131} {db3218f8-61c4-444c-9758-eaf0a3fc7cb5} @@ -767,6 +779,9 @@ {e80dca43-1a2b-4037-8751-245a3b07d04a} + + {aba2432e-d6dc-403e-84f4-4e2c7d24e681} + {b6397317-93c5-48d9-a2bd-107761509eb7} diff --git a/include/tc.h b/include/tc.h index 08106f50..578d9076 100644 --- a/include/tc.h +++ b/include/tc.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include diff --git a/include/tc/encode.h b/include/tc/encode.h new file mode 100644 index 00000000..991a7bba --- /dev/null +++ b/include/tc/encode.h @@ -0,0 +1,13 @@ + /** + * @file encode.h + * @brief Declaration of the encode/decode library + */ +#pragma once +#include +#include + + /** + * @namespace tc::encode + * @brief Namespace of the encode/decode library + */ +#include \ No newline at end of file diff --git a/include/tc/encode/Base64Util.h b/include/tc/encode/Base64Util.h new file mode 100644 index 00000000..31f02079 --- /dev/null +++ b/include/tc/encode/Base64Util.h @@ -0,0 +1,116 @@ + /** + * @file Base64Util.h + * @brief Declaration of tc::encode::Base64Util + * @author Jack (jakcron) + * @version 0.1 + * @date 2024/09/29 + **/ +#pragma once +#include +#include + +namespace tc { namespace encode { + + /** + * @class Base64Util + * @brief A collection of utilities to encode/decode binary data/strings to base64 and vice-versa. + **/ +class Base64Util +{ +public: + /** + * @brief Encode byte array as base64 string. + * + * @param[in] data Byte array to encode. + * @param[in] size Size of byte array @p data. + * + * @return Converted byte array as base64 string. + * + * @post String returned will be empty if the byte array cannot be encoded. + **/ + static std::string encodeDataAsBase64(const byte_t* data, size_t size); + + /** + * @brief Encode byte array as base64 string. + * + * @param[in] data tc::ByteData containing byte array to encode. + * + * @return Converted byte array as base64 string. + * + * @post String returned will be empty if the byte array cannot be encoded. + **/ + static std::string encodeDataAsBase64(const tc::ByteData& data); + + /** + * @brief Encode string as base64 string. + * + * @param[in] str String to encode. + * @param[in] size Size in bytes of string @p str. + * + * @return Converted string as base64 string. + * + * @post String returned will be empty if the string cannot be encoded. + **/ + static std::string encodeStringAsBase64(const char* str, size_t size); + + /** + * @brief Encode string as base64 string. + * + * @param[in] str String to encode. + * + * @return Converted string as base64 string. + * + * @post String returned will be empty if the string cannot be encoded. + **/ + static std::string encodeStringAsBase64(const std::string& str); + + /** + * @brief Decode base64 string to bytes. + * + * @param[in] str Base64 string to decode. + * @param[in] size Size of string @p str. + * + * @return Converted base64 string as bytes. + * + * @post String returned will be empty if the base64 string cannot be decoded. + **/ + static tc::ByteData decodeBase64AsData(const char* str, size_t size); + + /** + * @brief Decode base64 string to bytes. + * + * @param[in] str Base64 string to decode. + * + * @return Converted base64 string as bytes. + * + * @post String returned will be empty if the base64 string cannot be decoded. + **/ + static tc::ByteData decodeBase64AsData(const std::string& str); + + /** + * @brief Decode base64 string to ASCII string. + * @note this is provided for completeness, and only supports ASCII strings + * + * @param[in] str Base64 string to decode. + * @param[in] size Size of string @p str. + * + * @return Converted base64 string as ASCII string. + * + * @post String returned will be empty if the base64 string cannot be decoded. + **/ + static std::string decodeBase64AsString(const char* str, size_t size); + + /** + * @brief Decode base64 string to ASCII string. + * @note this is provided for completeness, and only supports ASCII strings + * + * @param[in] str Base64 string to decode. + * + * @return Converted base64 string as ASCII string. + * + * @post String returned will be empty if the base64 string cannot be decoded. + **/ + static std::string decodeBase64AsString(const std::string& str); +}; + +}} // namespace tc::encode diff --git a/makefile b/makefile index 3b4109a9..a0fce2df 100644 --- a/makefile +++ b/makefile @@ -8,7 +8,7 @@ PROJECT_NAME = libtoolchain # Project Relative Paths PROJECT_PATH = $(CURDIR) PROJECT_SRC_PATH = src -PROJECT_SRC_SUBDIRS = $(PROJECT_SRC_PATH) $(PROJECT_SRC_PATH)/io $(PROJECT_SRC_PATH)/string $(PROJECT_SRC_PATH)/cli $(PROJECT_SRC_PATH)/os $(PROJECT_SRC_PATH)/crypto $(PROJECT_SRC_PATH)/crypto/detail +PROJECT_SRC_SUBDIRS = $(PROJECT_SRC_PATH) $(PROJECT_SRC_PATH)/io $(PROJECT_SRC_PATH)/string $(PROJECT_SRC_PATH)/cli $(PROJECT_SRC_PATH)/os $(PROJECT_SRC_PATH)/encode $(PROJECT_SRC_PATH)/crypto $(PROJECT_SRC_PATH)/crypto/detail PROJECT_INCLUDE_PATH = include PROJECT_TESTSRC_PATH = test PROJECT_TESTSRC_SUBDIRS = $(PROJECT_TESTSRC_PATH) diff --git a/src/encode/Base64Util.cpp b/src/encode/Base64Util.cpp new file mode 100644 index 00000000..19842190 --- /dev/null +++ b/src/encode/Base64Util.cpp @@ -0,0 +1,96 @@ +#include + +#include + +#include + +inline std::string byteDataAsString(const tc::ByteData& data) +{ + std::string str = ""; + + if (data.size() > 0) + { + str = std::string((const char*)data.data()); + } + + for (size_t i = 0; i < str.size(); i++) + { + if ( ! std::isprint(static_cast(str[i])) ) + { + str = ""; + break; + } + } + + return str; +} + +std::string tc::encode::Base64Util::encodeDataAsBase64(const byte_t* data, size_t size) +{ + if (data == nullptr && size != 0) + { + return ""; + } + + size_t enc_len = 0; + + mbedtls_base64_encode(nullptr, enc_len, &enc_len, data, size); + + tc::ByteData enc_data = tc::ByteData(enc_len); + + int mbedtls_ret = mbedtls_base64_encode(enc_data.data(), enc_data.size(), &enc_len, data, size); + if (mbedtls_ret != 0) + enc_data = tc::ByteData(); + + return byteDataAsString(enc_data); +} + +std::string tc::encode::Base64Util::encodeDataAsBase64(const tc::ByteData& data) +{ + return encodeDataAsBase64(data.data(), data.size()); +} + +std::string tc::encode::Base64Util::encodeStringAsBase64(const char* str, size_t size) +{ + return encodeDataAsBase64((const byte_t*)str, size); +} + +std::string tc::encode::Base64Util::encodeStringAsBase64(const std::string& str) +{ + return encodeDataAsBase64((const byte_t*)str.c_str(), str.size()); +} + +tc::ByteData tc::encode::Base64Util::decodeBase64AsData(const char* str, size_t size) +{ + if (str == nullptr && size != 0) + { + return tc::ByteData(); + } + + size_t dec_len = 0; + + mbedtls_base64_decode(nullptr, dec_len, &dec_len, (const byte_t*)str, size); + + tc::ByteData dec_data = tc::ByteData(dec_len); + + int mbedtls_ret = mbedtls_base64_decode(dec_data.data(), dec_data.size(), &dec_len, (const byte_t*)str, size); + if (mbedtls_ret != 0) + dec_data = tc::ByteData(); + + return dec_data; +} + +tc::ByteData tc::encode::Base64Util::decodeBase64AsData(const std::string& str) +{ + return decodeBase64AsData(str.c_str(), str.size()); +} + +std::string tc::encode::Base64Util::decodeBase64AsString(const char* str, size_t size) +{ + return byteDataAsString(decodeBase64AsData(str, size)); +} + +std::string tc::encode::Base64Util::decodeBase64AsString(const std::string& str) +{ + return byteDataAsString(decodeBase64AsData(str.data(), str.size())); +} \ No newline at end of file diff --git a/test/encode_Base64Util_TestClass.cpp b/test/encode_Base64Util_TestClass.cpp new file mode 100644 index 00000000..c51da3d6 --- /dev/null +++ b/test/encode_Base64Util_TestClass.cpp @@ -0,0 +1,371 @@ +#include "encode_Base64Util_TestClass.h" + +#include + +//--------------------------------------------------------- + +encode_Base64Util_TestClass::encode_Base64Util_TestClass() : + mTestTag("tc::encode::Base64Util"), + mTestResults() +{ +} + +void encode_Base64Util_TestClass::runAllTests(void) +{ + testEncodeDataAsBase64(); + testEncodeStringAsBase64(); + testDecodeBase64AsData(); + testDecodeBase64AsString(); +} + +const std::string& encode_Base64Util_TestClass::getTestTag() const +{ + return mTestTag; +} + +const std::vector& encode_Base64Util_TestClass::getTestResults() const +{ + return mTestResults; +} + +//--------------------------------------------------------- + +void encode_Base64Util_TestClass::testEncodeDataAsBase64() +{ + TestResult test; + test.test_name = "testEncodeDataAsBase64"; + test.result = "NOT RUN"; + test.comments = ""; + + try + { + struct TestCase + { + std::string test_name; + std::string in_string; + tc::ByteData in_data; + std::string out_base64; + }; + + // create happy path tests + std::vector tests = + { + { "empty data", std::string(), tc::ByteData(), std::string()}, + { "single space", " ", tc::cli::FormatUtil::hexStringToBytes("20"), "IA=="}, + { "ascii string", "The quick brown fox jumps over the lazy dog.", tc::cli::FormatUtil::hexStringToBytes("54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f672e"), "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4="}, + { "512 bytes binary data", std::string(), tc::cli::FormatUtil::hexStringToBytes("85d278fb18be42a62a56b495d4ab56a803fad5a1c66153b0c8a8628c3222fe55c500fa9721d5bfebff31ec59dcf810ac4bd0d2939ae045e7fcc34a432b45aca93e2a7a35b8208e75e1477d02bfd2175ae4a2d68dbf1a882249ca3f2b36586db700fb47e954f9233c9a2227e850957bbb1f3da6e9b4693d38e09434a691451b897c8b1fbbd5de4921e1f84fc136dd1b9297ad5e065556cd2ce52ec6eed8133d83bfc4e54c7d715de5e2b7eed9898b9b5eebbd5d07ae22f83d87218f7fe7355b5b6379e4a3eb27d48a9e042aa637063aac9a0b7bc104758bee61321183c0bb1ffb8ceb8c3d0cbbd5a55fe7809df89bf0883d8d7c762673fbfc5fceb6b14b4b1a828da5059cb090a2286ae7efc64b09e033d70cb6a528c54cea3d90f35b8fdfd73a1e85604c827cf5719b488e37b8cda2e2baba5cec43b1d031e7e4a47f3baedb00c037779a6f0e9fab5c9c3c84236458378847acd174083570c628074417ebba551853299eea400dfa95a8aff5f1fd9c314225365023ee31a03930c0029d57feb81417d57f9f45f225faa3790c0b239891c82151a7449507cab70376975ba9f2e68f5e37544f848faf875b9e24dccb9c556aae2a57a7f369581d50dfdf06fdff27fd2107db080c4d9e1c100427a1493ddb5e80e43f943bbbad9113448b658a5a5cdefd70e57d0b28e2bd942a978179eb88d6661b30f2b5b346fff1d0a5c9b93d2d"), "hdJ4+xi+QqYqVrSV1KtWqAP61aHGYVOwyKhijDIi/lXFAPqXIdW/6/8x7Fnc+BCsS9DSk5rgRef8w0pDK0WsqT4qejW4II514Ud9Ar/SF1rkotaNvxqIIknKPys2WG23APtH6VT5IzyaIifoUJV7ux89pum0aT044JQ0ppFFG4l8ix+71d5JIeH4T8E23RuSl61eBlVWzSzlLsbu2BM9g7/E5Ux9cV3l4rfu2YmLm17rvV0HriL4PYchj3/nNVtbY3nko+sn1IqeBCqmNwY6rJoLe8EEdYvuYTIRg8C7H/uM64w9DLvVpV/ngJ34m/CIPY18diZz+/xfzraxS0sago2lBZywkKIoaufvxksJ4DPXDLalKMVM6j2Q81uP39c6HoVgTIJ89XGbSI43uM2i4rq6XOxDsdAx5+Skfzuu2wDAN3eabw6fq1ycPIQjZFg3iEes0XQINXDGKAdEF+u6VRhTKZ7qQA36laiv9fH9nDFCJTZQI+4xoDkwwAKdV/64FBfVf59F8iX6o3kMCyOYkcghUadElQfKtwN2l1up8uaPXjdUT4SPr4dbniTcy5xVaq4qV6fzaVgdUN/fBv3/J/0hB9sIDE2eHBAEJ6FJPdtegOQ/lDu7rZETRItlilpc3v1w5X0LKOK9lCqXgXnriNZmGzDytbNG//HQpcm5PS0="}, + }; + + // run error path tests + { + std::string out = tc::encode::Base64Util::encodeDataAsBase64(nullptr, 0xdeadbeef); + + if (out != "") + { + throw tc::TestException(fmt::format("encodeDataAsBase64() did not return an empty string for invalid input: data=nullptr, size=0xdeadbeef")); + } + } + + // run happy path tests + for (auto test = tests.begin(); test != tests.end(); test++) + { + std::string out_1 = tc::encode::Base64Util::encodeDataAsBase64(test->in_data); + std::string out_2 = tc::encode::Base64Util::encodeDataAsBase64(test->in_data.data(), test->in_data.size()); + + if (out_1 != test->out_base64) + { + throw tc::TestException(fmt::format("Test({:s}) to convert data({:s}) to base64, returned \"{:s}\" (should be: \"{:s}\")", test->test_name, tc::cli::FormatUtil::formatBytesAsString(test->in_data, false, ""), out_1, test->out_base64)); + } + + if (out_2 != test->out_base64) + { + throw tc::TestException(fmt::format("Test({:s}) to convert data({:s}), size({:d}) to base64, returned \"{:s}\" (should be: \"{:s}\")", test->test_name, tc::cli::FormatUtil::formatBytesAsString(test->in_data.data(), test->in_data.size(), false, ""), test->in_data.size(), out_2, test->out_base64)); + } + } + + // record result + test.result = "PASS"; + test.comments = ""; + } + catch (const tc::TestException& e) + { + // record result + test.result = "FAIL"; + test.comments = e.what(); + } + catch (const std::exception& e) + { + // record result + test.result = "UNHANDLED EXCEPTION"; + test.comments = e.what(); + } + + // add result to list + mTestResults.push_back(std::move(test)); +} + +void encode_Base64Util_TestClass::testEncodeStringAsBase64() +{ + TestResult test; + test.test_name = "testEncodeStringAsBase64"; + test.result = "NOT RUN"; + test.comments = ""; + + try + { + struct TestCase + { + std::string test_name; + std::string in_string; + tc::ByteData in_data; + std::string out_base64; + }; + + // create happy path tests + std::vector tests = + { + { "empty data", std::string(), tc::ByteData(), std::string()}, + { "single space", " ", tc::cli::FormatUtil::hexStringToBytes("20"), "IA=="}, + { "ascii string", "The quick brown fox jumps over the lazy dog.", tc::cli::FormatUtil::hexStringToBytes("54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f672e"), "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4="}, + // skip binary test as not a string + //{ "512 bytes binary data", std::string(), tc::cli::FormatUtil::hexStringToBytes("85d278fb18be42a62a56b495d4ab56a803fad5a1c66153b0c8a8628c3222fe55c500fa9721d5bfebff31ec59dcf810ac4bd0d2939ae045e7fcc34a432b45aca93e2a7a35b8208e75e1477d02bfd2175ae4a2d68dbf1a882249ca3f2b36586db700fb47e954f9233c9a2227e850957bbb1f3da6e9b4693d38e09434a691451b897c8b1fbbd5de4921e1f84fc136dd1b9297ad5e065556cd2ce52ec6eed8133d83bfc4e54c7d715de5e2b7eed9898b9b5eebbd5d07ae22f83d87218f7fe7355b5b6379e4a3eb27d48a9e042aa637063aac9a0b7bc104758bee61321183c0bb1ffb8ceb8c3d0cbbd5a55fe7809df89bf0883d8d7c762673fbfc5fceb6b14b4b1a828da5059cb090a2286ae7efc64b09e033d70cb6a528c54cea3d90f35b8fdfd73a1e85604c827cf5719b488e37b8cda2e2baba5cec43b1d031e7e4a47f3baedb00c037779a6f0e9fab5c9c3c84236458378847acd174083570c628074417ebba551853299eea400dfa95a8aff5f1fd9c314225365023ee31a03930c0029d57feb81417d57f9f45f225faa3790c0b239891c82151a7449507cab70376975ba9f2e68f5e37544f848faf875b9e24dccb9c556aae2a57a7f369581d50dfdf06fdff27fd2107db080c4d9e1c100427a1493ddb5e80e43f943bbbad9113448b658a5a5cdefd70e57d0b28e2bd942a978179eb88d6661b30f2b5b346fff1d0a5c9b93d2d"), "hdJ4+xi+QqYqVrSV1KtWqAP61aHGYVOwyKhijDIi/lXFAPqXIdW/6/8x7Fnc+BCsS9DSk5rgRef8w0pDK0WsqT4qejW4II514Ud9Ar/SF1rkotaNvxqIIknKPys2WG23APtH6VT5IzyaIifoUJV7ux89pum0aT044JQ0ppFFG4l8ix+71d5JIeH4T8E23RuSl61eBlVWzSzlLsbu2BM9g7/E5Ux9cV3l4rfu2YmLm17rvV0HriL4PYchj3/nNVtbY3nko+sn1IqeBCqmNwY6rJoLe8EEdYvuYTIRg8C7H/uM64w9DLvVpV/ngJ34m/CIPY18diZz+/xfzraxS0sago2lBZywkKIoaufvxksJ4DPXDLalKMVM6j2Q81uP39c6HoVgTIJ89XGbSI43uM2i4rq6XOxDsdAx5+Skfzuu2wDAN3eabw6fq1ycPIQjZFg3iEes0XQINXDGKAdEF+u6VRhTKZ7qQA36laiv9fH9nDFCJTZQI+4xoDkwwAKdV/64FBfVf59F8iX6o3kMCyOYkcghUadElQfKtwN2l1up8uaPXjdUT4SPr4dbniTcy5xVaq4qV6fzaVgdUN/fBv3/J/0hB9sIDE2eHBAEJ6FJPdtegOQ/lDu7rZETRItlilpc3v1w5X0LKOK9lCqXgXnriNZmGzDytbNG//HQpcm5PS0="}, + }; + + // run error path tests + { + std::string out = tc::encode::Base64Util::encodeStringAsBase64(nullptr, 0xdeadbeef); + + if (out != "") + { + throw tc::TestException(fmt::format("encodeStringAsBase64() did not return an empty string for invalid input: str=nullptr, size=0xdeadbeef")); + } + } + + // run happy path tests + for (auto test = tests.begin(); test != tests.end(); test++) + { + std::string out_1 = tc::encode::Base64Util::encodeStringAsBase64(test->in_string); + std::string out_2 = tc::encode::Base64Util::encodeStringAsBase64(test->in_string.c_str(), test->in_string.size()); + + if (out_1 != test->out_base64) + { + throw tc::TestException(fmt::format("Test({:s}) to convert str({:s}) to base64, returned \"{:s}\" (should be: \"{:s}\")", test->test_name, test->in_string, out_1, test->out_base64)); + } + + if (out_2 != test->out_base64) + { + throw tc::TestException(fmt::format("Test({:s}) to convert str({:s}), size({:d}) to base64, returned \"{:s}\" (should be: \"{:s}\")", test->test_name, test->in_string.c_str(), test->in_string.size(), test->in_data.size(), out_2, test->out_base64)); + } + } + + // record result + test.result = "PASS"; + test.comments = ""; + } + catch (const tc::TestException& e) + { + // record result + test.result = "FAIL"; + test.comments = e.what(); + } + catch (const std::exception& e) + { + // record result + test.result = "UNHANDLED EXCEPTION"; + test.comments = e.what(); + } + + // add result to list + mTestResults.push_back(std::move(test)); +} + +void encode_Base64Util_TestClass::testDecodeBase64AsData() +{ + TestResult test; + test.test_name = "testDecodeBase64AsData"; + test.result = "NOT RUN"; + test.comments = ""; + + try + { + struct TestCase + { + std::string test_name; + std::string in_string; + tc::ByteData in_data; + std::string out_base64; + }; + + // create happy path tests + std::vector tests = + { + { "empty data", std::string(), tc::ByteData(), std::string()}, + { "single space", " ", tc::cli::FormatUtil::hexStringToBytes("20"), "IA=="}, + { "ascii string", "The quick brown fox jumps over the lazy dog.", tc::cli::FormatUtil::hexStringToBytes("54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f672e"), "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4="}, + { "512 bytes binary data", std::string(), tc::cli::FormatUtil::hexStringToBytes("85d278fb18be42a62a56b495d4ab56a803fad5a1c66153b0c8a8628c3222fe55c500fa9721d5bfebff31ec59dcf810ac4bd0d2939ae045e7fcc34a432b45aca93e2a7a35b8208e75e1477d02bfd2175ae4a2d68dbf1a882249ca3f2b36586db700fb47e954f9233c9a2227e850957bbb1f3da6e9b4693d38e09434a691451b897c8b1fbbd5de4921e1f84fc136dd1b9297ad5e065556cd2ce52ec6eed8133d83bfc4e54c7d715de5e2b7eed9898b9b5eebbd5d07ae22f83d87218f7fe7355b5b6379e4a3eb27d48a9e042aa637063aac9a0b7bc104758bee61321183c0bb1ffb8ceb8c3d0cbbd5a55fe7809df89bf0883d8d7c762673fbfc5fceb6b14b4b1a828da5059cb090a2286ae7efc64b09e033d70cb6a528c54cea3d90f35b8fdfd73a1e85604c827cf5719b488e37b8cda2e2baba5cec43b1d031e7e4a47f3baedb00c037779a6f0e9fab5c9c3c84236458378847acd174083570c628074417ebba551853299eea400dfa95a8aff5f1fd9c314225365023ee31a03930c0029d57feb81417d57f9f45f225faa3790c0b239891c82151a7449507cab70376975ba9f2e68f5e37544f848faf875b9e24dccb9c556aae2a57a7f369581d50dfdf06fdff27fd2107db080c4d9e1c100427a1493ddb5e80e43f943bbbad9113448b658a5a5cdefd70e57d0b28e2bd942a978179eb88d6661b30f2b5b346fff1d0a5c9b93d2d"), "hdJ4+xi+QqYqVrSV1KtWqAP61aHGYVOwyKhijDIi/lXFAPqXIdW/6/8x7Fnc+BCsS9DSk5rgRef8w0pDK0WsqT4qejW4II514Ud9Ar/SF1rkotaNvxqIIknKPys2WG23APtH6VT5IzyaIifoUJV7ux89pum0aT044JQ0ppFFG4l8ix+71d5JIeH4T8E23RuSl61eBlVWzSzlLsbu2BM9g7/E5Ux9cV3l4rfu2YmLm17rvV0HriL4PYchj3/nNVtbY3nko+sn1IqeBCqmNwY6rJoLe8EEdYvuYTIRg8C7H/uM64w9DLvVpV/ngJ34m/CIPY18diZz+/xfzraxS0sago2lBZywkKIoaufvxksJ4DPXDLalKMVM6j2Q81uP39c6HoVgTIJ89XGbSI43uM2i4rq6XOxDsdAx5+Skfzuu2wDAN3eabw6fq1ycPIQjZFg3iEes0XQINXDGKAdEF+u6VRhTKZ7qQA36laiv9fH9nDFCJTZQI+4xoDkwwAKdV/64FBfVf59F8iX6o3kMCyOYkcghUadElQfKtwN2l1up8uaPXjdUT4SPr4dbniTcy5xVaq4qV6fzaVgdUN/fBv3/J/0hB9sIDE2eHBAEJ6FJPdtegOQ/lDu7rZETRItlilpc3v1w5X0LKOK9lCqXgXnriNZmGzDytbNG//HQpcm5PS0="}, + }; + + // run error path tests + { + tc::ByteData out = tc::encode::Base64Util::decodeBase64AsData(nullptr, 0xdeadbeef); + + if (out.data() != nullptr || out.size() != 0) + { + throw tc::TestException(fmt::format("decodeBase64AsData() did not return an empty tc::ByteData for invalid input: str=nullptr, size=0xdeadbeef")); + } + } + { + tc::ByteData out = tc::encode::Base64Util::decodeBase64AsData("not base 64"); + + if (out.data() != nullptr || out.size() != 0) + { + throw tc::TestException(fmt::format("decodeBase64AsData() did not return an empty tc::ByteData for invalid input: str=\"not base 64\"")); + } + } + + // run happy path tests + for (auto test = tests.begin(); test != tests.end(); test++) + { + tc::ByteData out_1 = tc::encode::Base64Util::decodeBase64AsData(test->out_base64); + tc::ByteData out_2 = tc::encode::Base64Util::decodeBase64AsData(test->out_base64.data(), test->out_base64.size()); + + // size should match + if (out_1.size() != test->in_data.size()) + { + throw tc::TestException(fmt::format("Test({:s}) to convert str({:s}) from base64, size was \"{:d}\" (should be: \"{:d}\")", test->test_name, test->out_base64, out_1.size(), test->in_data.size())); + } + + // if expected data was nullptr, so should actual + if (test->in_data.data() == nullptr && out_1.data() != nullptr) + { + throw tc::TestException(fmt::format("Test({:s}) to convert str({:s}) from base64, ret.data() should have been null", test->test_name, test->out_base64)); + } + + // if the data was matching and non-zero, they should also match + if (memcmp(out_2.data(), test->in_data.data(), test->in_data.size()) != 0) + { + throw tc::TestException(fmt::format("Test({:s}) to convert str({:s}) from base64, returned \"{:s}\" (should be: \"{:s}\")", test->test_name, test->out_base64, tc::cli::FormatUtil::formatBytesAsString(out_2, false, ""), tc::cli::FormatUtil::formatBytesAsString(test->in_data, false, ""))); + } + + // size should match + if (out_2.size() != test->in_data.size()) + { + throw tc::TestException(fmt::format("Test({:s}) to convert str({:s}), size({:d}) from base64, size was \"{:d}\" (should be: \"{:d}\")", test->test_name, test->out_base64.c_str(), test->out_base64.size(), out_2.size(), test->in_data.size())); + } + + // if expected data was nullptr, so should actual + if (test->in_data.data() == nullptr && out_2.data() != nullptr) + { + throw tc::TestException(fmt::format("Test({:s}) to convert str({:s}), size({:d}) from base64, ret.data() should have been null", test->test_name, test->out_base64.c_str(), test->out_base64.size())); + } + + // if the data was matching and non-zero, they should also match + if (memcmp(out_2.data(), test->in_data.data(), test->in_data.size()) != 0) + { + throw tc::TestException(fmt::format("Test({:s}) to convert str({:s}), size({:d}) from base64, returned \"{:s}\" (should be: \"{:s}\")", test->test_name, test->out_base64.c_str(), test->out_base64.size(), tc::cli::FormatUtil::formatBytesAsString(out_1, false, ""), tc::cli::FormatUtil::formatBytesAsString(test->in_data, false, ""))); + } + } + + // record result + test.result = "PASS"; + test.comments = ""; + } + catch (const tc::TestException& e) + { + // record result + test.result = "FAIL"; + test.comments = e.what(); + } + catch (const std::exception& e) + { + // record result + test.result = "UNHANDLED EXCEPTION"; + test.comments = e.what(); + } + + // add result to list + mTestResults.push_back(std::move(test)); +} + +void encode_Base64Util_TestClass::testDecodeBase64AsString() +{ + TestResult test; + test.test_name = "testDecodeBase64AsString"; + test.result = "NOT RUN"; + test.comments = ""; + + try + { + struct TestCase + { + std::string test_name; + std::string in_string; + tc::ByteData in_data; + std::string out_base64; + }; + + // create happy path tests + std::vector tests = + { + { "empty data", std::string(), tc::ByteData(), std::string()}, + { "single space", " ", tc::cli::FormatUtil::hexStringToBytes("20"), "IA=="}, + { "ascii string", "The quick brown fox jumps over the lazy dog.", tc::cli::FormatUtil::hexStringToBytes("54686520717569636b2062726f776e20666f78206a756d7073206f76657220746865206c617a7920646f672e"), "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4="}, + // skip binary test as not a string + //{ "512 bytes binary data", std::string(), tc::cli::FormatUtil::hexStringToBytes("85d278fb18be42a62a56b495d4ab56a803fad5a1c66153b0c8a8628c3222fe55c500fa9721d5bfebff31ec59dcf810ac4bd0d2939ae045e7fcc34a432b45aca93e2a7a35b8208e75e1477d02bfd2175ae4a2d68dbf1a882249ca3f2b36586db700fb47e954f9233c9a2227e850957bbb1f3da6e9b4693d38e09434a691451b897c8b1fbbd5de4921e1f84fc136dd1b9297ad5e065556cd2ce52ec6eed8133d83bfc4e54c7d715de5e2b7eed9898b9b5eebbd5d07ae22f83d87218f7fe7355b5b6379e4a3eb27d48a9e042aa637063aac9a0b7bc104758bee61321183c0bb1ffb8ceb8c3d0cbbd5a55fe7809df89bf0883d8d7c762673fbfc5fceb6b14b4b1a828da5059cb090a2286ae7efc64b09e033d70cb6a528c54cea3d90f35b8fdfd73a1e85604c827cf5719b488e37b8cda2e2baba5cec43b1d031e7e4a47f3baedb00c037779a6f0e9fab5c9c3c84236458378847acd174083570c628074417ebba551853299eea400dfa95a8aff5f1fd9c314225365023ee31a03930c0029d57feb81417d57f9f45f225faa3790c0b239891c82151a7449507cab70376975ba9f2e68f5e37544f848faf875b9e24dccb9c556aae2a57a7f369581d50dfdf06fdff27fd2107db080c4d9e1c100427a1493ddb5e80e43f943bbbad9113448b658a5a5cdefd70e57d0b28e2bd942a978179eb88d6661b30f2b5b346fff1d0a5c9b93d2d"), "hdJ4+xi+QqYqVrSV1KtWqAP61aHGYVOwyKhijDIi/lXFAPqXIdW/6/8x7Fnc+BCsS9DSk5rgRef8w0pDK0WsqT4qejW4II514Ud9Ar/SF1rkotaNvxqIIknKPys2WG23APtH6VT5IzyaIifoUJV7ux89pum0aT044JQ0ppFFG4l8ix+71d5JIeH4T8E23RuSl61eBlVWzSzlLsbu2BM9g7/E5Ux9cV3l4rfu2YmLm17rvV0HriL4PYchj3/nNVtbY3nko+sn1IqeBCqmNwY6rJoLe8EEdYvuYTIRg8C7H/uM64w9DLvVpV/ngJ34m/CIPY18diZz+/xfzraxS0sago2lBZywkKIoaufvxksJ4DPXDLalKMVM6j2Q81uP39c6HoVgTIJ89XGbSI43uM2i4rq6XOxDsdAx5+Skfzuu2wDAN3eabw6fq1ycPIQjZFg3iEes0XQINXDGKAdEF+u6VRhTKZ7qQA36laiv9fH9nDFCJTZQI+4xoDkwwAKdV/64FBfVf59F8iX6o3kMCyOYkcghUadElQfKtwN2l1up8uaPXjdUT4SPr4dbniTcy5xVaq4qV6fzaVgdUN/fBv3/J/0hB9sIDE2eHBAEJ6FJPdtegOQ/lDu7rZETRItlilpc3v1w5X0LKOK9lCqXgXnriNZmGzDytbNG//HQpcm5PS0="}, + }; + + // run error path tests + { + std::string out = tc::encode::Base64Util::decodeBase64AsString(nullptr, 0xdeadbeef); + + if (out != "") + { + throw tc::TestException(fmt::format("decodeBase64AsString() did not return an empty string for invalid input: str=nullptr, size=0xdeadbeef")); + } + } + { + std::string out = tc::encode::Base64Util::decodeBase64AsString("not base 64"); + + if (out != "") + { + throw tc::TestException(fmt::format("decodeBase64AsString() did not return an empty string for invalid input: str=\"not base 64\"")); + } + } + + // run happy path tests + for (auto test = tests.begin(); test != tests.end(); test++) + { + std::string out_1 = tc::encode::Base64Util::decodeBase64AsString(test->out_base64); + std::string out_2 = tc::encode::Base64Util::decodeBase64AsString(test->out_base64.data(), test->out_base64.size()); + + if (out_1 != test->in_string) + { + throw tc::TestException(fmt::format("Test({:s}) to convert str({:s}) from base64, returned \"{:s}\" (should be: \"{:s}\")", test->test_name, test->out_base64, out_1, test->in_string)); + } + + if (out_2 != test->in_string) + { + throw tc::TestException(fmt::format("Test({:s}) to convert str({:s}), size({:d}) from base64, returned \"{:s}\" (should be: \"{:s}\")", test->test_name, test->out_base64.c_str(), test->out_base64.size(), test->in_data.size(), out_2, test->in_string)); + } + } + + // record result + test.result = "PASS"; + test.comments = ""; + } + catch (const tc::TestException& e) + { + // record result + test.result = "FAIL"; + test.comments = e.what(); + } + catch (const std::exception& e) + { + // record result + test.result = "UNHANDLED EXCEPTION"; + test.comments = e.what(); + } + + // add result to list + mTestResults.push_back(std::move(test)); +} \ No newline at end of file diff --git a/test/encode_Base64Util_TestClass.h b/test/encode_Base64Util_TestClass.h new file mode 100644 index 00000000..bea9f60d --- /dev/null +++ b/test/encode_Base64Util_TestClass.h @@ -0,0 +1,26 @@ +#pragma once +#include "ITestClass.h" +#include + +class encode_Base64Util_TestClass : public ITestClass +{ +public: + encode_Base64Util_TestClass(); + + // this will run the tests + void runAllTests(); + + // this is the label for this test (for filtering purposes) + const std::string& getTestTag() const; + + // this is where the test results are written + const std::vector& getTestResults() const; +private: + std::string mTestTag; + std::vector mTestResults; + + void testEncodeDataAsBase64(); + void testEncodeStringAsBase64(); + void testDecodeBase64AsData(); + void testDecodeBase64AsString(); +}; \ No newline at end of file diff --git a/test/main.cpp b/test/main.cpp index aa099a11..e7d00466 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -3,6 +3,7 @@ #include "string_TranscodeUtil_TestClass.h" #include "cli_FormatUtil_TestClass.h" #include "cli_OptionParser_TestClass.h" +#include "encode_Base64Util_TestClass.h" #include "bn_binaryutils_TestClass.h" #include "bn_pad_TestClass.h" #include "bn_string_TestClass.h" @@ -235,6 +236,9 @@ int main(int argc, char** argv) runTest(global_test_results, include_test_regex, exclude_test_regex, include_slow_tests); runTest(global_test_results, include_test_regex, exclude_test_regex, include_slow_tests); + // namespace tc::encode + runTest(global_test_results, include_test_regex, exclude_test_regex, include_slow_tests); + // namespace tc::bn runTest(global_test_results, include_test_regex, exclude_test_regex, include_slow_tests); runTest(global_test_results, include_test_regex, exclude_test_regex, include_slow_tests);