Skip to content

Commit 52d01ba

Browse files
author
kelbon
committed
copy_n_fast
1 parent df8b8be commit 52d01ba

6 files changed

Lines changed: 92 additions & 13 deletions

File tree

include/hpack/basic_types.hpp

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#pragma once
22

3+
#include <algorithm>
34
#include <cstdint>
45
#include <iterator>
56
#include <string_view>
6-
77
#include <exception>
88

99
namespace hpack {
@@ -21,7 +21,11 @@ struct protocol_error : std::exception {
2121

2222
// thrown if there are not enough data for reading header
2323
struct incomplete_data_error : hpack::protocol_error {
24-
incomplete_data_error() : hpack::protocol_error("incomplete data") {
24+
// approx value - how many bytes need to be readen for receiving next part (int or string)
25+
size_t required_bytes = 0;
26+
27+
explicit incomplete_data_error(size_t required_bytes_approx)
28+
: hpack::protocol_error("incomplete data"), required_bytes(required_bytes_approx) {
2529
}
2630
};
2731

@@ -48,6 +52,8 @@ concept Out = std::output_iterator<T, byte_t>;
4852

4953
namespace noexport {
5054

55+
// caches first byte for avoiding *it = x == push_back,
56+
// so *it | mask will be into next byte (may be with back_inserter)
5157
template <typename T>
5258
struct adapted_output_iterator {
5359
T base_it;
@@ -102,6 +108,54 @@ Original unadapt(byte_t* ptr) {
102108
return reinterpret_cast<Original>(ptr);
103109
}
104110

111+
// standard interface (e.g. for vector) for inserting many values at back
112+
// Note: ignores fact, that someone can make super-bad type with push_back + insert making something wrong
113+
template <typename T>
114+
constexpr inline bool can_insert_many =
115+
requires(T& value, const char* p) { value.insert(value.end(), p, p); };
116+
117+
// standard back_insert_iterator rly uses protected field exactly for such accessing
118+
template <typename C>
119+
C& access_protected_container(std::back_insert_iterator<C> c) {
120+
static_assert(std::is_trivially_copyable_v<decltype(c)>);
121+
struct accessor : std::back_insert_iterator<C> {
122+
C* get() noexcept {
123+
return this->container;
124+
}
125+
};
126+
return *accessor(c).get();
127+
}
128+
129+
template <typename C>
130+
requires(can_insert_many<C>)
131+
std::back_insert_iterator<C> do_copy_n_fast(const char* ptr, size_t sz, std::back_insert_iterator<C> it) {
132+
auto& c = access_protected_container(it);
133+
c.insert(c.end(), ptr, ptr + sz);
134+
return it; // back insert iterator does not change on ++/* etc
135+
}
136+
137+
template <typename C>
138+
requires(can_insert_many<C>)
139+
adapted_output_iterator<std::back_insert_iterator<C>> do_copy_n_fast(
140+
const char* ptr, size_t sz, adapted_output_iterator<std::back_insert_iterator<C>> it) {
141+
auto& c = access_protected_container(it.base_it);
142+
c.insert(c.end(), ptr, ptr + sz);
143+
return it; // adapted iterator must be unchanged, since base_it unchanged (its back inserter)
144+
}
145+
146+
// fallback
147+
template <typename It>
148+
It do_copy_n_fast(const char* ptr, size_t sz, It it) {
149+
return std::copy_n(ptr, sz, std::move(it));
150+
}
151+
152+
// makes copy_n, but for back_insert iterator makes insert(end, It, It + n)
153+
// this converts many push_backs into one uninitialized_copy_n
154+
template <typename It>
155+
It copy_n_fast(const char* ptr, size_t sz, It it) {
156+
return do_copy_n_fast(ptr, sz, std::move(it));
157+
}
158+
105159
} // namespace noexport
106160

107161
struct table_entry {

include/hpack/decoder.hpp

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,11 +165,12 @@ struct stream_decoder {
165165

166166
// returns where first unparsed byte starts
167167
template <typename V>
168-
In do_feed(std::span<byte_t> chunk, bool last_chunk, V&& visitor) {
168+
In do_feed(std::span<byte_t> chunk, bool last_chunk, V&& visitor, size_t& approx) {
169169
In in = chunk.data();
170170
In e = in + chunk.size();
171171
assert(in != e);
172172
In in_just_before_fail;
173+
approx = 0;
173174
try {
174175
header_view header;
175176
while (in != e) {
@@ -182,11 +183,12 @@ struct stream_decoder {
182183
}
183184
// successfully parsed all headers
184185
return e;
185-
} catch (hpack::incomplete_data_error&) {
186+
} catch (hpack::incomplete_data_error& e) {
187+
approx = e.required_bytes;
186188
if (last_chunk)
187189
throw;
190+
return in_just_before_fail;
188191
}
189-
return in_just_before_fail;
190192
}
191193

192194
public:
@@ -198,22 +200,27 @@ struct stream_decoder {
198200

199201
// `visitor` should accept two string_views, name and value
200202
// optimized for case when each `chunk` >> 1 header
203+
// returns approx count of bytes required for receiving next part of header
204+
// or 0 if there are no unhandled data in chunk
205+
// e.g. may be used to detect too big string before receiving it
201206
template <typename V>
202-
void feed(std::span<byte_t> chunk, bool last_chunk, V&& visitor) {
207+
size_t feed(std::span<byte_t> chunk, bool last_chunk, V&& visitor) {
203208
if (chunk.empty()) [[unlikely]]
204-
return;
209+
return 0;
210+
size_t approx;
205211
if (!incomplete.empty()) {
206212
incomplete.insert(incomplete.end(), chunk.begin(), chunk.end());
207-
In i = do_feed(incomplete, last_chunk, std::forward<V>(visitor));
213+
In i = do_feed(incomplete, last_chunk, std::forward<V>(visitor), approx);
208214
In e = incomplete.data() + incomplete.size();
209215
auto sz = e - i;
210216
// avoid UB on .assign (iterators into vector itself)
211217
memmove(incomplete.data(), i, sz);
212218
incomplete.resize(sz);
213219
} else {
214-
In i = do_feed(chunk, last_chunk, std::forward<V>(visitor));
220+
In i = do_feed(chunk, last_chunk, std::forward<V>(visitor), approx);
215221
incomplete.assign(i, In(chunk.data()) + chunk.size());
216222
}
223+
return approx;
217224
}
218225

219226
// makes possible start from beginning, forgetting previous `feed` calls

include/hpack/integers.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ template <std::unsigned_integral UInt = size_type>
5656
const UInt prefix_mask = (1 << N) - 1;
5757
auto pull = [&] {
5858
if (in == e)
59-
throw incomplete_data_error();
59+
throw incomplete_data_error(2);
6060
auto i = *in;
6161
++in;
6262
return i;

include/hpack/strings.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ O encode_string(std::string_view str, O _out) {
5757
if constexpr (!Huffman) {
5858
*out = 0; // set H bit to 0
5959
out = encode_integer(str.size(), 7, out);
60-
out = std::copy_n(str.data(), str.size(), out);
60+
out = noexport::copy_n_fast(str.data(), str.size(), out);
6161
} else {
6262
out = encode_string_huffman(str, out);
6363
}

src/decoder.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,11 @@ static size_type decode_dynamic_table_size_update(In& in, In e) {
158158

159159
void decode_string(In& in, In e, decoded_string& out) {
160160
if (in == e)
161-
throw incomplete_data_error();
161+
throw incomplete_data_error(1);
162162
bool is_huffman = *in & 0b1000'0000;
163163
size_type str_len = decode_integer(in, e, 7);
164164
if (str_len > std::distance(in, e))
165-
throw incomplete_data_error();
165+
throw incomplete_data_error(str_len - std::distance(in, e));
166166
if (is_huffman)
167167
out.set_huffman((const char*)in, str_len);
168168
else

tests/test_hpack.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,23 @@ TEST(stream_decoder) {
568568
}
569569
}
570570

571+
TEST(stream_decoder_big_str) {
572+
std::string name = "abchdr";
573+
std::string value(15000, 'A');
574+
hpack::encoder e;
575+
bytes_t bytes;
576+
e.encode_header_never_indexing(name, value, std::back_inserter(bytes));
577+
hpack::decoder d;
578+
hpack::stream_decoder sd(d);
579+
error_if(bytes.size() < value.size());
580+
auto vtor = [](std::string_view, std::string_view) { error_if(true); };
581+
size_t required = sd.feed({bytes.data(), bytes.size() - 1}, false, vtor);
582+
error_if(required != 1);
583+
sd.clear();
584+
required = sd.feed({bytes.data(), 8 /*name*/ + 3 /*str size*/ + 1 /*used byte from str*/}, false, vtor);
585+
error_if(required != value.size() - 1);
586+
}
587+
571588
TEST(decode_status) {
572589
hpack::encoder e;
573590
hpack::decoder de;
@@ -1102,6 +1119,7 @@ TEST(encode_with_cache) {
11021119
}
11031120

11041121
int main() {
1122+
test_stream_decoder_big_str();
11051123
test_stream_decoder();
11061124
test_tg_answer_parts();
11071125
test_dyntable_move();

0 commit comments

Comments
 (0)