/src/simdutf/fuzz/base64.cpp
Line | Count | Source |
1 | | #include <cstddef> |
2 | | #include <cstdint> |
3 | | #include <array> |
4 | | #include <cstdlib> |
5 | | |
6 | | #include "helpers/common.h" |
7 | | #include "simdutf.h" |
8 | | |
9 | | constexpr std::array options = { |
10 | | simdutf::base64_default, |
11 | | simdutf::base64_url, |
12 | | simdutf::base64_default_no_padding, |
13 | | simdutf::base64_url_with_padding, |
14 | | }; |
15 | | |
16 | | constexpr std::array last_chunk = { |
17 | | simdutf::last_chunk_handling_options::loose, |
18 | | simdutf::last_chunk_handling_options::strict, |
19 | | simdutf::last_chunk_handling_options::stop_before_partial}; |
20 | | |
21 | | struct decoderesult { |
22 | | std::size_t maxbinarylength{}; |
23 | | simdutf::result convertresult{}; |
24 | | auto operator<=>(const decoderesult&) const = default; |
25 | | }; |
26 | | |
27 | | template <typename FromChar> |
28 | | void decode(std::span<const FromChar> base64_, const auto selected_option, |
29 | 2.25k | const auto last_chunk_option) { |
30 | 2.25k | std::vector<FromChar> base64(begin(base64_), end(base64_)); |
31 | 2.25k | const auto implementations = get_supported_implementations(); |
32 | 2.25k | std::vector<decoderesult> results; |
33 | 2.25k | results.reserve(implementations.size()); |
34 | 6.76k | for (auto impl : implementations) { |
35 | 6.76k | auto& r = results.emplace_back(); |
36 | 6.76k | r.maxbinarylength = |
37 | 6.76k | impl->maximal_binary_length_from_base64(base64.data(), base64.size()); |
38 | | // binary_length_from_base64 is not compared across implementations: for |
39 | | // invalid input the implementations may legitimately return different |
40 | | // estimates, so keep it as a local rather than a field of decoderesult. |
41 | 6.76k | const std::size_t binarylength = |
42 | 6.76k | impl->binary_length_from_base64(base64.data(), base64.size()); |
43 | | // binary_length_from_base64 must never exceed the maximal upper bound. |
44 | 6.76k | if (binarylength > r.maxbinarylength) { |
45 | 0 | std::cerr << "binary_length_from_base64 (" << binarylength |
46 | 0 | << ") > maximal_binary_length_from_base64 (" |
47 | 0 | << r.maxbinarylength << ") for impl " << impl->name() << "\n"; |
48 | 0 | std::abort(); |
49 | 0 | } |
50 | 6.76k | std::vector<char> output(r.maxbinarylength); |
51 | 6.76k | r.convertresult = |
52 | 6.76k | impl->base64_to_binary(base64.data(), base64.size(), output.data(), |
53 | 6.76k | selected_option, last_chunk_option); |
54 | | // binary_length_from_base64 takes no last_chunk_handling option, so its |
55 | | // estimate only equals the decoded size under loose handling. Under strict |
56 | | // and stop_before_partial a partial final chunk is dropped, so a SUCCESS |
57 | | // can legitimately write fewer bytes than the estimate (e.g. "QQQ" with |
58 | | // stop_before_partial reports 2 but decodes 0). Only assert equality for |
59 | | // loose to avoid false positives. |
60 | 6.76k | if (last_chunk_option == simdutf::last_chunk_handling_options::loose && |
61 | 3.03k | r.convertresult.error == simdutf::error_code::SUCCESS && |
62 | 1.21k | binarylength != r.convertresult.count) { |
63 | 0 | std::cerr << "binary_length_from_base64 (" << binarylength |
64 | 0 | << ") != decoded count (" << r.convertresult.count |
65 | 0 | << ") for impl " << impl->name() << "\n"; |
66 | 0 | std::abort(); |
67 | 0 | } |
68 | 6.76k | } |
69 | 4.51k | auto neq = [](const auto& a, const auto& b) { return a != b; };auto decode<char, simdutf::base64_options, simdutf::last_chunk_handling_options>(std::__1::span<char const, 18446744073709551615ul>, simdutf::base64_options, simdutf::last_chunk_handling_options)::{lambda(auto:1 const&, auto:2 const&)#1}::operator()<decoderesult, {lambda(auto:1 const&, auto:2 const&)#1}::operator()>(decoderesult const&, {lambda(auto:1 const&, auto:2 const&)#1}::operator() const&) constLine | Count | Source | 69 | 2.03k | auto neq = [](const auto& a, const auto& b) { return a != b; }; |
auto decode<char16_t, simdutf::base64_options, simdutf::last_chunk_handling_options>(std::__1::span<char16_t const, 18446744073709551615ul>, simdutf::base64_options, simdutf::last_chunk_handling_options)::{lambda(auto:1 const&, auto:2 const&)#1}::operator()<decoderesult, {lambda(auto:1 const&, auto:2 const&)#1}::operator()>(decoderesult const&, {lambda(auto:1 const&, auto:2 const&)#1}::operator() const&) constLine | Count | Source | 69 | 2.47k | auto neq = [](const auto& a, const auto& b) { return a != b; }; |
|
70 | 2.25k | if (std::ranges::adjacent_find(results, neq) != results.end()) { |
71 | 0 | std::cerr << "output differs between implementations for decode\n"; |
72 | 0 | const auto implementations = get_supported_implementations(); |
73 | 0 | std::size_t i = 0; |
74 | 0 | for (const auto& r : results) { |
75 | 0 | std::cerr << "impl " << implementations[i]->name() |
76 | 0 | << " got maxbinarylength=" << r.maxbinarylength |
77 | 0 | << " convertresult=" << r.convertresult << "\n"; |
78 | 0 | ++i; |
79 | 0 | } |
80 | 0 | std::cerr << "option: " << selected_option << '\n'; |
81 | 0 | std::cerr << "data: " |
82 | 0 | << (std::is_same_v<FromChar, char> ? "char" : "char16_t") << "{"; |
83 | 0 | for (int v : base64) { |
84 | 0 | std::cerr << v << ", "; |
85 | 0 | } |
86 | 0 | std::cerr << "}\n"; |
87 | 0 | std::abort(); |
88 | 0 | } |
89 | 2.25k | } void decode<char, simdutf::base64_options, simdutf::last_chunk_handling_options>(std::__1::span<char const, 18446744073709551615ul>, simdutf::base64_options, simdutf::last_chunk_handling_options) Line | Count | Source | 29 | 1.01k | const auto last_chunk_option) { | 30 | 1.01k | std::vector<FromChar> base64(begin(base64_), end(base64_)); | 31 | 1.01k | const auto implementations = get_supported_implementations(); | 32 | 1.01k | std::vector<decoderesult> results; | 33 | 1.01k | results.reserve(implementations.size()); | 34 | 3.05k | for (auto impl : implementations) { | 35 | 3.05k | auto& r = results.emplace_back(); | 36 | 3.05k | r.maxbinarylength = | 37 | 3.05k | impl->maximal_binary_length_from_base64(base64.data(), base64.size()); | 38 | | // binary_length_from_base64 is not compared across implementations: for | 39 | | // invalid input the implementations may legitimately return different | 40 | | // estimates, so keep it as a local rather than a field of decoderesult. | 41 | 3.05k | const std::size_t binarylength = | 42 | 3.05k | impl->binary_length_from_base64(base64.data(), base64.size()); | 43 | | // binary_length_from_base64 must never exceed the maximal upper bound. | 44 | 3.05k | if (binarylength > r.maxbinarylength) { | 45 | 0 | std::cerr << "binary_length_from_base64 (" << binarylength | 46 | 0 | << ") > maximal_binary_length_from_base64 (" | 47 | 0 | << r.maxbinarylength << ") for impl " << impl->name() << "\n"; | 48 | 0 | std::abort(); | 49 | 0 | } | 50 | 3.05k | std::vector<char> output(r.maxbinarylength); | 51 | 3.05k | r.convertresult = | 52 | 3.05k | impl->base64_to_binary(base64.data(), base64.size(), output.data(), | 53 | 3.05k | selected_option, last_chunk_option); | 54 | | // binary_length_from_base64 takes no last_chunk_handling option, so its | 55 | | // estimate only equals the decoded size under loose handling. Under strict | 56 | | // and stop_before_partial a partial final chunk is dropped, so a SUCCESS | 57 | | // can legitimately write fewer bytes than the estimate (e.g. "QQQ" with | 58 | | // stop_before_partial reports 2 but decodes 0). Only assert equality for | 59 | | // loose to avoid false positives. | 60 | 3.05k | if (last_chunk_option == simdutf::last_chunk_handling_options::loose && | 61 | 1.40k | r.convertresult.error == simdutf::error_code::SUCCESS && | 62 | 654 | binarylength != r.convertresult.count) { | 63 | 0 | std::cerr << "binary_length_from_base64 (" << binarylength | 64 | 0 | << ") != decoded count (" << r.convertresult.count | 65 | 0 | << ") for impl " << impl->name() << "\n"; | 66 | 0 | std::abort(); | 67 | 0 | } | 68 | 3.05k | } | 69 | 1.01k | auto neq = [](const auto& a, const auto& b) { return a != b; }; | 70 | 1.01k | if (std::ranges::adjacent_find(results, neq) != results.end()) { | 71 | 0 | std::cerr << "output differs between implementations for decode\n"; | 72 | 0 | const auto implementations = get_supported_implementations(); | 73 | 0 | std::size_t i = 0; | 74 | 0 | for (const auto& r : results) { | 75 | 0 | std::cerr << "impl " << implementations[i]->name() | 76 | 0 | << " got maxbinarylength=" << r.maxbinarylength | 77 | 0 | << " convertresult=" << r.convertresult << "\n"; | 78 | 0 | ++i; | 79 | 0 | } | 80 | 0 | std::cerr << "option: " << selected_option << '\n'; | 81 | 0 | std::cerr << "data: " | 82 | 0 | << (std::is_same_v<FromChar, char> ? "char" : "char16_t") << "{"; | 83 | 0 | for (int v : base64) { | 84 | 0 | std::cerr << v << ", "; | 85 | 0 | } | 86 | 0 | std::cerr << "}\n"; | 87 | 0 | std::abort(); | 88 | 0 | } | 89 | 1.01k | } |
void decode<char16_t, simdutf::base64_options, simdutf::last_chunk_handling_options>(std::__1::span<char16_t const, 18446744073709551615ul>, simdutf::base64_options, simdutf::last_chunk_handling_options) Line | Count | Source | 29 | 1.23k | const auto last_chunk_option) { | 30 | 1.23k | std::vector<FromChar> base64(begin(base64_), end(base64_)); | 31 | 1.23k | const auto implementations = get_supported_implementations(); | 32 | 1.23k | std::vector<decoderesult> results; | 33 | 1.23k | results.reserve(implementations.size()); | 34 | 3.71k | for (auto impl : implementations) { | 35 | 3.71k | auto& r = results.emplace_back(); | 36 | 3.71k | r.maxbinarylength = | 37 | 3.71k | impl->maximal_binary_length_from_base64(base64.data(), base64.size()); | 38 | | // binary_length_from_base64 is not compared across implementations: for | 39 | | // invalid input the implementations may legitimately return different | 40 | | // estimates, so keep it as a local rather than a field of decoderesult. | 41 | 3.71k | const std::size_t binarylength = | 42 | 3.71k | impl->binary_length_from_base64(base64.data(), base64.size()); | 43 | | // binary_length_from_base64 must never exceed the maximal upper bound. | 44 | 3.71k | if (binarylength > r.maxbinarylength) { | 45 | 0 | std::cerr << "binary_length_from_base64 (" << binarylength | 46 | 0 | << ") > maximal_binary_length_from_base64 (" | 47 | 0 | << r.maxbinarylength << ") for impl " << impl->name() << "\n"; | 48 | 0 | std::abort(); | 49 | 0 | } | 50 | 3.71k | std::vector<char> output(r.maxbinarylength); | 51 | 3.71k | r.convertresult = | 52 | 3.71k | impl->base64_to_binary(base64.data(), base64.size(), output.data(), | 53 | 3.71k | selected_option, last_chunk_option); | 54 | | // binary_length_from_base64 takes no last_chunk_handling option, so its | 55 | | // estimate only equals the decoded size under loose handling. Under strict | 56 | | // and stop_before_partial a partial final chunk is dropped, so a SUCCESS | 57 | | // can legitimately write fewer bytes than the estimate (e.g. "QQQ" with | 58 | | // stop_before_partial reports 2 but decodes 0). Only assert equality for | 59 | | // loose to avoid false positives. | 60 | 3.71k | if (last_chunk_option == simdutf::last_chunk_handling_options::loose && | 61 | 1.62k | r.convertresult.error == simdutf::error_code::SUCCESS && | 62 | 561 | binarylength != r.convertresult.count) { | 63 | 0 | std::cerr << "binary_length_from_base64 (" << binarylength | 64 | 0 | << ") != decoded count (" << r.convertresult.count | 65 | 0 | << ") for impl " << impl->name() << "\n"; | 66 | 0 | std::abort(); | 67 | 0 | } | 68 | 3.71k | } | 69 | 1.23k | auto neq = [](const auto& a, const auto& b) { return a != b; }; | 70 | 1.23k | if (std::ranges::adjacent_find(results, neq) != results.end()) { | 71 | 0 | std::cerr << "output differs between implementations for decode\n"; | 72 | 0 | const auto implementations = get_supported_implementations(); | 73 | 0 | std::size_t i = 0; | 74 | 0 | for (const auto& r : results) { | 75 | 0 | std::cerr << "impl " << implementations[i]->name() | 76 | 0 | << " got maxbinarylength=" << r.maxbinarylength | 77 | 0 | << " convertresult=" << r.convertresult << "\n"; | 78 | 0 | ++i; | 79 | 0 | } | 80 | 0 | std::cerr << "option: " << selected_option << '\n'; | 81 | 0 | std::cerr << "data: " | 82 | 0 | << (std::is_same_v<FromChar, char> ? "char" : "char16_t") << "{"; | 83 | 0 | for (int v : base64) { | 84 | 0 | std::cerr << v << ", "; | 85 | 0 | } | 86 | 0 | std::cerr << "}\n"; | 87 | 0 | std::abort(); | 88 | 0 | } | 89 | 1.23k | } |
|
90 | | |
91 | | template <typename FromChar> |
92 | | void decode_safe(std::span<const FromChar> base64_, const auto selected_option, |
93 | | const std::size_t decode_buf_size, |
94 | 913 | const auto last_chunk_option) { |
95 | 913 | std::vector<FromChar> base64(begin(base64_), end(base64_)); |
96 | 913 | std::vector<char> output(decode_buf_size); |
97 | 913 | std::size_t outlen = decode_buf_size; |
98 | 913 | const auto convertresult = simdutf::base64_to_binary_safe( |
99 | 913 | base64.data(), base64.size(), output.data(), outlen, selected_option, |
100 | 913 | last_chunk_option); |
101 | | |
102 | | // the number of written bytes must always be less than the supplied buffer |
103 | 913 | assert(outlen <= decode_buf_size); |
104 | | |
105 | 913 | switch (convertresult.error) { |
106 | 111 | case simdutf::error_code::OUTPUT_BUFFER_TOO_SMALL: { |
107 | 111 | if (!(convertresult.count <= base64.size())) { |
108 | 0 | std::cerr << " decode_buf_size=" << decode_buf_size |
109 | 0 | << " outlen=" << outlen << " and result=" << convertresult |
110 | 0 | << '\n'; |
111 | 0 | std::abort(); |
112 | 0 | } |
113 | 111 | } break; |
114 | 269 | case simdutf::error_code::INVALID_BASE64_CHARACTER: { |
115 | 269 | assert(convertresult.count < base64.size()); |
116 | 269 | } break; |
117 | 269 | case simdutf::error_code::BASE64_INPUT_REMAINDER: { |
118 | 51 | if (!(convertresult.count <= base64.size())) { |
119 | 0 | std::cerr << "on input with size=" << base64.size() |
120 | 0 | << ": got BASE64_INPUT_REMAINDER decode_buf_size=" |
121 | 0 | << decode_buf_size << " outlen=" << outlen |
122 | 0 | << " and result=" << convertresult << '\n'; |
123 | 0 | std::abort(); |
124 | 0 | } |
125 | 51 | } break; |
126 | 465 | case simdutf::error_code::SUCCESS: { |
127 | | // possibility to compare with the normal function |
128 | 465 | } break; |
129 | 17 | default:; |
130 | 913 | } |
131 | 913 | } void decode_safe<char, simdutf::base64_options, simdutf::last_chunk_handling_options>(std::__1::span<char const, 18446744073709551615ul>, simdutf::base64_options, unsigned long, simdutf::last_chunk_handling_options) Line | Count | Source | 94 | 402 | const auto last_chunk_option) { | 95 | 402 | std::vector<FromChar> base64(begin(base64_), end(base64_)); | 96 | 402 | std::vector<char> output(decode_buf_size); | 97 | 402 | std::size_t outlen = decode_buf_size; | 98 | 402 | const auto convertresult = simdutf::base64_to_binary_safe( | 99 | 402 | base64.data(), base64.size(), output.data(), outlen, selected_option, | 100 | 402 | last_chunk_option); | 101 | | | 102 | | // the number of written bytes must always be less than the supplied buffer | 103 | 402 | assert(outlen <= decode_buf_size); | 104 | | | 105 | 402 | switch (convertresult.error) { | 106 | 54 | case simdutf::error_code::OUTPUT_BUFFER_TOO_SMALL: { | 107 | 54 | if (!(convertresult.count <= base64.size())) { | 108 | 0 | std::cerr << " decode_buf_size=" << decode_buf_size | 109 | 0 | << " outlen=" << outlen << " and result=" << convertresult | 110 | 0 | << '\n'; | 111 | 0 | std::abort(); | 112 | 0 | } | 113 | 54 | } break; | 114 | 74 | case simdutf::error_code::INVALID_BASE64_CHARACTER: { | 115 | 74 | assert(convertresult.count < base64.size()); | 116 | 74 | } break; | 117 | 74 | case simdutf::error_code::BASE64_INPUT_REMAINDER: { | 118 | 30 | if (!(convertresult.count <= base64.size())) { | 119 | 0 | std::cerr << "on input with size=" << base64.size() | 120 | 0 | << ": got BASE64_INPUT_REMAINDER decode_buf_size=" | 121 | 0 | << decode_buf_size << " outlen=" << outlen | 122 | 0 | << " and result=" << convertresult << '\n'; | 123 | 0 | std::abort(); | 124 | 0 | } | 125 | 30 | } break; | 126 | 236 | case simdutf::error_code::SUCCESS: { | 127 | | // possibility to compare with the normal function | 128 | 236 | } break; | 129 | 8 | default:; | 130 | 402 | } | 131 | 402 | } |
void decode_safe<char16_t, simdutf::base64_options, simdutf::last_chunk_handling_options>(std::__1::span<char16_t const, 18446744073709551615ul>, simdutf::base64_options, unsigned long, simdutf::last_chunk_handling_options) Line | Count | Source | 94 | 511 | const auto last_chunk_option) { | 95 | 511 | std::vector<FromChar> base64(begin(base64_), end(base64_)); | 96 | 511 | std::vector<char> output(decode_buf_size); | 97 | 511 | std::size_t outlen = decode_buf_size; | 98 | 511 | const auto convertresult = simdutf::base64_to_binary_safe( | 99 | 511 | base64.data(), base64.size(), output.data(), outlen, selected_option, | 100 | 511 | last_chunk_option); | 101 | | | 102 | | // the number of written bytes must always be less than the supplied buffer | 103 | 511 | assert(outlen <= decode_buf_size); | 104 | | | 105 | 511 | switch (convertresult.error) { | 106 | 57 | case simdutf::error_code::OUTPUT_BUFFER_TOO_SMALL: { | 107 | 57 | if (!(convertresult.count <= base64.size())) { | 108 | 0 | std::cerr << " decode_buf_size=" << decode_buf_size | 109 | 0 | << " outlen=" << outlen << " and result=" << convertresult | 110 | 0 | << '\n'; | 111 | 0 | std::abort(); | 112 | 0 | } | 113 | 57 | } break; | 114 | 195 | case simdutf::error_code::INVALID_BASE64_CHARACTER: { | 115 | 195 | assert(convertresult.count < base64.size()); | 116 | 195 | } break; | 117 | 195 | case simdutf::error_code::BASE64_INPUT_REMAINDER: { | 118 | 21 | if (!(convertresult.count <= base64.size())) { | 119 | 0 | std::cerr << "on input with size=" << base64.size() | 120 | 0 | << ": got BASE64_INPUT_REMAINDER decode_buf_size=" | 121 | 0 | << decode_buf_size << " outlen=" << outlen | 122 | 0 | << " and result=" << convertresult << '\n'; | 123 | 0 | std::abort(); | 124 | 0 | } | 125 | 21 | } break; | 126 | 229 | case simdutf::error_code::SUCCESS: { | 127 | | // possibility to compare with the normal function | 128 | 229 | } break; | 129 | 9 | default:; | 130 | 511 | } | 131 | 511 | } |
|
132 | | |
133 | | struct roundtripresult { |
134 | | std::size_t length{}; |
135 | | std::size_t maxbinarylength{}; |
136 | | std::string outputhash; |
137 | | std::size_t written{}; |
138 | | simdutf::result convertbackresult{}; |
139 | | auto operator<=>(const roundtripresult&) const = default; |
140 | | }; |
141 | | |
142 | | /// verifies that base64 with lines is the same as without lines, but with |
143 | | /// newlines every line_length:th byte |
144 | | void verify_lines(std::span<const char> without_lines, |
145 | | std::span<const char> with_lines, |
146 | 3.37k | const std::size_t line_length) { |
147 | | // ensure we get the same as output, with a newline every line_length:th |
148 | | // byte |
149 | 43.9M | for (std::size_t i = 0, j = 0;;) { |
150 | | // check one line |
151 | 293M | for (int count = 0; count < line_length && j < with_lines.size(); ++count) { |
152 | 249M | if (without_lines[i++] != with_lines[j++]) { |
153 | | // unexpected - different content |
154 | 0 | std::abort(); |
155 | 0 | } |
156 | 249M | } |
157 | 43.9M | if (j == with_lines.size()) { |
158 | | // we are at the end of with_lines |
159 | 3.37k | if (i != without_lines.size()) { |
160 | | // unexpected - we are not at the end of without_lines |
161 | 0 | std::abort(); |
162 | 0 | } |
163 | 3.37k | break; |
164 | 3.37k | } |
165 | 43.9M | if (with_lines[j++] != '\n') { |
166 | | // unexpected - not a newline |
167 | 0 | std::abort(); |
168 | 0 | } |
169 | 43.9M | } |
170 | 3.37k | } |
171 | | |
172 | | void roundtrip(std::span<const char> binary, const auto selected_option, |
173 | 1.12k | const auto last_chunk_option, const std::size_t line_length) { |
174 | 1.12k | if (last_chunk_option == |
175 | 1.12k | simdutf::last_chunk_handling_options::stop_before_partial) { |
176 | 1 | return; // this is not a valid option for roundtrip |
177 | 1 | } |
178 | 1.12k | const auto inputhash = FNV1A_hash::as_str(binary); |
179 | 1.12k | const auto implementations = get_supported_implementations(); |
180 | 1.12k | std::vector<roundtripresult> results; |
181 | 1.12k | results.reserve(implementations.size()); |
182 | 3.37k | for (auto impl : implementations) { |
183 | 3.37k | auto& r = results.emplace_back(); |
184 | 3.37k | r.length = impl->base64_length_from_binary(binary.size(), selected_option); |
185 | 3.37k | std::vector<char> output(r.length); |
186 | 3.37k | r.written = impl->binary_to_base64(binary.data(), binary.size(), |
187 | 3.37k | output.data(), selected_option); |
188 | 3.37k | if (r.length != r.written) { |
189 | 0 | std::abort(); |
190 | 0 | } |
191 | | |
192 | | // make sure generating base64 with lines gives the expected result |
193 | 3.37k | const auto length_with_lines = |
194 | 3.37k | simdutf::base64_length_from_binary_with_lines( |
195 | 3.37k | binary.size(), selected_option, line_length); |
196 | 3.37k | assert(length_with_lines >= r.length); |
197 | 3.37k | std::string output_with_lines(length_with_lines, '\0'); |
198 | 3.37k | const auto nwritten_with_lines = impl->binary_to_base64_with_lines( |
199 | 3.37k | binary.data(), binary.size(), output_with_lines.data(), line_length, |
200 | 3.37k | selected_option); |
201 | 3.37k | if (nwritten_with_lines != length_with_lines) { |
202 | 0 | std::cerr << nwritten_with_lines << "!=" << length_with_lines << '\n'; |
203 | 0 | std::abort(); |
204 | 0 | } |
205 | 3.37k | verify_lines(output, output_with_lines, line_length); |
206 | | |
207 | 3.37k | r.outputhash = FNV1A_hash::as_str(output); |
208 | | // convert back to binary |
209 | 3.37k | r.maxbinarylength = |
210 | 3.37k | impl->maximal_binary_length_from_base64(output.data(), output.size()); |
211 | 3.37k | std::vector<char> restored(r.maxbinarylength); |
212 | 3.37k | r.convertbackresult = |
213 | 3.37k | impl->base64_to_binary(output.data(), output.size(), restored.data(), |
214 | 3.37k | selected_option, last_chunk_option); |
215 | 3.37k | if (const auto restoredhash = FNV1A_hash::as_str(restored); |
216 | 3.37k | inputhash != restoredhash) { |
217 | 0 | std::abort(); |
218 | 0 | } |
219 | 3.37k | if (restored.size() != binary.size()) { |
220 | 0 | std::abort(); |
221 | 0 | } |
222 | 3.37k | } |
223 | | |
224 | 2.25k | auto neq = [](const auto& a, const auto& b) { return a != b; }; |
225 | 1.12k | if (std::ranges::adjacent_find(results, neq) != results.end()) { |
226 | 0 | std::cerr << "output differs between implementations\n"; |
227 | 0 | for (const auto& r : results) { |
228 | 0 | std::cout << "written=" << r.written << " maxlength=" << r.maxbinarylength |
229 | 0 | << " length=" << r.length << '\n'; |
230 | 0 | } |
231 | 0 | std::abort(); |
232 | 0 | } |
233 | 1.12k | } |
234 | | |
235 | 4.29k | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { |
236 | | // pick one of the function pointers, based on the fuzz data |
237 | | // the first byte is which action to take. step forward |
238 | | // several bytes so the input is aligned. |
239 | 4.29k | constexpr auto optionbytes = 6u; |
240 | 4.29k | static_assert(optionbytes % 2 == 0, |
241 | 4.29k | "optionbytes must be even to avoid misaligned char16 pointers"); |
242 | | |
243 | 4.29k | if (size < optionbytes) { |
244 | 4 | return 0; |
245 | 4 | } |
246 | 4.29k | constexpr auto Ncases = 5u; |
247 | 4.29k | constexpr auto actionmask = std::bit_ceil(Ncases) - 1; |
248 | 4.29k | const auto action = data[0] & actionmask; |
249 | | |
250 | | // pick a random option |
251 | 4.29k | const auto selected_option = [](auto index) { |
252 | 4.29k | if (index >= options.size()) |
253 | 0 | return options[0]; |
254 | 4.29k | else { |
255 | 4.29k | return options[index]; |
256 | 4.29k | } |
257 | 4.29k | }(data[1] & (std::bit_ceil(options.size()) - 1)); |
258 | 4.29k | const auto selected_last_chunk = |
259 | 4.29k | (selected_option == simdutf::base64_url || |
260 | 3.64k | selected_option == simdutf::base64_default_no_padding) |
261 | 4.29k | ? simdutf::last_chunk_handling_options::loose |
262 | 4.29k | : [](auto index) { |
263 | 3.16k | if (index >= last_chunk.size()) |
264 | 419 | return last_chunk[0]; |
265 | 2.74k | else { |
266 | 2.74k | return last_chunk[index]; |
267 | 2.74k | } |
268 | 3.16k | }(data[2] & (std::bit_ceil(last_chunk.size()) - 1)); |
269 | | |
270 | | // decode buffer size |
271 | 4.29k | const std::size_t decode_buffer_size = (data[4] << 8) + data[3]; |
272 | | |
273 | | // line length must be at least 4 |
274 | 4.29k | const std::size_t line_length = unsigned{data[5]} + 4u; |
275 | | |
276 | 4.29k | data += optionbytes; |
277 | 4.29k | size -= optionbytes; |
278 | | |
279 | 4.29k | switch (action) { |
280 | 1.12k | case 0: { |
281 | 1.12k | const std::span<const char> chardata{(const char*)data, size}; |
282 | 1.12k | roundtrip(chardata, selected_option, selected_last_chunk, line_length); |
283 | 1.12k | } break; |
284 | 1.01k | case 1: { |
285 | 1.01k | const std::span<const char> chardata{(const char*)data, size}; |
286 | 1.01k | decode(chardata, selected_option, selected_last_chunk); |
287 | 1.01k | } break; |
288 | 1.23k | case 2: { |
289 | 1.23k | const std::span<const char16_t> chardata{(const char16_t*)data, size / 2}; |
290 | 1.23k | decode(chardata, selected_option, selected_last_chunk); |
291 | 1.23k | } break; |
292 | 402 | case 3: { |
293 | 402 | const std::span<const char> chardata{(const char*)data, size}; |
294 | 402 | decode_safe(chardata, selected_option, decode_buffer_size, |
295 | 402 | selected_last_chunk); |
296 | 402 | } break; |
297 | 511 | case 4: { |
298 | 511 | const std::span<const char16_t> chardata{(const char16_t*)data, size / 2}; |
299 | 511 | decode_safe(chardata, selected_option, decode_buffer_size, |
300 | 511 | selected_last_chunk); |
301 | 511 | } break; |
302 | 4.29k | } |
303 | | |
304 | 4.29k | return 0; |
305 | 4.29k | } |