LCOV - code coverage report
Current view: top level - spb/json - base64.h (source / functions) Coverage Total Hit
Test: coverage.info Lines: 97.9 % 97 95
Test Date: 2026-07-19 10:31:31 Functions: 100.0 % 12 12

            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         5502 : void base64_encode(auto &output, std::span<const std::byte> input)
      22              : {
      23              :     static constexpr char encode_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
      24              : 
      25         5502 :     const auto *p_char = reinterpret_cast<const uint8_t *>(input.data());
      26              :     //
      27              :     //- +3 means 3 bytes are being processed in one iteration (3 * 8 = 24 bits)
      28              :     //
      29      2189432 :     for (size_t idx = 3; idx <= input.size(); idx += 3)
      30              :     {
      31      2183930 :         auto temp = uint32_t(*p_char++) << 16U;
      32      2183930 :         temp += uint32_t(*p_char++) << 8U;
      33      2183930 :         temp += (*p_char++);
      34      2183930 :         output.write(encode_table[(temp & 0x00FC0000U) >> 18U]);
      35      2183930 :         output.write(encode_table[(temp & 0x0003F000U) >> 12U]);
      36      2183930 :         output.write(encode_table[(temp & 0x00000FC0U) >> 6U]);
      37      2183930 :         output.write(encode_table[(temp & 0x0000003FU)]);
      38              :     }
      39         5502 :     switch (input.size() % 3)
      40              :     {
      41         1928 :     case 1:
      42              :     {
      43         1928 :         auto temp = uint32_t(*p_char++) << 16U;
      44         1928 :         output.write(encode_table[(temp & 0x00FC0000U) >> 18U]);
      45         1928 :         output.write(encode_table[(temp & 0x0003F000U) >> 12U]);
      46         1928 :         output.write('=');
      47         1928 :         output.write('=');
      48              :     }
      49         1928 :     break;
      50         1832 :     case 2:
      51              :     {
      52         1832 :         auto temp = uint32_t(*p_char++) << 16U;
      53         1832 :         temp += uint32_t(*p_char++) << 8U;
      54         1832 :         output.write(encode_table[(temp & 0x00FC0000) >> 18]);
      55         1832 :         output.write(encode_table[(temp & 0x0003F000) >> 12]);
      56         1832 :         output.write(encode_table[(temp & 0x00000FC0) >> 6]);
      57         1832 :         output.write('=');
      58              :     }
      59         1832 :     break;
      60              :     }
      61         5502 : }
      62              : 
      63              : template <typename istream>
      64         2756 : void base64_decode_string(spb::detail::proto_field_bytes auto &output, istream &stream,
      65              :                           size_t max_output_size = 0)
      66              : {
      67              :     static constexpr uint8_t decode_table[256] = {
      68              :         128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
      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, 62,  128, 128, 128, 63,  52,  53,  54,  55,  56,  57,  58,  59,  60,  61,  128, 128,
      71              :         128, 128, 128, 128, 128, 0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   10,  11,  12,  13,  14,
      72              :         15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  128, 128, 128, 128, 128, 128, 26,  27,  28,
      73              :         29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  48,
      74              :         49,  50,  51,  128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
      75              :         128, 128, 128, 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};
      81              : 
      82              :     /*static constexpr uint8_t decode_table2[] = {
      83              :         62, 128, 128, 128, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 128, 128, 128, 128, 128, 128,
      84              :         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,
      85              :     24, 25, 128, 128, 128, 128, 128, 128, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
      86              :     40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
      87              :     };*/
      88              : 
      89              :     if constexpr (spb::detail::proto_field_bytes_resizable<decltype(output)>)
      90         2732 :         output.clear();
      91              : 
      92         2756 :     if (!stream.consume('"')) [[unlikely]]
      93            9 :         throw std::runtime_error("expecting '\"'");
      94              : 
      95         2747 :     if (stream.consume_and_skip_white_space('"'))
      96            5 :         return;
      97              : 
      98         2742 :     auto mask = uint8_t(0);
      99              : 
     100         2742 :     for (auto out_index = size_t(0);;)
     101              :     {
     102         2742 :         auto view      = stream.view(1, UINT32_MAX);
     103         2741 :         auto length    = view.find('"');
     104         2741 :         auto end_found = length < view.npos;
     105         2741 :         if ((end_found && length % 4 != 0) || view.size() <= 4) [[unlikely]]
     106            7 :             throw std::runtime_error("invalid base64");
     107              : 
     108         2734 :         length = std::min(length, view.size());
     109              : 
     110              :         //- align to 4 bytes
     111         2734 :         auto aligned_length = length & ~3;
     112         2734 :         if (aligned_length > 4) [[likely]]
     113              :         {
     114         2658 :             auto out_length = ((aligned_length - 4) / 4) * 3;
     115         2658 :             view            = view.substr(0, aligned_length);
     116              : 
     117              :             if constexpr (spb::detail::proto_field_bytes_resizable<decltype(output)>)
     118              :             {
     119         2634 :                 if (max_output_size && (output.size() + out_length > max_output_size)) [[unlikely]]
     120            0 :                     throw std::length_error("bytes is too large");
     121              : 
     122         2634 :                 output.resize(output.size() + out_length);
     123              :             }
     124              :             else
     125              :             {
     126           24 :                 if (out_length > (output.size() - out_index)) [[unlikely]]
     127            0 :                     throw std::runtime_error("too large base64");
     128              :             }
     129              : 
     130         2658 :             auto *p_out       = output.data() + out_index;
     131         2658 :             const auto *p_in  = reinterpret_cast<const uint8_t *>(view.data());
     132         2658 :             const auto *p_end = p_in + aligned_length - 4; //- exclude the last 4 chars (possible padding)
     133              : 
     134      1094616 :             while (p_in < p_end) [[likely]]
     135              :             {
     136      1091958 :                 uint8_t v0 = decode_table[*p_in++];
     137      1091958 :                 uint8_t v1 = decode_table[*p_in++];
     138      1091958 :                 uint8_t v2 = decode_table[*p_in++];
     139      1091958 :                 uint8_t v3 = decode_table[*p_in++];
     140      1091958 :                 mask |= (v0 | v1 | v2 | v3);
     141              : 
     142      1091958 :                 *p_out++ = std::byte((v0 << 2) | (v1 >> 4));
     143      1091958 :                 *p_out++ = std::byte((v1 << 4) | (v2 >> 2));
     144      1091958 :                 *p_out++ = std::byte((v2 << 6) | (v3));
     145              : 
     146      1091958 :                 out_index += 3;
     147              :             }
     148         2658 :             auto consumed_bytes = p_in - reinterpret_cast<const uint8_t *>(view.data());
     149         2658 :             view.remove_prefix(consumed_bytes);
     150         2658 :             stream.skip(consumed_bytes);
     151              :         }
     152              : 
     153         2734 :         if (end_found)
     154              :         {
     155              :             //- handle padding
     156         2734 :             const auto *p_in = reinterpret_cast<const uint8_t *>(view.data());
     157              : 
     158         2734 :             uint8_t v0 = decode_table[*p_in++];
     159         2734 :             uint8_t v1 = decode_table[*p_in++];
     160         2734 :             auto i1    = *p_in++;
     161         2734 :             uint8_t v2 = i1 == '=' ? 0 : decode_table[i1];
     162         2734 :             auto i2    = *p_in++;
     163         2734 :             uint8_t v3 = i2 == '=' ? 0 : decode_table[i2];
     164         2734 :             mask |= (v0 | v1 | v2 | v3);
     165         2734 :             mask |= ((i1 == '=') & (i2 != '=')) ? 128 : 0;
     166         2734 :             if (mask & 128) [[unlikely]]
     167            4 :                 throw std::runtime_error("invalid base64");
     168              : 
     169         2730 :             auto padding_size   = (i1 == '=' ? 1 : 0) + (i2 == '=' ? 1 : 0);
     170         2730 :             auto consumed_bytes = 3 - padding_size;
     171              :             //- +1 is for "
     172         2730 :             stream.skip(5);
     173              :             if constexpr (spb::detail::proto_field_bytes_resizable<decltype(output)>)
     174              :             {
     175         2706 :                 if (max_output_size && (output.size() + consumed_bytes > max_output_size))
     176            8 :                     throw std::length_error("bytes is too large");
     177              : 
     178         2698 :                 output.resize(output.size() + consumed_bytes);
     179              :             }
     180              :             else
     181              :             {
     182           24 :                 if (output.size() != out_index + consumed_bytes) [[unlikely]]
     183            3 :                     throw std::runtime_error("too large base64");
     184              :             }
     185         2719 :             auto *p_out = output.data() + out_index;
     186         2719 :             if (padding_size == 0)
     187              :             {
     188          863 :                 *p_out++ = std::byte((v0 << 2) | (v1 >> 4));
     189          863 :                 *p_out++ = std::byte((v1 << 4) | (v2 >> 2));
     190          863 :                 *p_out++ = std::byte((v2 << 6) | (v3));
     191              :             }
     192         1856 :             else if (padding_size == 1)
     193              :             {
     194          906 :                 *p_out++ = std::byte((v0 << 2) | (v1 >> 4));
     195          906 :                 *p_out++ = std::byte((v1 << 4) | (v2 >> 2));
     196              :             }
     197          950 :             else if (padding_size == 2)
     198              :             {
     199          950 :                 *p_out++ = std::byte((v0 << 2) | (v1 >> 4));
     200              :             }
     201         2719 :             return;
     202              :         }
     203              :     }
     204              : }
     205              : } // namespace spb::json::detail
        

Generated by: LCOV version 2.0-1