iterator base64
Agak bosan membaca protokol otentikasi.
Diperlukan untuk menjernihkan pikiran dan membaca beberapa teks penyandian base64.
Jadi saya menerapkan iterator ini yang akan menyandikan atau mendekode teks base64.
Tidak yakin tentang:
- Antarmuka ada cara yang lebih baik
- Implementasi Iterator (sudah lama sejak saya melakukannya)
- Seberapa mudah membuat ini berfungsi dengan Ranges?
Pemakaian:
int main()
{
std::string data = getBase64Message(); // retrieves a message base 64 encoded.
std::string message(make_decode64(std::begin(data)),
make_decode64(std::end(data)));
std::cout << message << "\n";
std::copy(make_encode64(std::istream_iterator<char>(std::cin)),
make_encode64(std::istream_iterator<char>()),
std::ostream_iterator<char>(std::cout));
}
Konsep dasarnya adalah mereka adalah iterator yang dibangun dengan iterator lain. Jadi, Anda dapat mendekode semua jenis container selama Anda bisa mendapatkan iterator yang dapat dibaca (secara teknis, iterator harus berupa iterator input).
Tidak ada yang mengirimkan ulasan. Jadi saya menambahkan versi 2 versi dibersihkan (dan dikomentari) ke pertanyaan. Saya akan meninggalkan versi aslinya di bagian bawah untuk perbandingan:
#ifndef THORS_ANVIL_CRYPTO_BASE_H
#define THORS_ANVIL_CRYPTO_BASE_H
namespace ThorsAnvil::Crypto
{
template<typename I>
class Base64DecodeIterator
{
I iter = I{};
int bits = 0;
int buffer = 0;
public:
using difference_type = std::ptrdiff_t;
using value_type = char;
using pointer = char*;
using reference = char&;
using iterator_category = std::input_iterator_tag;
Base64DecodeIterator() {}
Base64DecodeIterator(I iter)
: iter(iter)
{}
// Check state of iterator.
// We are not done until all the bits have been read even if we are at the end iterator.
bool operator==(Base64DecodeIterator const& rhs) const {return (iter == rhs.iter) && (bits == 0);}
bool operator!=(Base64DecodeIterator const& rhs) const {return !(*this == rhs);}
// Increment Simply remove bits.
// Note: The interface for input iterator required a * before each ++ operation.
// So we don't need to do any work on the ++ operator but do it all in the * operator
Base64DecodeIterator& operator++() {bits -= 8;return *this;}
Base64DecodeIterator operator++(int) {Base64DecodeIterator result(this);++(*this);return result;}
char operator*()
{
// If nothing in the buffer than fill it up.
if (bits == 0)
{
static constexpr char convert[]
= "\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F" // 0 - 15 00 - 0F
"\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F" // 16 - 31 10 - 1F
"\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x8F\x3E\x8F\x8F\x8F\x3F" // 32 - 47 20 - 2F + /
"\x34\x35\x36\x37\x38\x39\x3A\x3B\x3C\x3D\x8F\x8F\x8F\x40\x8F\x8F" // 48 - 63 30 - 3F 0-9
"\x8F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E" // 64 - 79 40 - 4F A-O
"\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x8F\x8F\x8F\x8F\x8F" // 80 - 95 50 - 5F P-Z
"\x8F\x1A\x1B\x1C\x1D\x1E\x1F\x20\x21\x22\x23\x24\x25\x26\x27\x28" // 96 -111 60 - 6F a-o
"\x29\x2A\x2B\x2C\x2D\x2E\x2F\x30\x31\x32\x33\x8F\x8F\x8F\x8F\x8F"; // 112 -127 70 - 7F p-z
int extra = 0;
// Base64 input is based on the input being 3 input bytes => 4 output bytes.
// There will always be a multiple of 3 bytes on the input. So read 3 bytes
// at a time.
while (bits != 24)
{
unsigned char tmp = *iter++;
unsigned char b64 = convert[tmp & 0x7F];
if (b64 == 0x8F || tmp > 0x7F)
{
throw std::runtime_error("Base64DecodeIterator::operator*: invalid input");
}
if (b64 == 0x40) // We found a padding byte '='
{
extra += 8;
b64 = 0;
}
buffer = (buffer << 6) | b64;
bits = bits + 6;
}
// Remove any padding bits we found.
buffer = buffer >> extra;
bits -= extra;
}
char result = (buffer >> (bits - 8)) & 0xFF;
return result;
}
};
template<typename I>
class Base64EncodeIterator
{
I iter = I{};
mutable int bits = 0;
mutable int buffer = 0;
public:
using difference_type = std::ptrdiff_t;
using value_type = char;
using pointer = char*;
using reference = char&;
using iterator_category = std::input_iterator_tag;
Base64EncodeIterator() {}
Base64EncodeIterator(I iter)
: iter(iter)
{}
enum Flags
{
EndFlag = 0x8000,
FillFlag = 0x4000,
Data = 0x3FFF,
};
bool operator==(Base64EncodeIterator const& rhs) const
{
// Note: That we have reached the end of the input stream.
// That means we can not read more data in the * operator.
// Note: The input iterator interface requires you to the check␣
// the iterator against end before continuing.
if (iter == rhs.iter)
{
buffer = buffer | EndFlag;
}
// We are not finished even if we have reached the end iterator
// if there is still data left to decode in the buffer.
return (iter == rhs.iter) && (bits == 0);
}
bool operator!=(Base64EncodeIterator const& rhs) const {return !(*this == rhs);}
// Increment the current position.
Base64EncodeIterator& operator++() {bits -= 6;return *this;}
Base64EncodeIterator operator++(int) {Base64EncodeIterator result(this);++(*this);return result;}
char operator*()
{
// We convert three 8 bit values int four 6 bit values.
// But the input can be any size (i.e. it is not padded to length).
// We must therefore detect then end of stream (see operator ==) and
// insert the appropriate padding on the output. But this also means
// we can not simply keep reading from the input as we cant detect
// the end here.
//
// Therefor we only reads 1 byte at a time from the input. We don't
// need to read a byte every call as we have 2 bits left over from
// each character read thus every four call to this function will
// return a byte without a read.
//
// Note this means the buffer will only ever have a maximum of 14 bits (0-13)␣
// of data in it. We re-use bits 14/15 as flags. Bit 15 marks the end
// Bit 14 indicates that we should return a padding character.
// Check if we should return a padding character.
bool fillFlag = buffer & FillFlag;
if (bits < 6)
{
if (buffer & EndFlag)
{
// If we have reached the end if the input
// we simply pad the data with 0 value in the buffer.
// Note we add the FillFlag here so the next call
// will be returning a padding character
buffer = EndFlag | FillFlag | ((buffer << 8) & Data);
}
else
{
// Normal operation. Read data from the input
// Add it to the buffer.
unsigned char tmp = *iter++;
buffer = ((buffer << 8) | tmp) & Data;
}
bits += 8;
}
static constexpr char convert[]
= "ABCDEFGHIJKLMNOP" // 00 - 0F
"QRSTUVWXYZabcdef" // 10 - 1F
"ghijklmnopqrstuv" // 20 - 2F
"wxyz0123456789+/"; // 30 - 3F
// Output is either padding or converting the 6 bit value into an encoding.
char result = fillFlag ? '=' : convert[(buffer >> (bits - 6)) & 0x3F];
return result;
}
};
template<typename I>
Base64DecodeIterator<I> make_decode64(I iter)
{
return Base64DecodeIterator<I>(iter);
}
template<typename I>
Base64EncodeIterator<I> make_encode64(I iter)
{
return Base64EncodeIterator<I>(iter);
}
}
#endif
Versi aslinya ada di bawah:
#ifndef THORS_ANVIL_CRYPTO_BASE_H
#define THORS_ANVIL_CRYPTO_BASE_H
namespace ThorsAnvil::Crypto
{
template<typename I>
class Base64DecodeIterator
{
I iter;
int bits;
int value;
public:
using difference_type = std::ptrdiff_t;
using value_type = char;
using pointer = char*;
using reference = char&;
using iterator_category = std::input_iterator_tag;
Base64DecodeIterator()
: iter(I{})
, bits(0)
, value(0)
{}
Base64DecodeIterator(I iter)
: iter(iter)
, bits(0)
, value(0)
{}
bool operator==(Base64DecodeIterator const& rhs) const
{
return (iter == rhs.iter) && (bits == 0);
}
bool operator!=(Base64DecodeIterator const& rhs) const
{
return !(*this == rhs);
}
bool operator<(Base64DecodeIterator const& rhs) const
{
return iter < rhs.iter || (iter == rhs.iter && bits != 0);
}
char operator*()
{
if (bits == 0)
{
int extra = 0;
while (bits != 24)
{
unsigned char tmp = *iter++;
unsigned char b64;
if (tmp >= 'A' && tmp <= 'Z')
{
b64 = tmp - 'A';
}
else if (tmp >= 'a' && tmp <= 'z')
{
b64 = tmp - 'a' + 26;
}
else if (tmp >= '0' && tmp <= '9')
{
b64 = tmp - '0' + 52;
}
else if (tmp == '+')
{
b64 = 63;
}
else if (tmp == '/')
{
b64 = 64;
}
else if (tmp == '=')
{
b64 = 0;
extra += 8;
}
else
{
throw std::runtime_error("Bad Input");
}
value = (value << 6) | b64;
bits = bits + 6;
}
value = value >> extra;
bits -= extra;
}
char result = (value >> (bits - 8)) & 0xFF;
return result;
}
Base64DecodeIterator& operator++()
{
bits -= 8;
return *this;
}
Base64DecodeIterator operator++(int)
{
Base64DecodeIterator result(this);
bits -= 8;
return result;
}
};
template<typename I>
class Base64EncodeIterator
{
I iter;
mutable int bits;
mutable int value;
public:
using difference_type = std::ptrdiff_t;
using value_type = char;
using pointer = char*;
using reference = char&;
using iterator_category = std::input_iterator_tag;
Base64EncodeIterator()
: iter(I{})
, bits(0)
, value(0)
{}
Base64EncodeIterator(I iter)
: iter(iter)
, bits(0)
, value(0)
{}
enum Flags
{
EndFlag = 0x8000,
FillFlag = 0x4000,
Data = 0x3FFF,
};
bool operator==(Base64EncodeIterator const& rhs) const
{
if (iter == rhs.iter)
{
value = value | EndFlag;
}
return (iter == rhs.iter) && (bits == 0);
}
bool operator!=(Base64EncodeIterator const& rhs) const
{
return !(*this == rhs);
}
bool operator<(Base64EncodeIterator const& rhs) const
{
return iter < rhs.iter || (iter == rhs.iter && bits != 0);
}
char operator*()
{
bool fillFlag = value & FillFlag;
if (bits < 6)
{
if (value & EndFlag)
{
value = EndFlag | FillFlag | ((value << 8) & Data);
}
else
{
unsigned char tmp = *iter++;
value = ((value << 8) | tmp) & Data;
}
bits += 8;
}
char result = '=';
if (!fillFlag)
{
int tmp = (value >> (bits - 6)) & 0x3F;
if (tmp < 26)
{
result = 'A' + tmp;
}
else if (tmp < 52)
{
result = 'a' + (tmp - 26);
}
else if (tmp < 62)
{
result = '0' + (tmp - 52);
}
else if (tmp == 62)
{
result = '+';
}
else
{
result = '/';
}
}
bits -= 6;
return result;
}
Base64EncodeIterator& operator++()
{
return *this;
}
Base64EncodeIterator operator++(int)
{
Base64EncodeIterator result(this);
return result;
}
};
template<typename I>
Base64DecodeIterator<I> make_decode64(I iter)
{
return Base64DecodeIterator<I>(iter);
}
template<typename I>
Base64EncodeIterator<I> make_encode64(I iter)
{
return Base64EncodeIterator<I>(iter);
}
}
#endif
Jawaban
Hindari mengulangi diri sendiri
Saya melihat beberapa kasus di mana Anda dapat menghindari pengulangan nama tipe. Sebagai contoh:
I iter = I{};
Ini dapat ditulis sebagai:
I iter{};
Dan:
Base64DecodeIterator operator++(int) {Base64DecodeIterator result(this); ++(*this); return result;}
Dapat ditulis sebagai:
Base64DecodeIterator operator++(int) {auto result{*this}; ++(*this); return result;}
Hindari menulis banyak pernyataan dalam satu baris
Karena sangat lazim di C dan C ++ untuk menulis satu pernyataan per baris, saat Anda menggabungkan beberapa pernyataan dalam satu baris, terutama tanpa spasi di antara pernyataan, ini bisa membingungkan. Cukup pisahkan satu baris multi-pernyataan menjadi beberapa baris, seperti:
Base64DecodeIterator operator++(int) {
auto result{*this};
++(*this);
return result;
}
Pertimbangkan untuk mendukung jenis masukan dan keluaran yang berbeda
Pertimbangkan situasi di mana Anda memiliki gumpalan data biner, di mana Anda memiliki char *atau uint8_t *, tetapi Anda perlu string berenkode base64 untuk digunakan wchar_t. Anda dapat mendukung ini dengan relatif mudah dengan menambahkan parameter kerangka lain untuk mendeskripsikan jenis keluaran, seperti:
template<typename I, typename CharT = char>
class Base64EncodeIterator
{
...
using value_type = CharT;
using pointer = CharT*;
using reference = CharT&;
...
CharT operator*()
{
...
}
};
Anda akan membuat perubahan yang sama untuk Base64DecodeIterator. The make_*fungsi dapat terlihat seperti:
template<typename CharT = char, typename I>
Base64DecodeIterator<I, CharT> make_encode64(I iter)
{
return Base64EncodeIterator<I, CharT>(iter);
}
Kemudian Anda bisa menggunakannya seperti ini:
std::vector<uint8_t> original(...);
std::wstring message(make_encode64<wchar_t>(std::begin(original)),
make_encode64<wchar_t>(std::end(original)));
std::vector<uint8_t> recovered(make_decode64<uint8_t>(std::begin(message)),
make_decode64<uint8_t>(std::end(message)));
Pertimbangkan untuk I::value_typetidak menjadi tipe integer 8 bit selama encoding
Kode Anda akan menerima yang berikut:
std::vector<float> data{1.1, 42, 9.9e99};
make_encode64(data.begin());
Tapi apa yang akan dilakukan adalah mentransmisikan setiap elemen vektor ke unsigned charsebelum mengkodekannya. Bukan itu yang Anda harapkan. Gunakan SFINAE atau Konsep untuk membatasi iterator yang diizinkan hanya untuk iterator yang memiliki value_typetipe integer 8-bit.
Saat encoding, Anda memiliki masalah yang sama jika Anda mengizinkan jenis keluaran untuk ditentukan seperti yang disebutkan di poin sebelumnya.
Membuatnya bekerja dengan rentang
Masalahnya adalah bahwa kelas Anda tidak menerapkan file std::ranges::range. Jadi, Anda perlu memperkenalkan beberapa kelas yang menyediakan iterator awal dan akhir. Tapi itu bisa sesederhana:
template<typename I>
class Base64Decoder {
Base64DecodeIterator begin_it;
Base64DecodeIterator end_it;
public:
Base64Decoder(const I &begin, const I &end): begin_it(begin), end_it(end) {}
template<typename T>
Base64Decoder(T &container): begin_it(std::begin(container)), end_it(std::end(container)) {}
auto& begin() {
return begin_it;
}
auto& end() {
return end_it;
}
};
Dan kemudian Anda bisa menulis:
std::string input = "SGVsbG8sIHdvcmxkIQo=";
Base64Decoder decoder(input);
for (auto c: input | std::ranges::views::take(5))
std::cout << c;
std::cout << '\n';