LCOV - code coverage report
Current view: top level - spb/json - base64.h (source / functions) Coverage Total Hit
Test: coverage.info Lines: 98.0 % 98 96
Test Date: 2026-05-30 19:41:37 Functions: 100.0 % 8 8

            Line data    Source code
       1              : /***************************************************************************\
       2              : * Name        : base64 library for json                                     *
       3              : * Description : RFC 4648 base64 decoder and encoder                         *
       4              : * Author      : antonin.kriz@gmail.com                                      *
       5              : * ------------------------------------------------------------------------- *
       6              : * This is free software; you can redistribute it and/or modify it under the *
       7              : * terms of the MIT license. A copy of the license can be found in the file  *
       8              : * "LICENSE" at the root of this distribution.                               *
       9              : \***************************************************************************/
      10              : 
      11              : #pragma once
      12              : 
      13              : #include "../concepts.h"
      14              : #include <cstddef>
      15              : #include <cstdint>
      16              : #include <span>
      17              : #include <stdexcept>
      18              : 
      19              : namespace spb::json::detail
      20              : {
      21              : template <typename ostream>
      22         5423 : static inline void base64_encode(ostream &output, std::span<const std::byte> input)
      23              : {
      24              :     static constexpr char encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
      25              : 
      26         5423 :     const auto *p_char = reinterpret_cast<const uint8_t *>(input.data());
      27              :     //
      28              :     //- +3 means 3 bytes are being processed in one iteration (3 * 8 = 24 bits)
      29              :     //
      30      2189314 :     for (size_t idx = 3; idx <= input.size(); idx += 3)
      31              :     {
      32      2183891 :         auto temp = uint32_t(*p_char++) << 16U;
      33      2183891 :         temp += uint32_t(*p_char++) << 8U;
      34      2183891 :         temp += (*p_char++);
      35      2183891 :         output.write(encode_table[(temp & 0x00FC0000U) >> 18U]);
      36      2183891 :         output.write(encode_table[(temp & 0x0003F000U) >> 12U]);
      37      2183891 :         output.write(encode_table[(temp & 0x00000FC0U) >> 6U]);
      38      2183891 :         output.write(encode_table[(temp & 0x0000003FU)]);
      39              :     }
      40         5423 :     switch (input.size() % 3)
      41              :     {
      42         1884 :     case 1:
      43              :     {
      44         1884 :         auto temp = uint32_t(*p_char++) << 16U;
      45         1884 :         output.write(encode_table[(temp & 0x00FC0000U) >> 18U]);
      46         1884 :         output.write(encode_table[(temp & 0x0003F000U) >> 12U]);
      47         1884 :         output.write('=');
      48         1884 :         output.write('=');
      49              :     }
      50         1884 :     break;
      51         1810 :     case 2:
      52              :     {
      53         1810 :         auto temp = uint32_t(*p_char++) << 16U;
      54         1810 :         temp += uint32_t(*p_char++) << 8U;
      55         1810 :         output.write(encode_table[(temp & 0x00FC0000) >> 18]);
      56         1810 :         output.write(encode_table[(temp & 0x0003F000) >> 12]);
      57         1810 :         output.write(encode_table[(temp & 0x00000FC0) >> 6]);
      58         1810 :         output.write('=');
      59              :     }
      60         1810 :     break;
      61              :     }
      62         5423 : }
      63              : 
      64              : template <typename istream>
      65         2715 : static inline void base64_decode_string(spb::detail::proto_field_bytes auto &output, istream &stream,
      66              :                                         size_t max_output_size = 0)
      67              : {
      68              :     static constexpr uint8_t decode_table[256] = {
      69              :         128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
      70              :         128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
      71              :         128, 128, 128, 62,  128, 128, 128, 63,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  128, 128,
      72              :         128, 128, 128, 128, 128, 0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,
      73              :         15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  128, 128, 128, 128, 128, 128, 26,  27,  28,
      74              :         29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  48,
      75              :         49,  50,  51,  128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
      76              :         128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
      77              :         128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
      78              :         128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
      79              :         128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
      80              :         128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
      81              :         128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128};
      82              : 
      83              :     /*static constexpr uint8_t decode_table2[] = {
      84              :         62, 128, 128, 128, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 128, 128, 128, 128, 128, 128,
      85              :         128, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
      86              :     24, 25, 128, 128, 128, 128, 128, 128, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
      87              :     40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
      88              :     };*/
      89              : 
      90              :     if constexpr (spb::detail::proto_field_bytes_resizable<decltype(output)>)
      91         2697 :         output.clear();
      92              : 
      93         2715 :     if (stream.current_char() != '"') [[unlikely]]
      94            9 :         throw std::runtime_error("expecting '\"'");
      95              : 
      96         2706 :     stream.consume_current_char(false);
      97         2706 :     if (stream.consume('"'))
      98            5 :         return;
      99              : 
     100         2701 :     auto mask = uint8_t(0);
     101              : 
     102        18795 :     for (auto out_index = size_t(0);;)
     103              :     {
     104        18795 :         auto view = stream.view(UINT32_MAX);
     105        18794 :         auto length = view.find('"');
     106        18794 :         auto end_found = length < view.npos;
     107        18794 :         if ((end_found && length % 4 != 0) || view.size() <= 4) [[unlikely]]
     108            7 :             throw std::runtime_error("invalid base64");
     109              : 
     110        18787 :         length = std::min(length, view.size());
     111              : 
     112              :         //- align to 4 bytes
     113        18787 :         auto aligned_length = length & ~3;
     114        18787 :         if (aligned_length > 4) [[likely]]
     115              :         {
     116        18691 :             auto out_length = ((aligned_length - 4) / 4) * 3;
     117        18691 :             view = view.substr(0, aligned_length);
     118              : 
     119              :             if constexpr (spb::detail::proto_field_bytes_resizable<decltype(output)>)
     120              :             {
     121        18660 :                 if (max_output_size && (output.size() + out_length > max_output_size)) [[unlikely]]
     122            0 :                     throw std::length_error("bytes is too large");
     123              : 
     124        18660 :                 output.resize(output.size() + out_length);
     125              :             }
     126              :             else
     127              :             {
     128           31 :                 if (out_length > (output.size() - out_index)) [[unlikely]]
     129            0 :                     throw std::runtime_error("too large base64");
     130              :             }
     131              : 
     132        18691 :             auto *p_out = output.data() + out_index;
     133        18691 :             const auto *p_in = reinterpret_cast<const uint8_t *>(view.data());
     134        18691 :             const auto *p_end = p_in + aligned_length - 4; //- exclude the last 4 chars (possible padding)
     135              : 
     136      1110621 :             while (p_in < p_end) [[likely]]
     137              :             {
     138      1091930 :                 uint8_t v0 = decode_table[*p_in++];
     139      1091930 :                 uint8_t v1 = decode_table[*p_in++];
     140      1091930 :                 uint8_t v2 = decode_table[*p_in++];
     141      1091930 :                 uint8_t v3 = decode_table[*p_in++];
     142      1091930 :                 mask |= (v0 | v1 | v2 | v3);
     143              : 
     144      1091930 :                 *p_out++ = std::byte((v0 << 2) | (v1 >> 4));
     145      1091930 :                 *p_out++ = std::byte((v1 << 4) | (v2 >> 2));
     146      1091930 :                 *p_out++ = std::byte((v2 << 6) | (v3));
     147              : 
     148      1091930 :                 out_index += 3;
     149              :             }
     150        18691 :             auto consumed_bytes = p_in - reinterpret_cast<const uint8_t *>(view.data());
     151        18691 :             view.remove_prefix(consumed_bytes);
     152        18691 :             stream.skip(consumed_bytes);
     153              :         }
     154              : 
     155        18787 :         if (end_found)
     156              :         {
     157              :             //- handle padding
     158         2693 :             const auto *p_in = reinterpret_cast<const uint8_t *>(view.data());
     159              : 
     160         2693 :             uint8_t v0 = decode_table[*p_in++];
     161         2693 :             uint8_t v1 = decode_table[*p_in++];
     162         2693 :             auto i1 = *p_in++;
     163         2693 :             uint8_t v2 = i1 == '=' ? 0 : decode_table[i1];
     164         2693 :             auto i2 = *p_in++;
     165         2693 :             uint8_t v3 = i2 == '=' ? 0 : decode_table[i2];
     166         2693 :             mask |= (v0 | v1 | v2 | v3);
     167         2693 :             mask |= ((i1 == '=') & (i2 != '=')) ? 128 : 0;
     168         2693 :             if (mask & 128) [[unlikely]]
     169            4 :                 throw std::runtime_error("invalid base64");
     170              : 
     171         2689 :             auto padding_size = (i1 == '=' ? 1 : 0) + (i2 == '=' ? 1 : 0);
     172         2689 :             auto consumed_bytes = 3 - padding_size;
     173              :             //- +1 is for "
     174         2689 :             stream.skip(5);
     175              :             if constexpr (spb::detail::proto_field_bytes_resizable<decltype(output)>)
     176              :             {
     177         2671 :                 if (max_output_size && (output.size() + consumed_bytes > max_output_size))
     178            8 :                     throw std::length_error("bytes is too large");
     179              : 
     180         2663 :                 output.resize(output.size() + consumed_bytes);
     181              :             }
     182              :             else
     183              :             {
     184           18 :                 if (output.size() != out_index + consumed_bytes) [[unlikely]]
     185            3 :                     throw std::runtime_error("too large base64");
     186              :             }
     187         2678 :             auto *p_out = output.data() + out_index;
     188         2678 :             if (padding_size == 0)
     189              :             {
     190          860 :                 *p_out++ = std::byte((v0 << 2) | (v1 >> 4));
     191          860 :                 *p_out++ = std::byte((v1 << 4) | (v2 >> 2));
     192          860 :                 *p_out++ = std::byte((v2 << 6) | (v3));
     193              :             }
     194         1818 :             else if (padding_size == 1)
     195              :             {
     196          892 :                 *p_out++ = std::byte((v0 << 2) | (v1 >> 4));
     197          892 :                 *p_out++ = std::byte((v1 << 4) | (v2 >> 2));
     198              :             }
     199          926 :             else if (padding_size == 2)
     200              :             {
     201          926 :                 *p_out++ = std::byte((v0 << 2) | (v1 >> 4));
     202              :             }
     203         2678 :             return;
     204              :         }
     205              :     }
     206              : }
     207              : } // namespace spb::json::detail
        

Generated by: LCOV version 2.0-1