trình lặp base64

Aug 24 2020

Hơi chán khi đọc các giao thức xác thực.
Cần thiết để giải tỏa tâm trí và đọc một số văn bản mã hóa base64.

Vì vậy, tôi đã triển khai các trình vòng lặp này sẽ mã hóa hoặc giải mã văn bản base64.

Không chắc chắn về:

  • Giao diện có một cách tốt hơn
  • Thực hiện lặp lại (đã lâu rồi kể từ khi tôi làm điều đó)
  • Làm thế nào dễ dàng để thực hiện điều này với Ranges?

Sử dụng:

 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));

 }

Khái niệm cơ bản là chúng là các trình vòng lặp được xây dựng với các trình vòng lặp khác. Vì vậy, bạn có thể giải mã bất kỳ loại vùng chứa nào miễn là bạn có thể có được một trình lặp có thể đọc được cho nó (về mặt kỹ thuật, trình vòng lặp phải là một trình lặp đầu vào).


Không ai đã gửi một đánh giá. Vì vậy, tôi đang thêm phiên bản 2 phiên bản đã được làm sạch (và nhận xét) cho câu hỏi. Mình sẽ để nguyên bản ở phía dưới để tiện so sánh:

#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

Phiên bản gốc là bên dưới:

#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

Trả lời

3 G.Sliepen Aug 25 2020 at 03:14

Tránh lặp lại chính mình

Tôi thấy một vài trường hợp mà bạn có thể tránh lặp lại tên loại. Ví dụ:

I iter = I{};

Điều này có thể được viết là:

I iter{};

Và:

Base64DecodeIterator operator++(int) {Base64DecodeIterator result(this); ++(*this); return result;}

Có thể được viết như:

Base64DecodeIterator operator++(int) {auto result{*this}; ++(*this); return result;}

Tránh viết nhiều câu lệnh trên một dòng

Vì thông lệ trong C và C ++ là viết một câu lệnh trên mỗi dòng, khi bạn kết hợp nhiều câu lệnh trên một dòng, đặc biệt là không có khoảng trắng giữa các câu lệnh, nó có thể gây nhầm lẫn. Chỉ cần chia các dòng chữ một trong nhiều câu lệnh thành nhiều dòng, như:

Base64DecodeIterator operator++(int) {
    auto result{*this};
    ++(*this);
    return result;
}

Cân nhắc hỗ trợ các loại đầu vào và đầu ra khác nhau

Hãy xem xét một tình huống trong đó bạn có một khối dữ liệu nhị phân, mà bạn có char *hoặc uint8_t *nhưng bạn cần chuỗi được mã hóa base64 để sử dụng wchar_t. Bạn có thể hỗ trợ điều này tương đối dễ dàng bằng cách thêm một tham số mẫu khác để mô tả loại đầu ra, như sau:

template<typename I, typename CharT = char>
class Base64EncodeIterator
{
     ...
     using value_type = CharT;
     using pointer = CharT*;
     using reference = CharT&;
     ...
     CharT operator*()
     {
         ...
     }
};

Bạn sẽ thực hiện thay đổi tương tự cho Base64DecodeIterator. Các make_*chức năng có thể trông giống như:

template<typename CharT = char, typename I>
Base64DecodeIterator<I, CharT> make_encode64(I iter)
{
    return Base64EncodeIterator<I, CharT>(iter);
}

Sau đó, bạn có thể sử dụng nó như vậy:

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)));

Hãy xem xét I::value_typekhông phải là một loại số nguyên 8 bit trong quá trình mã hóa

Mã của bạn sẽ chấp nhận những điều sau:

std::vector<float> data{1.1, 42, 9.9e99};
make_encode64(data.begin());

Nhưng điều này sẽ làm là chuyển từng phần tử của vector thành một unsigned chartrước khi mã hóa nó. Đó không phải là những gì bạn mong đợi. Sử dụng SFINAE hoặc Khái niệm để giới hạn các trình vòng lặp được phép đối với những trình vòng lặp có value_typekiểu số nguyên 8 bit.

Khi mã hóa, bạn gặp vấn đề tương tự nếu bạn cho phép loại đầu ra được chỉ định như đã đề cập ở điểm trước.

Làm cho nó hoạt động với phạm vi

Vấn đề là các lớp của bạn không triển khai a std::ranges::range. Vì vậy, bạn sẽ cần phải giới thiệu một số lớp cung cấp cả trình lặp bắt đầu và kết thúc. Nhưng điều đó có thể đơn giản như:

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;
    }
};

Và sau đó bạn có thể viết:

std::string input = "SGVsbG8sIHdvcmxkIQo=";
Base64Decoder decoder(input);
for (auto c: input | std::ranges::views::take(5))
    std::cout << c;
std::cout << '\n';