#include <stdint.h>
#include <stdexcept>
#include <string>
+#include <cstring>
#include <vector>
using namespace std;
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];
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;
}
// 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_);
}
}
}
}
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
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;
}
}
+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
--- /dev/null
+// 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();
+}
+
+}