]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#3209] Created new BaseNEncoder class
authorThomas Markwalder <tmark@isc.org>
Tue, 30 Jan 2024 20:47:54 +0000 (15:47 -0500)
committerThomas Markwalder <tmark@isc.org>
Wed, 7 Feb 2024 13:58:47 +0000 (13:58 +0000)
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

src/lib/util/encode/encode.cc
src/lib/util/encode/encode.h
src/lib/util/tests/Makefile.am
src/lib/util/tests/base32hex_unittest.cc [deleted file]
src/lib/util/tests/base64_unittest.cc [deleted file]
src/lib/util/tests/encode_unittest.cc [new file with mode: 0644]
src/lib/util/tests/hex_unittest.cc [deleted file]

index 52b15e6907fb6b9777614a70bf686c8b50280d33..cb06cdac0d3bbac8ae37eda158b66712e21e782e 100644 (file)
@@ -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
index 2e6bca0e54cedb6fc4e9c438df2c9d6ccffd1342..13d3c3e96e880ca4370a32e7932a3fb24c6659cf 100644 (file)
@@ -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));
index 5f8b1e8570130e26ee7e37a8507e194269197048..de05ec62e97c595ebf194a1c56c2281eedf55451 100644 (file)
@@ -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 (file)
index ff68ba7..0000000
+++ /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 (file)
index 71b4d42..0000000
+++ /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 (file)
index 0000000..6799df2
--- /dev/null
@@ -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 (file)
index 27fa166..0000000
+++ /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]);
-    }
-}
-
-}