Ok, the following code should satisfy requirements but it hasn't been
extensively tested. Feedback is welcome. I decided to code from
scratch without using library functions.
Thanks,
Paul
// Problem is
https://cryptopals.com/sets/1/challenges/1
//49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d
// should produce
// SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t
#include <iostream>
#include <unordered_map>
#include <cctype>
#include <cmath>
#include <utility>
#include <vector>
std::unordered_map<char, int> buildHexMap()
{
std::unordered_map<char, int> hexMap;
for(char letter = 'a'; letter <= 'f'; ++letter)
hexMap[std::toupper(letter)] = hexMap[letter] = 10 - 'a' + letter;
for(char letter = '1'; letter <= '9'; ++letter)
hexMap[letter] = 1 - '1' + letter;
return hexMap;
}
// Use this map to build a std::vector<int> from a string
std::vector<int> hex(const std::string& hexString, std::unordered_map<char, int> hexMap = buildHexMap())
{
std::vector<int> result(hexString.size());
for(int i = 0; i < result.size(); ++i)
result[i] = hexMap[hexString[i]];
return result;
}
//
https://en.wikipedia.org/wiki/Base64 is reference
std::unordered_map<int, char> build64Map()
{
constexpr int alphabetSize = 26;
std::unordered_map<int, char> base64Map;
for(char letter = 'A'; letter <= 'Z'; ++letter)
{
base64Map[letter - 'A'] = letter;
base64Map [alphabetSize - 'a' + std::tolower(letter)] = std::tolower(letter);
}
for(char letter = '0'; letter <= '9'; ++letter)
base64Map[52 - '0' + letter] = letter;
base64Map[62] = '+' ;
base64Map[63] = '/';
return base64Map;
}
// A naive conversion can result in excessive zeros at the front.
// These are now removed.
std::vector<int> trim(const std::vector<int>& vec)
{
int i = 0;
while(i < vec.size() && !vec[i])
++i;
if(i == vec.size())
return {0};
return std::vector<int>(vec.begin() + i, vec.end());
}
// A block of 3 hex digits is equivalent to a block of two hex digits
// Use this equivalence to transform a vector of hex digits to a vector of base 64 digits
std::vector<int> hexToBase64(const std::vector<int>& hex)
{
if(hex.empty())
return hex;
constexpr int hexBlock = 3;
constexpr int base64Block = 2;
constexpr int convertBase = 64;
constexpr int hexBase = 16;
const double hexSize = hex.size(); // cast to double for ceiling operation
std::vector<int> result( std::ceil(hexSize/hexBlock) * base64Block);
int finalComponent = result.size() - 1;
for(int i = hexSize - 1; i>= 0; i -= hexBlock)
{
int hexValue = hex[i];
if(i)
hexValue += hexBase * hex[i - 1];
if(i >= 2)
hexValue += hexBase * hexBase * hex[i - 2];
result[finalComponent--] = hexValue % convertBase;
result[finalComponent--] = hexValue / convertBase;
}
return trim(result);
}
std::string hexToBase64(const std::string& hexString)
{
const std::vector<int>& base64 = hexToBase64(hex(hexString));
std::unordered_map<int, char> base64Map = build64Map();
std::string result;
for(int i : base64)
result += base64Map[i];
return result;
}
int main()
{
const std::string hex = "49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d";
const std::string answer = "SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t";
std::cout << ( hexToBase64(hex) == answer ? "Test passed" : "Test failed");
}