diff --git a/common/include/villas/utils.hpp b/common/include/villas/utils.hpp index 564ce4771..db8d22f4a 100644 --- a/common/include/villas/utils.hpp +++ b/common/include/villas/utils.hpp @@ -210,11 +210,10 @@ int sha1sum(FILE *f, unsigned char *sha1); namespace base64 { -std::string encode(const std::string &str); -std::string encode(const unsigned char *input, size_t len); +using byte = std::uint8_t; -std::string decode(const std::string &str); -std::string decode(unsigned char *input, size_t len); +std::string encode(const std::vector &input); +std::vector decode(const std::string &input); } /* namespace base64 */ } /* namespace utils */ diff --git a/common/lib/CMakeLists.txt b/common/lib/CMakeLists.txt index e932c1b41..edbc0a366 100644 --- a/common/lib/CMakeLists.txt +++ b/common/lib/CMakeLists.txt @@ -45,6 +45,7 @@ add_library(villas-common SHARED version.cpp common.cpp tool.cpp + base64.cpp ) execute_process( diff --git a/common/lib/base64.cpp b/common/lib/base64.cpp new file mode 100644 index 000000000..d7f3519c6 --- /dev/null +++ b/common/lib/base64.cpp @@ -0,0 +1,131 @@ +/** Base64 encoding/decoding + * + * @author Steffen Vogel + * @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLAScommon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include +#include +#include +#include + +#include + +namespace villas { +namespace utils { +namespace base64 { + +static const char kEncodeLookup[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char kPadCharacter = '='; + +std::string encode(const std::vector& input) +{ + std::string encoded; + encoded.reserve(((input.size() / 3) + (input.size() % 3 > 0)) * 4); + + std::uint32_t temp{}; + auto it = input.begin(); + + for (std::size_t i = 0; i < input.size() / 3; ++i) { + temp = (*it++) << 16; + temp += (*it++) << 8; + temp += (*it++); + encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]); + encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]); + encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6 ]); + encoded.append(1, kEncodeLookup[(temp & 0x0000003F) ]); + } + + switch (input.size() % 3) { + case 1: + temp = (*it++) << 16; + encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]); + encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]); + encoded.append(2, kPadCharacter); + break; + + case 2: + temp = (*it++) << 16; + temp += (*it++) << 8; + encoded.append(1, kEncodeLookup[(temp & 0x00FC0000) >> 18]); + encoded.append(1, kEncodeLookup[(temp & 0x0003F000) >> 12]); + encoded.append(1, kEncodeLookup[(temp & 0x00000FC0) >> 6 ]); + encoded.append(1, kPadCharacter); + break; + } + + return encoded; +} + +std::vector decode(const std::string& input) +{ + if (input.length() % 4) + throw std::runtime_error("Invalid base64 length!"); + + std::size_t padding{}; + + if (input.length()) { + if(input[input.length() - 1] == kPadCharacter) padding++; + if(input[input.length() - 2] == kPadCharacter) padding++; + } + + std::vector decoded; + decoded.reserve(((input.length() / 4) * 3) - padding); + + std::uint32_t temp{}; + auto it = input.begin(); + + while (it < input.end()) { + for (std::size_t i = 0; i < 4; ++i) { + temp <<= 6; + if (*it >= 0x41 && *it <= 0x5A) temp |= *it - 0x41; + else if (*it >= 0x61 && *it <= 0x7A) temp |= *it - 0x47; + else if (*it >= 0x30 && *it <= 0x39) temp |= *it + 0x04; + else if (*it == 0x2B) temp |= 0x3E; + else if (*it == 0x2F) temp |= 0x3F; + else if (*it == kPadCharacter) { + switch(input.end() - it) { + case 1: + decoded.push_back((temp >> 16) & 0x000000FF); + decoded.push_back((temp >> 8 ) & 0x000000FF); + return decoded; + case 2: + decoded.push_back((temp >> 10) & 0x000000FF); + return decoded; + default: + throw std::runtime_error("Invalid padding in base64!"); + } + } + else + throw std::runtime_error("Invalid character in base64!"); + + ++it; + } + + decoded.push_back((temp >> 16) & 0x000000FF); + decoded.push_back((temp >> 8 ) & 0x000000FF); + decoded.push_back((temp ) & 0x000000FF); + } + + return decoded; +} + +} /* namespace base64 */ +} /* namespace utils */ +} /* namespace villas */ diff --git a/common/lib/utils.cpp b/common/lib/utils.cpp index e944da54f..0057fa5a0 100644 --- a/common/lib/utils.cpp +++ b/common/lib/utils.cpp @@ -356,57 +356,5 @@ int sha1sum(FILE *f, unsigned char *sha1) return 0; } -namespace base64 { - -std::string encode(const std::string &str) -{ - return encode((unsigned char *) str.data(), str.size()); -} - -std::string decode(const std::string &str) -{ - return decode((unsigned char *) str.data(), str.size()); -} - -std::string encode(const unsigned char *input, size_t len) -{ - BIO *bmem, *b64; - BUF_MEM *bptr; - - b64 = BIO_new(BIO_f_base64()); - bmem = BIO_new(BIO_s_mem()); - b64 = BIO_push(b64, bmem); - BIO_write(b64, input, len); - BIO_flush(b64); - BIO_get_mem_ptr(b64, &bptr); - - std::string str(bptr->data, bptr->length); - - BIO_free_all(b64); - - return str; -} - -std::string decode(unsigned char *input, size_t len) -{ - BIO *b64, *bmem; - - std::string str(len, 0); - - char *buffer = (char *) malloc(len); - memset(buffer, 0, len); - - b64 = BIO_new(BIO_f_base64()); - bmem = BIO_new_mem_buf(input, len); - bmem = BIO_push(b64, bmem); - - BIO_read(bmem, const_cast(str.data()), str.capacity()); - - BIO_free_all(bmem); - - return buffer; -} - -} /* namespace base64 */ } /* namespace utils */ } /* namespace villas */ diff --git a/common/tests/unit/CMakeLists.txt b/common/tests/unit/CMakeLists.txt index 55a2fd79e..0e49a970c 100644 --- a/common/tests/unit/CMakeLists.txt +++ b/common/tests/unit/CMakeLists.txt @@ -30,6 +30,7 @@ add_executable(unit-tests-common task.cpp timing.cpp utils.cpp + base64.cpp ) if(ARCH STREQUAL "x86_64") diff --git a/common/tests/unit/base64.cpp b/common/tests/unit/base64.cpp new file mode 100644 index 000000000..f0a30526e --- /dev/null +++ b/common/tests/unit/base64.cpp @@ -0,0 +1,51 @@ +/** Unit tests for base64 encoding/decoding + * + * @author Steffen Vogel + * @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLAScommon + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#include + +#include + +#include + +using namespace villas::utils::base64; + +TestSuite(base64, .description = "Base64 En/decoder"); + +static std::vector vec(const char *str) +{ + return std::vector((byte *) str, (byte *) str + strlen(str)); +} + +static std::string str(const std::vector &vec) +{ + return std::string((char *) vec.data(), vec.size()); +} + +Test(base64, encoding) +{ + cr_assert(encode(vec("pohy0Aiy1ZaVa5aik2yaiy3ifoh3oole")) == "cG9oeTBBaXkxWmFWYTVhaWsyeWFpeTNpZm9oM29vbGU="); +} + +Test(base64, decoding) +{ + cr_assert(decode("cG9oeTBBaXkxWmFWYTVhaWsyeWFpeTNpZm9oM29vbGU=") == vec("pohy0Aiy1ZaVa5aik2yaiy3ifoh3oole")); +}