diff options
author | Thomas Markwalder <tmark@isc.org> | 2024-01-30 21:47:54 +0100 |
---|---|---|
committer | Thomas Markwalder <tmark@isc.org> | 2024-02-07 14:58:47 +0100 |
commit | 6949dd94a3f5e8fe5c5930d1cd9ce760512bb001 (patch) | |
tree | 5c61b2b2046e93e0153e96ac45fc8882c6d80554 | |
parent | [#3209] Initial commit (diff) | |
download | kea-6949dd94a3f5e8fe5c5930d1cd9ce760512bb001.tar.xz kea-6949dd94a3f5e8fe5c5930d1cd9ce760512bb001.zip |
[#3209] Created new BaseNEncoder class
src/lib/util/encode/encode.h
src/lib/util/encode/encode.cc
Reworked into new BaseNEncoder class and derivatives
src/lib/util/tests/Makefile.am
Removed obsolete test files
added new file encode_unittest.cc
new file: src/lib/util/tests/encode_unittest.cc
deleted: src/lib/util/tests/base32hex_unittest.cc
deleted: src/lib/util/tests/base64_unittest.cc
deleted: src/lib/util/tests/hex_unittest.cc
-rw-r--r-- | src/lib/util/encode/encode.cc | 249 | ||||
-rw-r--r-- | src/lib/util/encode/encode.h | 226 | ||||
-rw-r--r-- | src/lib/util/tests/Makefile.am | 4 | ||||
-rw-r--r-- | src/lib/util/tests/base32hex_unittest.cc | 152 | ||||
-rw-r--r-- | src/lib/util/tests/base64_unittest.cc | 89 | ||||
-rw-r--r-- | src/lib/util/tests/encode_unittest.cc | 395 | ||||
-rw-r--r-- | src/lib/util/tests/hex_unittest.cc | 120 |
7 files changed, 766 insertions, 469 deletions
diff --git a/src/lib/util/encode/encode.cc b/src/lib/util/encode/encode.cc index 52b15e6907..cb06cdac0d 100644 --- a/src/lib/util/encode/encode.cc +++ b/src/lib/util/encode/encode.cc @@ -14,6 +14,7 @@ #include <stdint.h> #include <stdexcept> #include <string> +#include <cstring> #include <vector> using namespace std; @@ -22,16 +23,55 @@ namespace isc { namespace util { namespace encode { +BaseNEncoder::BaseNEncoder(const std::string& algorithm, + const char* digit_set, + const std::vector<uint8_t>& bits_table, + size_t bits_per_digit, + size_t digits_per_group, + const char pad_char, + size_t max_pad, + bool case_sensitive) + : algorithm_(algorithm), + digit_set_(digit_set), + bits_table_(bits_table), + bits_per_digit_(bits_per_digit), + digits_per_group_(digits_per_group), + pad_char_(pad_char), + max_pad_(max_pad), + case_sensitive_(case_sensitive), + max_bits_to_digit_(strlen(digit_set) - 1), + max_digit_to_bits_(bits_table_.size() - 1) { +} + +char +BaseNEncoder::bitsToDigit(uint8_t bits) { + if (bits > max_bits_to_digit_) { + isc_throw(BadValue, "Digit bits : " + << static_cast<uint16_t>(bits) << " invalid for " << algorithm_); + } + + return(digit_set_[bits]); +} + +uint8_t +BaseNEncoder::digitToBits(uint8_t digit) { + if (digit > max_digit_to_bits_) { + isc_throw(BadValue, "Digit exceeds look up table: " + << static_cast<uint16_t>(digit) << " for " << algorithm_); + } + + return(bits_table_[digit]); +} + std::string -encodeBaseN(const std::vector<uint8_t>& input, const char* digit_set, size_t bits_per_digit, - size_t digits_per_group, const char pad_char) { +BaseNEncoder::encode(const std::vector<uint8_t>& input) { std::string encoded_output; if (input.empty()) { return(encoded_output); } // Turn the input data into a "bit stream" - /// @todo Can we devize a bit-stream class that can iterate over the input + /// @todo Can we devise a bit-stream class that can iterate over the input /// without copying it? The weakness here is inbits could be rather large /// for long strings since it input size * 8 bytes. bool inbits[input.size() * 8]; @@ -50,12 +90,12 @@ encodeBaseN(const std::vector<uint8_t>& input, const char* digit_set, size_t bit auto inbit_end = inbit; inbit = &inbits[0]; for (inbit = &inbits[0]; inbit != inbit_end; ++inbit) { - if (cnt < bits_per_digit) { - // Shift the index one to accomodate next bit. + if (cnt < bits_per_digit_) { + // Shift the index one to accommodate next bit. digit_idx <<= 1; } else { // Have a complete digit index, look it the digit and add it. - encoded_output.push_back(digit_set[digit_idx]); + encoded_output.push_back(bitsToDigit(digit_idx)); digit_idx = 0; cnt = 0; } @@ -67,17 +107,17 @@ encodeBaseN(const std::vector<uint8_t>& input, const char* digit_set, size_t bit // We've exhausted bits, but have left over if (cnt) { - digit_idx <<= (bits_per_digit - cnt); - encoded_output.push_back(digit_set[digit_idx]); + digit_idx <<= (bits_per_digit_ - cnt); + encoded_output.push_back(bitsToDigit(digit_idx)); } // Add padding as needed. - if (digits_per_group) { - auto rem = encoded_output.size() % digits_per_group; + if (digits_per_group_) { + auto rem = encoded_output.size() % digits_per_group_; if (rem) { - auto need = digits_per_group - rem + 1; + auto need = digits_per_group_ - rem + 1; while (--need) { - encoded_output.push_back(pad_char); + encoded_output.push_back(pad_char_); } } } @@ -86,28 +126,21 @@ encodeBaseN(const std::vector<uint8_t>& input, const char* digit_set, size_t bit } void -decodeBaseN(const std::string& algorithm, - const std::string& encoded_str, std::vector<uint8_t>& output, - const uint8_t* lookup_table, - size_t bits_per_digit, - size_t digits_per_group, - const char pad_char, - size_t max_pad) { - +BaseNEncoder::decode(const std::string& encoded_str, std::vector<uint8_t>& output) { output.clear(); - bool inbits[encoded_str.size() * bits_per_digit]; + bool inbits[encoded_str.size() * bits_per_digit_]; bool* inbit = &inbits[0]; size_t dig_cnt = 0; size_t pad_cnt = 0; - size_t shift_bits = 8 - bits_per_digit; + size_t shift_bits = 8 - bits_per_digit_; for (const auto enc_digit : encoded_str) { - if (pad_char && enc_digit == pad_char) { + if (pad_char_ && enc_digit == pad_char_) { pad_cnt++; continue; } // translate the b64 digit to bits. - uint8_t dig_bits = lookup_table[static_cast<uint8_t>(enc_digit)]; + uint8_t dig_bits = digitToBits(enc_digit); if (dig_bits == 0xee) { // skip whitespace @@ -116,42 +149,42 @@ decodeBaseN(const std::string& algorithm, if (dig_bits == 0xff) { isc_throw(isc::BadValue, "attempt to decode a value not in " - << algorithm << " char set" << ": " << encoded_str); + << algorithm_ << " char set" << ": " << encoded_str); } if (pad_cnt) { isc_throw(isc::BadValue, "pad mixed with digits in " - << algorithm << ": " << encoded_str); + << algorithm_ << ": " << encoded_str); } dig_cnt++; dig_bits <<= shift_bits; - for (auto i = 0; i < bits_per_digit; ++i) { + for (auto i = 0; i < bits_per_digit_; ++i) { *inbit++ = ((dig_bits & 0x80) == 0x80); dig_bits <<= 1; } } - if (pad_char) { + if (pad_char_) { // Check for invalid number of pad characters. - if (pad_cnt > max_pad) { + if (pad_cnt > max_pad_) { isc_throw(isc::BadValue, "too many pad characters for " - << algorithm << ": " << encoded_str); + << algorithm_ << ": " << encoded_str); } // Check for invalid number of pad characters. /// @todo is this valid - const size_t padbits = ((pad_cnt * bits_per_digit) + 7) & ~7; - if (padbits > bits_per_digit * (pad_cnt + 1)) { + const size_t padbits = ((pad_cnt * bits_per_digit_) + 7) & ~7; + if (padbits > bits_per_digit_ * (pad_cnt + 1)) { isc_throw(isc::BadValue, "Invalid padding for " - << algorithm << ": " << encoded_str); + << algorithm_ << ": " << encoded_str); } } // Check for an invalid total of encoded characters. - if ((pad_cnt + dig_cnt) % digits_per_group) { + if ((pad_cnt + dig_cnt) % digits_per_group_) { isc_throw (isc::BadValue, "Incomplete input for " - << algorithm << ": " << encoded_str); + << algorithm_ << ": " << encoded_str); } int cnt = 0; @@ -181,98 +214,106 @@ decodeBaseN(const std::string& algorithm, } } +const char* Base64Encoder::DIGIT_SET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/"; + +const std::vector<uint8_t> Base64Encoder::BITS_TABLE = { + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xee,0xee,0xee,0xee,0xee,0xff,0xff, // 00-0f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 10-1f + 0xee,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,62,0xff,0xff,0xff,63, // 20-2f + 52,53,54,55,56,57,58,59,60,61,0xff,0xff,0xff, 0,0xff,0xff, // 30-3f + 0xff, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, // 40-4f + 15,16,17,18,19,20,21,22,23,24,25,0xff,0xff,0xff,0xff,0xff, // 50-5f + 0xff,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, // 60-6f + 41,42,43,44,45,46,47,48,49,50,51,0xff,0xff,0xff,0xff,0xff, // 70-7f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 80-8f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 90-9f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // a0-af + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // b0-bf + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // c0-cf + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // d0-df + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // e0-ef + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff // f0-ff, +}; + +const char* Base32HexEncoder::DIGIT_SET = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; + +const std::vector<uint8_t> Base32HexEncoder::BITS_TABLE = { + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xee,0xee,0xee,0xee,0xee,0xff,0xff, // 00-0f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 10-1f + 0xee,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 20-2f + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,0xff,0xff,0xff,0xff,0xff,0xff, // 30-3f + 0xff,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, // 40-4f + 25,26,27,28,29,30,31,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 50-5f + 0xff,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, // 60-6f + 25,26,27,28,29,30,31,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 70-7f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 80-8f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 90-9f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // a0-af + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // b0-bf + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // c0-cf + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // d0-df + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // e0-ef + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff // f0-ff +}; + +const char* Base16Encoder::DIGIT_SET = "0123456789ABCDEF"; + +const std::vector<uint8_t> Base16Encoder::BITS_TABLE = { + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xee,0xee,0xee,0xee,0xee,0xff,0xff, // 00-0f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 10-1f + 0xee,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 20-2f + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,0xff,0xff,0xff,0xff,0xff,0xff, // 30-3f + 0xff,10,11,12,13,14,15,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 40-4f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 50-5f + 0xff,10,11,12,13,14,15,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 60-6f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 70-7f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 80-8f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 90-9f + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // a0-af + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // b0-bf + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // c0-cf + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // d0-df + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // e0-ef + 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff // f0-ff +}; string encodeBase64(const vector<uint8_t>& binary) { - static char B64_DIG[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789" - "+/"; - return(encodeBaseN(binary, B64_DIG, 6, 4, '=')); + Base64Encoder encoder; + return(encoder.encode(binary)); } void decodeBase64 (const std::string& encoded_str, std::vector<uint8_t>& output) { - static const uint8_t lookup_table[] = { - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xee,0xee,0xee,0xee,0xee,0xff,0xff, // 00-0f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 10-1f - 0xee,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,62,0xff,0xff,0xff,63, // 20-2f - 52,53,54,55,56,57,58,59,60,61,0xff,0xff,0xff, 0,0xff,0xff, // 30-3f - 0xff, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, // 40-4f - 15,16,17,18,19,20,21,22,23,24,25,0xff,0xff,0xff,0xff,0xff, // 50-5f - 0xff,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, // 60-6f - 41,42,43,44,45,46,47,48,49,50,51,0xff,0xff,0xff,0xff,0xff, // 70-7f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 80-8f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 90-9f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // a0-af - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // b0-bf - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // c0-cf - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // d0-df - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // e0-ef - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff // f0-ff - }; - - decodeBaseN("base64", encoded_str, output, lookup_table, 6, 4, '=', 2); + Base64Encoder encoder; + encoder.decode(encoded_str, output); } string encodeBase32Hex(const vector<uint8_t>& binary) { - static char B32_DIG[] = "0123456789ABCDEFGHIJKLMNOPQRSTUV"; - return(encodeBaseN(binary, B32_DIG, 5, 8, '=')); + Base32HexEncoder encoder; + return(encoder.encode(binary)); } void decodeBase32Hex(const std::string& encoded_str, std::vector<uint8_t>& output) { - static const uint8_t lookup_table[] = { - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xee,0xee,0xee,0xee,0xee,0xff,0xff, // 00-0f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 10-1f - 0xee,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 20-2f - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,0xff,0xff,0xff,0xff,0xff,0xff, // 30-3f - 0xff,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, // 40-4f - 25,26,27,28,29,30,31,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 50-5f - 0xff,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24, // 60-6f - 25,26,27,28,29,30,31,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 70-7f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 80-8f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 90-9f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // a0-af - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // b0-bf - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // c0-cf - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // d0-df - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // e0-ef - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff // f0-ff - }; - - decodeBaseN("base32hex", encoded_str, output, lookup_table, 5, 8, '=', 6); + Base32HexEncoder encoder; + encoder.decode(encoded_str, output); } string encodeHex(const vector<uint8_t>& binary) { - static char B16_DIG[] = "0123456789ABCDEF"; - return(encodeBaseN(binary, B16_DIG, 4, 1, 0)); + Base16Encoder encoder; + return(encoder.encode(binary)); } void decodeHex(const string& encoded_str, vector<uint8_t>& output) { - static const uint8_t lookup_table[] = { - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xee,0xee,0xee,0xee,0xee,0xff,0xff, // 00-0f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 10-1f - 0xee,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 20-2f - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,0xff,0xff,0xff,0xff,0xff,0xff, // 30-3f - 0xff,10,11,12,13,14,15,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 40-4f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 50-5f - 0xff,10,11,12,13,14,15,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 60-6f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 70-7f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 80-8f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // 90-9f - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // a0-af - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // b0-bf - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // c0-cf - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // d0-df - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff, // e0-ef - 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff // f0-ff - }; - - decodeBaseN("base16", encoded_str, output, lookup_table, 4, 1, 0, 0); + Base16Encoder encoder; + encoder.decode(encoded_str, output); } } // namespace encode diff --git a/src/lib/util/encode/encode.h b/src/lib/util/encode/encode.h index 2e6bca0e54..13d3c3e96e 100644 --- a/src/lib/util/encode/encode.h +++ b/src/lib/util/encode/encode.h @@ -15,6 +15,230 @@ namespace isc { namespace util { namespace encode { +/// @brief Class for encoding and decoding binary data using an algorithm +/// described in RFC 4648. +class BaseNEncoder { +public: + + /// @Brief Constructor + /// + /// @param algorithm name of the algorithm, used for logging + /// @param digit_set set of digits (i.e. alphabet) used for encoding + /// @param bits_table table to translate digits to data used during decoding + /// @param bits_per_digit number of data bits represented by a digit + /// @param digits_per_group number of digits contained in a group + /// @param pad_char character used for padding out to group size (0 means no + /// padding) + /// @param max_pad maximum number of pad characters in a group + /// @param case_sensitive indicates if the algorithm's digit set is + /// case sensitive. + BaseNEncoder(const std::string& algorithm, + const char* digit_set, + const std::vector<uint8_t>& bits_table, + size_t bits_per_digit, + size_t digits_per_group, + const char pad_char, + size_t max_pad, + bool case_sensitive); + + /// @Brief Destructor + virtual ~BaseNEncoder() = default; + + /// @brief Encodes binary data using the encoder's algorithm + /// + /// @param input binary data to encode + /// @return resultant encoded data string + /// @throw BadValue if an error occurs during encoding + std::string encode(const std::vector<uint8_t>& input); + + /// @brief Decodes an encoded string using the encoder's algorithm + /// + /// @param encoded_str encoded string to decode + /// @param[out] output vector into which the decoded data is stored + /// @throw BadValue if an error occurs during decoding + void decode(const std::string& encoded_str, std::vector<uint8_t>& output); + + /// @brief Translate a byte of binary data into the appropriate algorithm digit + /// + /// @param bits binary value to translate + /// @return char containing the digit corresponding to the binary value + /// @isc_throw BadValue if the bits value is out of range + char bitsToDigit(uint8_t bits); + + /// @brief Translate a digit into the appropriate algorithm bit value + /// + /// Function maps all 256 ASCII chars to their corresponding algorithm-specific + /// data value. A data value of 0xee marks a char as whitespace, 0xff marks a + /// char is invalid. + /// + /// @param digit the algorithm digit to translate + /// @return byte containing the binary value corresponding to the digit + uint8_t digitToBits(uint8_t digit); + + /// @brief Get the algorithm name + /// + /// @return string containing the algorithm name + std::string getAlgorithm() const { + return(algorithm_); + } + + /// @brief Get the digit set + /// + /// @return string containing the set of digits + const char* getDigitSet() const { + return(digit_set_); + } + + /// @brief Get the digit lookup table + /// + /// @return vector containing the lookup table + const std::vector<uint8_t>& getBitsTable() const { + return(bits_table_); + } + + /// @brief Get the number of data bits represented by a digit + /// + /// @return number of data bits per digit + size_t getBitsPerDigit() { + return(bits_per_digit_); + } + + /// @brief Get the number of digits contained in a group + /// + /// @return number of digits per group + size_t getDigitsPerGroup() const { + return(digits_per_group_); + } + + /// @brief Get the character used for padding out to group size (0 means no padding) + /// + /// @return Character used as a pad byte + uint8_t getPadChar() const { + return(pad_char_); + } + + /// @brief Get the maximum number of pad characters in a group + /// + /// @return Maximum number of pad characters + size_t getMaxPad() { + return(max_pad_); + } + + /// @brief Get the maxium index value of the digit set + /// + /// @return Maxium index value of the digit set + size_t getMaxBitsToDigit() { + return(max_bits_to_digit_); + } + + /// @brief Get the maxium index value of the algorithm bit table + /// + /// @return Maxium index value of the algorithm bit table + size_t getMaxDigitToBits() { + return(max_digit_to_bits_); + } + + /// @brief Indicates whether or not the algorithm's digit set + /// is case-sensitive. + /// + /// @return true if the digit set is case-sensitive. + bool isCaseSensitive() { + return(case_sensitive_); + } + +protected: + /// @brief Name of the algorithm, used for logging + std::string algorithm_; + + /// @brief Set of digits (i.e. alphabet) used for encoding + const char* digit_set_; + + /// @brief Table to translate digits to data used during decoding + /// + /// The table must map all 256 ASCII chars to their corresponding + /// algorithm-specific data value. A data value of 0xee marks + /// a char as whitespace, 0xff marks a char is invalid. + std::vector<uint8_t>bits_table_; + + /// @brief Number of data bits represented by a digit + size_t bits_per_digit_; + + /// @brief Number of digits contained in a group + size_t digits_per_group_; + + /// @brief Character used for padding out to group size (0 means no padding) + const char pad_char_; + + /// @brief Maximum number of pad characters in a group + size_t max_pad_; + + /// @brief Indicates whether or not the algorithm's digit set is case-sensitive. + bool case_sensitive_; + + /// @brief Maxium index value of the digit set + size_t max_bits_to_digit_; + + /// @brief Maxium index value of the algorithm bit table + size_t max_digit_to_bits_; +}; + +/// @brief Class for encoding and decoding binary data using Base64 +/// as described in RFC 4648. +class Base64Encoder : public BaseNEncoder { +public: + /// @brief Set of digits used for encoding in Base64 + static const char* DIGIT_SET; + + /// @brief Table that maps Base64 digits to their binary data value + static const std::vector<uint8_t> BITS_TABLE; + + /// @brief Constructor + Base64Encoder() + : BaseNEncoder("base64", DIGIT_SET, BITS_TABLE, 6, 4, '=', 2, true) { + } + + /// @brief Destructor + ~Base64Encoder() = default; +}; + +/// @brief Class for encoding and decoding binary data using Base32Hex +/// as described in RFC 4648. +class Base32HexEncoder : public BaseNEncoder { +public: + /// @brief Set of digits used for encoding in Base32Hex + static const char* DIGIT_SET; + + /// @brief Table that maps Base32Hex digits to their binary data value + static const std::vector<uint8_t> BITS_TABLE; + + /// @brief Constructor + Base32HexEncoder() + : BaseNEncoder("base32Hex", DIGIT_SET, BITS_TABLE, 5, 8, '=', 6, false) { + } + + /// @brief Destructor + ~Base32HexEncoder() = default; +}; + +/// @brief Class for encoding and decoding binary data using Base16 (aka Hex) +/// as described in RFC 4648. +class Base16Encoder : public BaseNEncoder { +public: + /// @brief Set of digits used for encoding in Base16 + static const char* DIGIT_SET; + + /// @brief Table that maps Base16 digits to their binary data value + static const std::vector<uint8_t> BITS_TABLE; + + /// @brief Constructor + Base16Encoder() + : BaseNEncoder("base16", DIGIT_SET, BITS_TABLE, 4, 2, '=', 0, false) { + } + + /// @brief Destructor + ~Base16Encoder() = default; +}; + /// @brief Encode binary data in the base32-hex format. /// /// @param binary vector object storing the data to be encoded. @@ -57,7 +281,7 @@ void decodeHex(const std::string& encoded_str, std::vector<uint8_t>& output); /// @brief Encode in hexadecimal inline /// /// @param value the value to encode -/// @return 0x followed by the value encoded in hexa +/// @return 0x followed by the value encoded in hex inline std::string toHex(std::string value) { std::vector<uint8_t> bin(value.begin(), value.end()); return ("0x" + encodeHex(bin)); diff --git a/src/lib/util/tests/Makefile.am b/src/lib/util/tests/Makefile.am index 5f8b1e8570..de05ec62e9 100644 --- a/src/lib/util/tests/Makefile.am +++ b/src/lib/util/tests/Makefile.am @@ -24,20 +24,18 @@ if HAVE_GTEST TESTS += run_unittests run_unittests_SOURCES = run_unittests.cc run_unittests_SOURCES += bigint_unittest.cc -run_unittests_SOURCES += base32hex_unittest.cc -run_unittests_SOURCES += base64_unittest.cc run_unittests_SOURCES += boost_time_utils_unittest.cc run_unittests_SOURCES += buffer_unittest.cc run_unittests_SOURCES += chrono_time_utils_unittest.cc run_unittests_SOURCES += csv_file_unittest.cc run_unittests_SOURCES += dhcp_space_unittest.cc run_unittests_SOURCES += doubles_unittest.cc +run_unittests_SOURCES += encode_unittest.cc run_unittests_SOURCES += fd_share_tests.cc run_unittests_SOURCES += fd_tests.cc run_unittests_SOURCES += file_utilities_unittest.cc run_unittests_SOURCES += filename_unittest.cc run_unittests_SOURCES += hash_unittest.cc -run_unittests_SOURCES += hex_unittest.cc run_unittests_SOURCES += io_utilities_unittest.cc run_unittests_SOURCES += labeled_value_unittest.cc run_unittests_SOURCES += memory_segment_local_unittest.cc diff --git a/src/lib/util/tests/base32hex_unittest.cc b/src/lib/util/tests/base32hex_unittest.cc deleted file mode 100644 index ff68ba7167..0000000000 --- a/src/lib/util/tests/base32hex_unittest.cc +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include <config.h> - -#include <stdint.h> - -#include <cctype> -#include <string> -#include <utility> -#include <vector> - -#include <exceptions/exceptions.h> - -#include <util/encode/encode.h> - -#include <gtest/gtest.h> - -using namespace std; -using namespace isc; -using namespace isc::util::encode; - -namespace { - -typedef pair<string, string> StringPair; - -class Base32HexTest : public ::testing::Test { -protected: - Base32HexTest() : encoding_chars("0123456789ABCDEFGHIJKLMNOPQRSTUV=") { - // test vectors from RFC4648 - test_sequence.push_back(StringPair("", "")); - test_sequence.push_back(StringPair("f", "CO======")); - test_sequence.push_back(StringPair("fo", "CPNG====")); - test_sequence.push_back(StringPair("foo", "CPNMU===")); - test_sequence.push_back(StringPair("foob", "CPNMUOG=")); - test_sequence.push_back(StringPair("fooba", "CPNMUOJ1")); - test_sequence.push_back(StringPair("foobar", "CPNMUOJ1E8======")); - - // the same data, encoded using lower case chars (testable only - // for the decode side) - test_sequence_lower.push_back(StringPair("f", "co======")); - test_sequence_lower.push_back(StringPair("fo", "cpng====")); - test_sequence_lower.push_back(StringPair("foo", "cpnmu===")); - test_sequence_lower.push_back(StringPair("foob", "cpnmuog=")); - test_sequence_lower.push_back(StringPair("fooba", "cpnmuoj1")); - test_sequence_lower.push_back(StringPair("foobar", - "cpnmuoj1e8======")); - } - vector<StringPair> test_sequence; - vector<StringPair> test_sequence_lower; - vector<uint8_t> decoded_data; - const string encoding_chars; -}; - -void -decodeCheck(const string& input_string, vector<uint8_t>& output, - const string& expected) -{ - decodeBase32Hex(input_string, output); - EXPECT_EQ(expected, string(output.begin(), output.end())); -} - -TEST_F(Base32HexTest, decode) { - for (auto const& it : test_sequence) { - decodeCheck(it.second, decoded_data, it.first); - } - - // whitespace should be allowed - decodeCheck("CP NM\tUOG=", decoded_data, "foob"); - decodeCheck("CPNMU===\n", decoded_data, "foo"); - decodeCheck(" CP NM\tUOG=", decoded_data, "foob"); - decodeCheck(" ", decoded_data, ""); - - // Incomplete input - EXPECT_THROW(decodeBase32Hex("CPNMUOJ", decoded_data), BadValue); - - // invalid number of padding characters - EXPECT_THROW(decodeBase32Hex("CPNMU0==", decoded_data), BadValue); - EXPECT_THROW(decodeBase32Hex("CO0=====", decoded_data), BadValue); - EXPECT_THROW(decodeBase32Hex("CO=======", decoded_data), // too many ='s - BadValue); - - // intermediate padding isn't allowed - EXPECT_THROW(decodeBase32Hex("CPNMUOG=CPNMUOG=", decoded_data), BadValue); - - // Non canonical form isn't allowed. - // P => 25(11001), so the padding byte would be 01000000 - EXPECT_THROW(decodeBase32Hex("0P======", decoded_data), BadValue); -} - -TEST_F(Base32HexTest, decodeLower) { - for (auto const& it : test_sequence_lower) { - decodeCheck(it.second, decoded_data, it.first); - } -} - -TEST_F(Base32HexTest, encode) { - for (auto const& it : test_sequence) { - decoded_data.assign(it.first.begin(), it.first.end()); - EXPECT_EQ(it.second, encodeBase32Hex(decoded_data)); - } -} - -// For Base32Hex we use handmade mappings, so it's prudent to test the -// entire mapping table explicitly. -TEST_F(Base32HexTest, decodeMap) { - string input(8, '0'); // input placeholder - - // We're going to populate an input string with only the last character - // not equal to the zero character ('0') for each valid base32hex encoding - // character. Decoding that input should result in a data stream with - // the last byte equal to the numeric value represented by the that - // character. For example, we'll generate and confirm the following: - // "00000000" => should be 0 (as a 40bit integer) - // "00000001" => should be 1 (as a 40bit integer) - // ... - // "0000000V" => should be 31 (as a 40bit integer) - // We also check the use of an invalid character for the last character - // surely fails. '=' should be accepted as a valid padding in this - // context; space characters shouldn't be allowed in this context. - - for (int i = 0; i < 256; ++i) { - input[7] = i; - - const char ch = toupper(i); - const size_t pos = encoding_chars.find(ch); - if (pos == string::npos) { - EXPECT_THROW(decodeBase32Hex(input, decoded_data), BadValue); - } else { - decodeBase32Hex(input, decoded_data); - if (ch == '=') { - EXPECT_EQ(4, decoded_data.size()); - } else { - EXPECT_EQ(5, decoded_data.size()); - EXPECT_EQ(pos, decoded_data[4]); - } - } - } -} - -TEST_F(Base32HexTest, encodeMap) { - for (uint8_t i = 0; i < 32; ++i) { - decoded_data.assign(4, 0); - decoded_data.push_back(i); - EXPECT_EQ(encoding_chars[i], encodeBase32Hex(decoded_data)[7]); - } -} - -} diff --git a/src/lib/util/tests/base64_unittest.cc b/src/lib/util/tests/base64_unittest.cc deleted file mode 100644 index 71b4d4214b..0000000000 --- a/src/lib/util/tests/base64_unittest.cc +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (C) 2010-2024 Internet Systems Consortium, Inc. ("ISC") -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include <config.h> - -#include <string> -#include <utility> -#include <vector> - -#include <exceptions/exceptions.h> - -#include <util/encode/encode.h> - -#include <gtest/gtest.h> - -using namespace std; -using namespace isc; -using namespace isc::util::encode; - -namespace { - -typedef pair<string, string> StringPair; - -class Base64Test : public ::testing::Test { -protected: - Base64Test() - { - // test vectors from RFC4648 - test_sequence.push_back(StringPair("", "")); - test_sequence.push_back(StringPair("f", "Zg==")); - test_sequence.push_back(StringPair("fo", "Zm8=")); - test_sequence.push_back(StringPair("foo", "Zm9v")); - test_sequence.push_back(StringPair("foob", "Zm9vYg==")); - test_sequence.push_back(StringPair("fooba", "Zm9vYmE=")); - test_sequence.push_back(StringPair("foobar", "Zm9vYmFy")); - } - vector<StringPair> test_sequence; - vector<uint8_t> decoded_data; -}; - -void -decodeCheck(const string& input_string, vector<uint8_t>& output, - const string& expected) -{ - decodeBase64(input_string, output); - EXPECT_EQ(expected, string(output.begin(), output.end())); -} - -TEST_F(Base64Test, decode) { - for (auto const& it : test_sequence) { - decodeCheck(it.second, decoded_data, it.first); - } - - // whitespace should be allowed - decodeCheck("Zm 9v\tYmF\ny", decoded_data, "foobar"); - decodeCheck("Zm9vYg==", decoded_data, "foob"); - decodeCheck("Zm9vYmE=\n", decoded_data, "fooba"); - decodeCheck(" Zm9vYmE=\n", decoded_data, "fooba"); - decodeCheck(" ", decoded_data, ""); - decodeCheck("\n\t", decoded_data, ""); - - // incomplete input - EXPECT_THROW(decodeBase64("Zm9vYmF", decoded_data), BadValue); - - // only up to 2 padding characters are allowed - EXPECT_THROW(decodeBase64("A===", decoded_data), BadValue); - EXPECT_THROW(decodeBase64("A= ==", decoded_data), BadValue); - - // intermediate padding isn't allowed - EXPECT_THROW(decodeBase64("YmE=YmE=", decoded_data), BadValue); - - // Non canonical form isn't allowed. - // Z => 25(011001), m => 38(100110), 9 => 60(111101), so the padding - // byte would be 0100 0000. - EXPECT_THROW(decodeBase64("Zm9=", decoded_data), BadValue); - // Same for the 1st padding byte. This would make it 01100000. - EXPECT_THROW(decodeBase64("Zm==", decoded_data), BadValue); -} - -TEST_F(Base64Test, encode) { - for (auto const& it : test_sequence) { - decoded_data.assign(it.first.begin(), it.first.end()); - EXPECT_EQ(it.second, encodeBase64(decoded_data)); - } -} -} diff --git a/src/lib/util/tests/encode_unittest.cc b/src/lib/util/tests/encode_unittest.cc new file mode 100644 index 0000000000..6799df2b82 --- /dev/null +++ b/src/lib/util/tests/encode_unittest.cc @@ -0,0 +1,395 @@ +// Copyright (C) 2024 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <exceptions/exceptions.h> +#include <util/encode/encode.h> +#include <testutils/gtest_utils.h> + +#include <gtest/gtest.h> +#include <boost/algorithm/string.hpp> +#include <boost/shared_ptr.hpp> + +#include <vector> +#include <functional> + +using namespace std; +using namespace isc; +using namespace isc::util::encode; + +namespace { + +/// @brief Defines a pointer to BaseNEncoder instances +typedef boost::shared_ptr<BaseNEncoder> BaseNEncoderPtr; + +/// @brief Defines a encoding function. +typedef std::function<std::string (const std::vector<uint8_t>&)> EncodeFunc; + +/// @brief Defines a decoding function. +typedef std::function<void (const std::string&, std::vector<uint8_t>&)> DecodeFunc; + +/// @brief Test fixture fro exercising BaseNEncoder derivatives. +class EncodeDecodeTest : public :: testing::Test { +public: + + /// @brief Constructor + /// + /// @param encoder pointer to an encoder instance to use in tests + /// @param encode_func encoding function to test + /// @param decode_func decoding function to test + EncodeDecodeTest(BaseNEncoderPtr encoder, EncodeFunc encode_func, DecodeFunc decode_func) + : encoder_(encoder), encode_func_(encode_func), decode_func_(decode_func) { + // From RFC4648 test vectors. + valid_input_strings_.push_back(""); + valid_input_strings_.push_back("f"); + valid_input_strings_.push_back("fo"); + valid_input_strings_.push_back("foo"); + valid_input_strings_.push_back("foob"); + valid_input_strings_.push_back("fooba"); + valid_input_strings_.push_back("foobar"); + } + + /// @brief Destructor + ~EncodeDecodeTest() = default; + + /// @brief Verifies encoding and decoding of test vectors from RFC4648 + /// + /// Tests encoding and decoding functions using RFC supplied test vectors + /// by: + /// -# Encoding an input string and verifying the output against the expected + /// encoded string. + /// -# Decoding the encoded output and verifying it against the original input + /// string + /// -# If the algorithm is case sensitive, convert the encoded output string to + /// lower case and recheck that it is decoded properly + void encodeDecode() { + ASSERT_EQ(expected_encoded_strings_.size(), valid_input_strings_.size()); + // For each valid input string: + // -# encode it and verify the encoded result is as expected + // -# decode the encoded result and verify it yields the original valid + // input string + // -# convert the encoded result to lower case and verify decoding + // yields correct result. + auto expected_output_str = expected_encoded_strings_.begin(); + for ( const auto& input : valid_input_strings_ ) { + std::vector<uint8_t>input_data(input.begin(), input.end()); + std::string output_str; + ASSERT_NO_THROW_LOG(output_str = (encode_func_)(input_data)); + ASSERT_EQ(output_str, *expected_output_str) << "input string: [" << input << "]"; + ++expected_output_str; + + std::vector<uint8_t> decoded_output; + ASSERT_NO_THROW_LOG((decode_func_)(output_str, decoded_output)); + ASSERT_EQ(decoded_output, input_data); + + if (!encoder_->isCaseSensitive()) { + const std::string lower_case_str = boost::algorithm::to_lower_copy(output_str); + decoded_output.clear(); + ASSERT_NO_THROW_LOG((decode_func_)(lower_case_str, decoded_output)); + ASSERT_EQ(decoded_output, input_data); + } + } + } + + /// @brief Verifies that a list of encoded strings produces the expected + /// decoded results. + /// + /// @param encoded_strings list of encoded strings to decode + /// @param expected_strings list of expected decoded data as strings + void decode(std::vector<std::string>& encoded_strings, + std::vector<std::string>& expected_strings) { + + ASSERT_EQ(encoded_strings.size(), expected_strings.size()); + auto expected_str = expected_strings.begin(); + for ( const auto& encoded_str : encoded_strings ) { + std::vector<uint8_t> decoded_output; + ASSERT_NO_THROW_LOG((decode_func_)(encoded_str, decoded_output)); + std::string tmp(decoded_output.begin(), decoded_output.end()); + EXPECT_EQ(tmp, *expected_str); + ++expected_str; + } + } + + /// @brief Verifies that a list of invalid encoded strings fail to + /// decode appropriately + /// + /// @param encoded_strings list of invalid encoded strings + void decodeInvalid(std::vector<std::string>& encoded_strings) { + for ( const auto& encoded_str : encoded_strings ) { + std::vector<uint8_t> decoded_output; + EXPECT_THROW((decode_func_)(encoded_str, decoded_output), BadValue); + } + } + + /// @brief Verifies the integrity to encoder's digit set and bit table. + void mapTest() { + size_t num_digits = strlen(encoder_->getDigitSet()); + size_t whitespaces = 0; + size_t valid_digits = 0; + size_t bad_chars = 0; + size_t pad_chars = 0; + size_t upper_cased = 0; + + auto pad_char = encoder_->getPadChar(); + + // Ensure the bit table is the proper size. + ASSERT_EQ(encoder_->getBitsTable().size(), 256); + + // Iterate over the whole ASCII character set: + // 1. Convert the ASCII value to its encoded binary bit value. + // 2. Classify the value as whitespace, invalid, pad or valid + // 3. For valid digits verify they exist in the digit set + for (uint16_t ascii = 0; ascii < 256; ++ascii) { + // Look up the binary data for the digit. + // No value under 256 should throw. + uint8_t bits; + ASSERT_NO_THROW_LOG(bits = encoder_->digitToBits(ascii)); + + // Classify the bits value we found. + switch(bits) { + case 0xee: + ASSERT_TRUE(isspace(ascii)); + ++whitespaces; + break; + case 0xff: + ++bad_chars; + break; + default: { + if (pad_char && ascii == pad_char) { + ++pad_chars; + } else { + // Verify the ascii value is in the digit set. + if (encoder_->isCaseSensitive()) { + ASSERT_TRUE(strchr(encoder_->getDigitSet(), ascii)) + << "ascii: " << std::hex << ascii; + ++valid_digits; + } else { + auto check_ascii = toupper(ascii); + ASSERT_TRUE(strchr(encoder_->getDigitSet(), check_ascii)) + << "ascii: " << std::hex << ascii + << " check_ascii: " << std::hex << check_ascii; + + if (check_ascii == ascii){ + ++valid_digits; + } else { + ++upper_cased; + } + } + } + + break; + }} + } + + // Verify that we see all valid digits. + EXPECT_EQ(valid_digits, num_digits); + + // Verify that all of the ASCII values are accounted for. + EXPECT_EQ((valid_digits + upper_cased + whitespaces + bad_chars + pad_chars), 256) + << " : " << valid_digits + << " + " << upper_cased + << " + " << whitespaces + << " + " << bad_chars + << " + " << pad_chars; + } + + BaseNEncoderPtr encoder_; + EncodeFunc encode_func_; + DecodeFunc decode_func_; + std::vector<std::string> valid_input_strings_; + std::vector<std::string> expected_encoded_strings_; +}; + + +/// @brief Test Fixture for Base64 encoding +class Base64Test : public EncodeDecodeTest { +public: + Base64Test() + : EncodeDecodeTest(BaseNEncoderPtr(new Base64Encoder()), encodeBase64, decodeBase64) { + // From RFC4648 test vectors. + expected_encoded_strings_.push_back(""); + expected_encoded_strings_.push_back("Zg=="); + expected_encoded_strings_.push_back("Zm8="); + expected_encoded_strings_.push_back("Zm9v"); + expected_encoded_strings_.push_back("Zm9vYg=="); + expected_encoded_strings_.push_back("Zm9vYmE="); + expected_encoded_strings_.push_back("Zm9vYmFy"); + } +}; + +/// @brief Test Fixture for Base32Hex encoding +class Base32HexTest : public EncodeDecodeTest { +public: + Base32HexTest() + : EncodeDecodeTest(BaseNEncoderPtr(new Base32HexEncoder()), encodeBase32Hex, decodeBase32Hex) { + // From RFC4648 test vectors. + expected_encoded_strings_.push_back(""); + expected_encoded_strings_.push_back("CO======"); + expected_encoded_strings_.push_back("CPNG===="); + expected_encoded_strings_.push_back("CPNMU==="); + expected_encoded_strings_.push_back("CPNMUOG="); + expected_encoded_strings_.push_back("CPNMUOJ1"); + expected_encoded_strings_.push_back("CPNMUOJ1E8======"); + } +}; + +/// @brief Test Fixture for Base16 encoding +class Base16Test : public EncodeDecodeTest { +public: + Base16Test() + : EncodeDecodeTest(BaseNEncoderPtr(new Base16Encoder()), encodeHex, decodeHex) { + // From RFC4648 test vectors. + expected_encoded_strings_.push_back(""); + expected_encoded_strings_.push_back("66"); + expected_encoded_strings_.push_back("666F"); + expected_encoded_strings_.push_back("666F6F"); + expected_encoded_strings_.push_back("666F6F62"); + expected_encoded_strings_.push_back("666F6F6261"); + expected_encoded_strings_.push_back("666F6F626172"); + } +}; + +// Verify RFC test vectors for Base64 +TEST_F(Base64Test, validEncodeDecode) { + encodeDecode(); +} + +// Verify whitespaces are handled properly in Base64 +TEST_F(Base64Test, whiteSpace) { + std::vector<std::string> encoded_strings = { + "Zm 9v\tYmF\ny", + "Zm9vYg==", + "Zm9vYmE=\n", + " Zm9vYmE=\n", + " ", + "\n\t" + }; + + std::vector<std::string> expected_strings = { + "foobar", + "foob", + "fooba", + "fooba", + "", + "" + }; + + decode(encoded_strings, expected_strings); +} + +// Verify invalid encodings are handled properly in Base64 +TEST_F(Base64Test, decodeInvalid) { + std::vector<std::string> encoded_strings = { + // incomplete input + "Zm9vYmF", + // only up to 2 padding characters are allowed + "A===", + "A= ==", + // intermediate padding isn't allowed + "YmE=YmE=", + // Non canonical form isn't allowed. + "Zm9=", + "Zm==", + }; + + decodeInvalid(encoded_strings); +} + +// Verify mappings for Base64 +TEST_F(Base64Test, mappingCheck) { + mapTest(); +} + +// Verify mappings for Base32Hex +TEST_F(Base32HexTest, mappingCheck) { + mapTest(); +} + +// Verify RFC test vectors for Base32Hex +TEST_F(Base32HexTest, validEncodeDecode) { + encodeDecode(); +} + +// Verify whitespaces are handled properly in Base32Hex +TEST_F(Base32HexTest, whiteSpace) { + std::vector<std::string> encoded_strings = { + "CP NM\tUOG=", + "CPNMU===\n", + " CP NM\tUOG=", + " " + }; + + std::vector<std::string> expected_strings = { + "foob", + "foo", + "foob", + "" + }; + + decode(encoded_strings, expected_strings); +} + +// Verify invalid encodings are handled properly in Base32Hex +TEST_F(Base32HexTest, decodeInvalid) { + std::vector<std::string> encoded_strings = { + // Incomplete input + "CPNMUOJ", + // invalid number of padding characters + "CPNMU0==", + "CO0=====", + "CO=======", + // intermediate padding isn't allowed + "CPNMUOG=CPNMUOG=", + // Non canonical form isn't allowed. + "0P======" + }; + + decodeInvalid(encoded_strings); +} + +// Verify RFC test vectors for Base16 +TEST_F(Base16Test, validEncodeDecode) { + encodeDecode(); +} + +// Verify whitespaces are handled properly in Base16 +TEST_F(Base16Test, whiteSpace) { + std::vector<std::string> encoded_strings = { + "66 6F\t6F62", + "66 6F6F\n", + " 66\v\t6F6F62", + " " + }; + + std::vector<std::string> expected_strings = { + "foob", + "foo", + "foob", + "" + }; + + decode(encoded_strings, expected_strings); +} + +// Verify invalid encodings are handled properly in Base16 +TEST_F(Base16Test, decodeInvalid) { + std::vector<std::string> encoded_strings = { + // Non hex digits should fail + "lx", + // Encoded string must have an even number of characters. + "dea" + }; + + decodeInvalid(encoded_strings); +} + +// Verify mappings for Base16 +TEST_F(Base16Test, mappingCheck) { + mapTest(); +} + +} diff --git a/src/lib/util/tests/hex_unittest.cc b/src/lib/util/tests/hex_unittest.cc deleted file mode 100644 index 27fa166bbb..0000000000 --- a/src/lib/util/tests/hex_unittest.cc +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright (C) 2010-2015 Internet Systems Consortium, Inc. ("ISC") -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -#include <config.h> - -#include <stdint.h> - -#include <vector> -#include <string> - -#include <exceptions/exceptions.h> - -#include <util/encode/encode.h> -#include <testutils/gtest_utils.h> - -#include <gtest/gtest.h> - -using namespace std; -using namespace isc; -using namespace isc::util::encode; - -namespace { -const string hex_txt("DEADBEEFDECADE"); -const string hex_txt_space("DEAD BEEF DECADE"); -const string hex_txt_lower("deadbeefdecade"); - -class HexTest : public ::testing::Test { -protected: - HexTest() : encoding_chars("0123456789ABCDEF") {} - vector<uint8_t> decoded_data; - const string encoding_chars; -}; - -TEST_F(HexTest, encodeHex) { - std::vector<uint8_t> data; - - data.push_back(0xde); - data.push_back(0xad); - data.push_back(0xbe); - data.push_back(0xef); - data.push_back(0xde); - data.push_back(0xca); - data.push_back(0xde); - EXPECT_EQ(hex_txt, encodeHex(data)); -} - -void -compareData(const std::vector<uint8_t>& data) { - EXPECT_EQ(0xde, data[0]); - EXPECT_EQ(0xad, data[1]); - EXPECT_EQ(0xbe, data[2]); - EXPECT_EQ(0xef, data[3]); - EXPECT_EQ(0xde, data[4]); - EXPECT_EQ(0xca, data[5]); - EXPECT_EQ(0xde, data[6]); -} - -TEST_F(HexTest, decodeHex) { - std::vector<uint8_t> result; - - decodeHex(hex_txt, result); - compareData(result); - - // lower case hex digits should be accepted - result.clear(); - decodeHex(hex_txt_lower, result); - compareData(result); - - // white space should be ignored - result.clear(); - decodeHex(hex_txt_space, result); - compareData(result); - - // Bogus input: should fail - result.clear(); - EXPECT_THROW(decodeHex("1x", result), BadValue); - - // Bogus input: encoded string must have an even number of characters. - result.clear(); - EXPECT_THROW(decodeHex("dea", result), BadValue); -} - -// For Hex encode/decode we use handmade mappings, so it's prudent to test the -// entire mapping table explicitly. -TEST_F(HexTest, decodeMap) { - string input("00"); // input placeholder - - // See Base32HexTest.decodeMap for details of the following tests. - for (int i = 0; i < 256; ++i) { - input[1] = i; - - const char ch = toupper(i); - const size_t pos = encoding_chars.find(ch); - if (pos == string::npos) { - if (!std::isspace(ch)) { - EXPECT_THROW(decodeHex(input, decoded_data), BadValue) << "input:" << input; - } else { - EXPECT_NO_THROW_LOG(decodeHex(input, decoded_data)); - EXPECT_EQ(0, decoded_data.size()); - } - } else { - decodeHex(input, decoded_data); - EXPECT_EQ(1, decoded_data.size()); - EXPECT_EQ(pos, decoded_data[0]); - } - } -} - -TEST_F(HexTest, encodeMap) { - for (uint8_t i = 0; i < 16; ++i) { - decoded_data.clear(); - decoded_data.push_back(i); - EXPECT_EQ(encoding_chars[i], encodeHex(decoded_data)[1]); - } -} - -} |