summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Markwalder <tmark@isc.org>2024-01-30 21:47:54 +0100
committerThomas Markwalder <tmark@isc.org>2024-02-07 14:58:47 +0100
commit6949dd94a3f5e8fe5c5930d1cd9ce760512bb001 (patch)
tree5c61b2b2046e93e0153e96ac45fc8882c6d80554
parent[#3209] Initial commit (diff)
downloadkea-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.cc249
-rw-r--r--src/lib/util/encode/encode.h226
-rw-r--r--src/lib/util/tests/Makefile.am4
-rw-r--r--src/lib/util/tests/base32hex_unittest.cc152
-rw-r--r--src/lib/util/tests/base64_unittest.cc89
-rw-r--r--src/lib/util/tests/encode_unittest.cc395
-rw-r--r--src/lib/util/tests/hex_unittest.cc120
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]);
- }
-}
-
-}