Line data Source code
1 : //
2 : // Copyright (c) 2021 Vinnie Falco (vinnie.falco@gmail.com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/http_proto
8 : //
9 :
10 : #include <boost/http_proto/fields_base.hpp>
11 : #include <boost/http_proto/field.hpp>
12 : #include <boost/http_proto/header_limits.hpp>
13 : #include <boost/http_proto/detail/except.hpp>
14 : #include "detail/copied_strings.hpp"
15 : #include "detail/move_chars.hpp"
16 : #include "detail/number_string.hpp"
17 : #include <boost/url/grammar/ci_string.hpp>
18 : #include <boost/assert.hpp>
19 : #include <boost/assert/source_location.hpp>
20 : #include <string>
21 :
22 : namespace boost {
23 : namespace http_proto {
24 :
25 : class fields_base::
26 : op_t
27 : {
28 : fields_base& self_;
29 : core::string_view* s0_;
30 : core::string_view* s1_;
31 : char* buf_ = nullptr;
32 : char const* cbuf_ = nullptr;
33 : std::size_t cap_ = 0;
34 :
35 : public:
36 : explicit
37 613 : op_t(
38 : fields_base& self,
39 : core::string_view* s0 = nullptr,
40 : core::string_view* s1 = nullptr) noexcept
41 613 : : self_(self)
42 : , s0_(s0)
43 613 : , s1_(s1)
44 : {
45 613 : }
46 :
47 613 : ~op_t()
48 613 : {
49 613 : if(buf_)
50 68 : delete[] buf_;
51 613 : }
52 :
53 : char const*
54 6 : buf() const noexcept
55 : {
56 6 : return buf_;
57 : }
58 :
59 : char const*
60 123 : cbuf() const noexcept
61 : {
62 123 : return cbuf_;
63 : }
64 :
65 : char*
66 9 : end() const noexcept
67 : {
68 9 : return buf_ + cap_;
69 : }
70 :
71 : table
72 3 : tab() const noexcept
73 : {
74 3 : return table(end());
75 : }
76 :
77 : static
78 : std::size_t
79 : growth(
80 : std::size_t n0,
81 : std::size_t m) noexcept;
82 :
83 : bool
84 : reserve(std::size_t bytes);
85 :
86 : bool
87 : grow(
88 : std::size_t extra_char,
89 : std::size_t extra_field);
90 :
91 : void
92 : copy_prefix(
93 : std::size_t n,
94 : std::size_t i) noexcept;
95 :
96 : void
97 : move_chars(
98 : char* dest,
99 : char const* src,
100 : std::size_t n) const noexcept;
101 : };
102 :
103 : /* Growth functions for containers
104 :
105 : N1 = g( N0, M );
106 :
107 : g = growth function
108 : M = minimum capacity
109 : N0 = old size
110 : N1 = new size
111 : */
112 : std::size_t
113 1153 : fields_base::
114 : op_t::
115 : growth(
116 : std::size_t n0,
117 : std::size_t m) noexcept
118 : {
119 1153 : auto const E = alignof(entry);
120 1153 : auto const m1 =
121 1153 : E * ((m + E - 1) / E);
122 1153 : BOOST_ASSERT(m1 >= m);
123 1153 : if(n0 == 0)
124 : {
125 : // exact
126 952 : return m1;
127 : }
128 201 : if(m1 > n0)
129 129 : return m1;
130 72 : return n0;
131 : }
132 :
133 : bool
134 597 : fields_base::
135 : op_t::
136 : reserve(
137 : std::size_t bytes)
138 : {
139 597 : if(bytes > max_capacity_in_bytes())
140 : {
141 : // max capacity exceeded
142 1 : detail::throw_length_error();
143 : }
144 596 : auto n = growth(
145 596 : self_.h_.cap, bytes);
146 596 : if(n <= self_.h_.cap)
147 49 : return false;
148 547 : auto buf = new char[n];
149 547 : buf_ = self_.h_.buf;
150 547 : cbuf_ = self_.h_.cbuf;
151 547 : cap_ = self_.h_.cap;
152 547 : self_.h_.buf = buf;
153 547 : self_.h_.cbuf = buf;
154 547 : self_.h_.cap = n;
155 547 : return true;
156 : }
157 :
158 : bool
159 559 : fields_base::
160 : op_t::
161 : grow(
162 : std::size_t extra_char,
163 : std::size_t extra_field)
164 : {
165 : // extra_field is naturally limited
166 : // by max_offset, since each field
167 : // is at least 4 bytes: "X:\r\n"
168 559 : BOOST_ASSERT(
169 : extra_field <= max_offset &&
170 : extra_field <= static_cast<
171 : std::size_t>(
172 : max_offset - self_.h_.count));
173 559 : if( extra_char > max_offset ||
174 557 : extra_char > static_cast<std::size_t>(
175 557 : max_offset - self_.h_.size))
176 2 : detail::throw_length_error();
177 1114 : auto n1 = growth(
178 557 : self_.h_.cap,
179 : detail::header::bytes_needed(
180 557 : self_.h_.size + extra_char,
181 557 : self_.h_.count + extra_field));
182 557 : return reserve(n1);
183 : }
184 :
185 : void
186 0 : fields_base::
187 : op_t::
188 : copy_prefix(
189 : std::size_t n,
190 : std::size_t i) noexcept
191 : {
192 : // copy first n chars
193 0 : std::memcpy(
194 0 : self_.h_.buf,
195 0 : cbuf_,
196 : n);
197 : // copy first i entries
198 0 : if(i > 0)
199 0 : std::memcpy(
200 0 : self_.h_.tab_() - i,
201 : reinterpret_cast<entry*>(
202 0 : buf_ + cap_) - i,
203 : i * sizeof(entry));
204 0 : }
205 :
206 : void
207 38 : fields_base::
208 : op_t::
209 : move_chars(
210 : char* dest,
211 : char const* src,
212 : std::size_t n) const noexcept
213 : {
214 38 : detail::move_chars(
215 38 : dest, src, n, s0_, s1_);
216 38 : }
217 :
218 : //------------------------------------------------
219 :
220 69 : fields_base::
221 : fields_base(
222 0 : detail::kind k) noexcept
223 0 : : fields_view_base(&h_)
224 69 : , h_(k)
225 : {
226 69 : }
227 :
228 : // copy s and parse it
229 459 : fields_base::
230 : fields_base(
231 : detail::kind k,
232 0 : core::string_view s)
233 0 : : fields_view_base(&h_)
234 459 : , h_(detail::empty{k})
235 : {
236 459 : auto n = detail::header::count_crlf(s);
237 459 : if(h_.kind == detail::kind::fields)
238 : {
239 201 : if(n < 1)
240 0 : detail::throw_invalid_argument();
241 201 : n -= 1;
242 : }
243 : else
244 : {
245 258 : if(n < 2)
246 0 : detail::throw_invalid_argument();
247 258 : n -= 2;
248 : }
249 918 : op_t op(*this);
250 459 : op.grow(s.size(), n);
251 459 : s.copy(h_.buf, s.size());
252 459 : system::error_code ec;
253 : // VFALCO This is using defaults?
254 459 : header_limits lim;
255 459 : h_.parse(s.size(), lim, ec);
256 459 : if(ec.failed())
257 0 : detail::throw_system_error(ec);
258 459 : }
259 :
260 : // construct a complete copy of h
261 18 : fields_base::
262 : fields_base(
263 12 : detail::header const& h)
264 12 : : fields_view_base(&h_)
265 18 : , h_(h.kind)
266 : {
267 18 : if(h.is_default())
268 : {
269 6 : BOOST_ASSERT(h.cap == 0);
270 6 : BOOST_ASSERT(h.buf == nullptr);
271 6 : h_ = h;
272 6 : return;
273 : }
274 :
275 : // allocate and copy the buffer
276 24 : op_t op(*this);
277 12 : op.grow(h.size, h.count);
278 12 : h.assign_to(h_);
279 12 : std::memcpy(
280 12 : h_.buf, h.cbuf, h.size);
281 12 : h.copy_table(h_.buf + h_.cap);
282 : }
283 :
284 : //------------------------------------------------
285 :
286 546 : fields_base::
287 558 : ~fields_base()
288 : {
289 546 : if(h_.buf)
290 483 : delete[] h_.buf;
291 546 : }
292 :
293 : //------------------------------------------------
294 : //
295 : // Capacity
296 : //
297 : //------------------------------------------------
298 :
299 : void
300 8 : fields_base::
301 : clear() noexcept
302 : {
303 8 : if(! h_.buf)
304 4 : return;
305 : using H =
306 : detail::header;
307 : auto const& h =
308 4 : *H::get_default(
309 4 : h_.kind);
310 4 : h.assign_to(h_);
311 4 : std::memcpy(
312 4 : h_.buf,
313 4 : h.cbuf,
314 4 : h_.size);
315 : }
316 :
317 : void
318 40 : fields_base::
319 : reserve_bytes(
320 : std::size_t n)
321 : {
322 41 : op_t op(*this);
323 40 : if(! op.reserve(n))
324 25 : return;
325 28 : std::memcpy(
326 14 : h_.buf, op.cbuf(), h_.size);
327 14 : auto const nt =
328 14 : sizeof(entry) * h_.count;
329 14 : if(nt > 0)
330 6 : std::memcpy(
331 6 : h_.buf + h_.cap - nt,
332 6 : op.end() - nt,
333 : nt);
334 : }
335 :
336 : void
337 7 : fields_base::
338 : shrink_to_fit() noexcept
339 : {
340 14 : if(detail::header::bytes_needed(
341 7 : h_.size, h_.count) >=
342 7 : h_.cap)
343 3 : return;
344 8 : fields_base tmp(h_);
345 4 : tmp.h_.swap(h_);
346 : }
347 :
348 : //------------------------------------------------
349 : //
350 : // Modifiers
351 : //
352 : //------------------------------------------------
353 :
354 : std::size_t
355 24 : fields_base::
356 : erase(
357 : field id) noexcept
358 : {
359 24 : BOOST_ASSERT(
360 : id != field::unknown);
361 : #if 1
362 24 : auto const end_ = end();
363 24 : auto it = find_last(end_, id);
364 24 : if(it == end_)
365 3 : return 0;
366 21 : std::size_t n = 1;
367 21 : auto const begin_ = begin();
368 21 : raw_erase(it.i_);
369 57 : while(it != begin_)
370 : {
371 36 : --it;
372 36 : if(it->id == id)
373 : {
374 25 : raw_erase(it.i_);
375 25 : ++n;
376 : }
377 : }
378 21 : h_.on_erase_all(id);
379 21 : return n;
380 : #else
381 : std::size_t n = 0;
382 : auto it0 = find(id);
383 : auto const end_ = end();
384 : if(it0 != end_)
385 : {
386 : auto it1 = it0;
387 : std::size_t total = 0;
388 : std::size_t size = 0;
389 : // [it0, it1) run of id
390 : for(;;)
391 : {
392 : size += length(it1.i_);
393 : ++it1;
394 : if(it1 == end_)
395 : goto finish;
396 : if(it1->id != id)
397 : break;
398 : }
399 : std::memmove(
400 : h_.buf + offset(it0.i_),
401 : h_.buf + offset(it1.i_),
402 : h_.size - offset(it2.i_));
403 :
404 : finish:
405 : h_.size -= size;
406 : h_.count -= n;
407 : }
408 : return n;
409 : #endif
410 : }
411 :
412 : std::size_t
413 18 : fields_base::
414 : erase(
415 : core::string_view name) noexcept
416 : {
417 18 : auto it0 = find(name);
418 18 : auto const end_ = end();
419 18 : if(it0 == end_)
420 3 : return 0;
421 15 : auto it = end_;
422 15 : std::size_t n = 1;
423 15 : auto const id = it0->id;
424 15 : if(id == field::unknown)
425 : {
426 : // fix self-intersection
427 6 : name = it0->name;
428 :
429 : for(;;)
430 : {
431 24 : --it;
432 24 : if(it == it0)
433 6 : break;
434 18 : if(grammar::ci_is_equal(
435 36 : it->name, name))
436 : {
437 9 : raw_erase(it.i_);
438 9 : ++n;
439 : }
440 : }
441 6 : raw_erase(it.i_);
442 : }
443 : else
444 : {
445 : for(;;)
446 : {
447 21 : --it;
448 21 : if(it == it0)
449 9 : break;
450 12 : if(it->id == id)
451 : {
452 6 : raw_erase(it.i_);
453 6 : ++n;
454 : }
455 : }
456 9 : raw_erase(it.i_);
457 9 : h_.on_erase_all(id);
458 : }
459 15 : return n;
460 : }
461 :
462 : //------------------------------------------------
463 :
464 : void
465 17 : fields_base::
466 : set(
467 : iterator it,
468 : core::string_view value)
469 : {
470 17 : auto const i = it.i_;
471 17 : auto tab = h_.tab();
472 17 : auto const& e0 = tab[i];
473 17 : auto const pos0 = offset(i);
474 17 : auto const pos1 = offset(i + 1 );
475 : std::ptrdiff_t dn =
476 17 : value.size() -
477 17 : it->value.size();
478 17 : if( value.empty() &&
479 17 : ! it->value.empty())
480 0 : --dn; // remove SP
481 17 : else if(
482 17 : it->value.empty() &&
483 0 : ! value.empty())
484 0 : ++dn; // add SP
485 :
486 34 : op_t op(*this, &value);
487 20 : if( dn > 0 &&
488 6 : op.grow(value.size() -
489 20 : it->value.size(), 0))
490 : {
491 : // reallocated
492 3 : auto dest = h_.buf +
493 3 : pos0 + e0.nn + 1;
494 6 : std::memcpy(
495 3 : h_.buf,
496 3 : op.buf(),
497 3 : dest - h_.buf);
498 3 : if(! value.empty())
499 : {
500 3 : *dest++ = ' ';
501 3 : value.copy(
502 : dest,
503 : value.size());
504 3 : dest += value.size();
505 : }
506 3 : *dest++ = '\r';
507 3 : *dest++ = '\n';
508 6 : std::memcpy(
509 3 : h_.buf + pos1 + dn,
510 6 : op.buf() + pos1,
511 3 : h_.size - pos1);
512 6 : std::memcpy(
513 3 : h_.buf + h_.cap -
514 3 : sizeof(entry) * h_.count,
515 3 : &op.tab()[h_.count - 1],
516 3 : sizeof(entry) * h_.count);
517 : }
518 : else
519 : {
520 : // copy the value first
521 28 : auto dest = h_.buf + pos0 +
522 14 : it->name.size() + 1;
523 14 : if(! value.empty())
524 : {
525 14 : *dest++ = ' ';
526 14 : value.copy(
527 : dest,
528 : value.size());
529 14 : dest += value.size();
530 : }
531 14 : op.move_chars(
532 14 : h_.buf + pos1 + dn,
533 14 : h_.buf + pos1,
534 14 : h_.size - pos1);
535 14 : *dest++ = '\r';
536 14 : *dest++ = '\n';
537 : }
538 : {
539 : // update tab
540 17 : auto ft = h_.tab();
541 22 : for(std::size_t j = h_.count - 1;
542 22 : j > i; --j)
543 5 : ft[j] = ft[j] + dn;
544 17 : auto& e = ft[i];
545 34 : e.vp = e.np + e.nn +
546 17 : 1 + ! value.empty();
547 17 : e.vn = static_cast<
548 17 : offset_type>(value.size());
549 17 : h_.size = static_cast<
550 17 : offset_type>(h_.size + dn);
551 : }
552 17 : auto const id = it->id;
553 17 : if(h_.is_special(id))
554 : {
555 : // replace first char of name
556 : // with null to hide metadata
557 7 : char saved = h_.buf[pos0];
558 7 : auto& e = h_.tab()[i];
559 7 : e.id = field::unknown;
560 7 : h_.buf[pos0] = '\0';
561 7 : h_.on_erase(id);
562 7 : h_.buf[pos0] = saved; // restore
563 7 : e.id = id;
564 7 : h_.on_insert(id, it->value);
565 : }
566 17 : }
567 :
568 : // erase existing fields with id
569 : // and then add the field with value
570 : void
571 18 : fields_base::
572 : set(
573 : field id,
574 : core::string_view value)
575 : {
576 18 : BOOST_ASSERT(
577 : id != field::unknown);
578 18 : auto const i0 = h_.find(id);
579 18 : if(i0 != h_.count)
580 : {
581 : // field exists
582 12 : auto const ft = h_.tab();
583 : {
584 : // provide strong guarantee
585 : auto const n0 =
586 12 : h_.size - length(i0);
587 : auto const n =
588 12 : ft[i0].nn + 2 +
589 12 : value.size() + 2;
590 : // VFALCO missing overflow check
591 12 : reserve_bytes(n0 + n);
592 : }
593 12 : erase_all_impl(i0, id);
594 : }
595 18 : insert_impl(id, to_string(id),
596 18 : value, h_.count);
597 18 : }
598 :
599 : // erase existing fields with name
600 : // and then add the field with value
601 : void
602 13 : fields_base::
603 : set(
604 : core::string_view name,
605 : core::string_view value)
606 : {
607 13 : auto const i0 = h_.find(name);
608 13 : if(i0 != h_.count)
609 : {
610 : // field exists
611 9 : auto const ft = h_.tab();
612 9 : auto const id = ft[i0].id;
613 : {
614 : // provide strong guarantee
615 : auto const n0 =
616 9 : h_.size - length(i0);
617 : auto const n =
618 9 : ft[i0].nn + 2 +
619 9 : value.size() + 2;
620 : // VFALCO missing overflow check
621 9 : reserve_bytes(n0 + n);
622 : }
623 : // VFALCO simple algorithm but
624 : // costs one extra memmove
625 9 : erase_all_impl(i0, id);
626 : }
627 13 : insert_impl(
628 : string_to_field(name),
629 13 : name, value, h_.count);
630 12 : }
631 :
632 : //------------------------------------------------
633 : //
634 : // (implementation)
635 : //
636 : //------------------------------------------------
637 :
638 : // copy start line and fields
639 : void
640 9 : fields_base::
641 : copy_impl(
642 : detail::header const& h)
643 : {
644 9 : BOOST_ASSERT(
645 : h.kind == ph_->kind);
646 9 : if(! h.is_default())
647 : {
648 : auto const n =
649 6 : detail::header::bytes_needed(
650 6 : h.size, h.count);
651 6 : if(n <= h_.cap)
652 : {
653 : // no realloc
654 1 : h.assign_to(h_);
655 1 : h.copy_table(
656 1 : h_.buf + h_.cap);
657 1 : std::memcpy(
658 1 : h_.buf,
659 1 : h.cbuf,
660 1 : h.size);
661 1 : return;
662 : }
663 : }
664 16 : fields_base tmp(h);
665 8 : tmp.h_.swap(h_);
666 : }
667 :
668 : void
669 85 : fields_base::
670 : insert_impl(
671 : field id,
672 : core::string_view name,
673 : core::string_view value,
674 : std::size_t before)
675 : {
676 85 : auto const tab0 = h_.tab_();
677 85 : auto const pos = offset(before);
678 : auto const n =
679 85 : name.size() + // name
680 85 : 1 + // ':'
681 85 : ! value.empty() + // [SP]
682 85 : value.size() + // value
683 85 : 2; // CRLF
684 :
685 170 : op_t op(*this, &name, &value);
686 85 : if(op.grow(n, 1))
687 : {
688 : // reallocated
689 59 : if(pos > 0)
690 50 : std::memcpy(
691 50 : h_.buf,
692 50 : op.cbuf(),
693 : pos);
694 59 : if(before > 0)
695 42 : std::memcpy(
696 21 : h_.tab_() - before,
697 21 : tab0 - before,
698 : before * sizeof(entry));
699 118 : std::memcpy(
700 59 : h_.buf + pos + n,
701 59 : op.cbuf() + pos,
702 59 : h_.size - pos);
703 : }
704 : else
705 : {
706 24 : op.move_chars(
707 24 : h_.buf + pos + n,
708 24 : h_.buf + pos,
709 24 : h_.size - pos);
710 : }
711 :
712 : // serialize
713 : {
714 83 : auto dest = h_.buf + pos;
715 83 : name.copy(dest, name.size());
716 83 : dest += name.size();
717 83 : *dest++ = ':';
718 83 : if(! value.empty())
719 : {
720 74 : *dest++ = ' ';
721 74 : value.copy(
722 : dest, value.size());
723 74 : dest += value.size();
724 : }
725 83 : *dest++ = '\r';
726 83 : *dest = '\n';
727 : }
728 :
729 : // update table
730 83 : auto const tab = h_.tab_();
731 : {
732 83 : auto i = h_.count - before;
733 83 : if(i > 0)
734 : {
735 18 : auto p0 = tab0 - h_.count;
736 18 : auto p = tab - h_.count - 1;
737 18 : do
738 : {
739 36 : *p++ = *p0++ + n;
740 : }
741 36 : while(--i);
742 : }
743 : }
744 83 : auto& e = tab[0 - static_cast<std::ptrdiff_t>(before) - 1];
745 83 : e.np = static_cast<offset_type>(
746 83 : pos - h_.prefix);
747 83 : e.nn = static_cast<
748 83 : offset_type>(name.size());
749 83 : e.vp = static_cast<offset_type>(
750 166 : pos - h_.prefix +
751 83 : name.size() + 1 +
752 83 : ! value.empty());
753 83 : e.vn = static_cast<
754 83 : offset_type>(value.size());
755 83 : e.id = id;
756 :
757 : // update container
758 83 : h_.count++;
759 83 : h_.size = static_cast<
760 83 : offset_type>(h_.size + n);
761 83 : if( id != field::unknown)
762 68 : h_.on_insert(id, value);
763 83 : }
764 :
765 : // erase i and update metadata
766 : void
767 31 : fields_base::
768 : erase_impl(
769 : std::size_t i,
770 : field id) noexcept
771 : {
772 31 : raw_erase(i);
773 31 : if(id != field::unknown)
774 31 : h_.on_erase(id);
775 31 : }
776 :
777 : //------------------------------------------------
778 :
779 : void
780 141 : fields_base::
781 : raw_erase(
782 : std::size_t i) noexcept
783 : {
784 141 : BOOST_ASSERT(i < h_.count);
785 141 : BOOST_ASSERT(h_.buf != nullptr);
786 141 : auto const p0 = offset(i);
787 141 : auto const p1 = offset(i + 1);
788 141 : std::memmove(
789 141 : h_.buf + p0,
790 141 : h_.buf + p1,
791 141 : h_.size - p1);
792 141 : auto const n = p1 - p0;
793 141 : --h_.count;
794 141 : auto ft = h_.tab();
795 216 : for(;i < h_.count; ++i)
796 75 : ft[i] = ft[i + 1] - n;
797 141 : h_.size = static_cast<
798 141 : offset_type>(h_.size - n);
799 141 : }
800 :
801 : //------------------------------------------------
802 :
803 : // erase all fields with id
804 : // and update metadata
805 : std::size_t
806 21 : fields_base::
807 : erase_all_impl(
808 : std::size_t i0,
809 : field id) noexcept
810 : {
811 21 : BOOST_ASSERT(
812 : id != field::unknown);
813 21 : std::size_t n = 1;
814 21 : std::size_t i = h_.count - 1;
815 21 : auto const ft = h_.tab();
816 46 : while(i > i0)
817 : {
818 25 : if(ft[i].id == id)
819 : {
820 13 : raw_erase(i);
821 13 : ++n;
822 : }
823 : // go backwards to
824 : // reduce memmoves
825 25 : --i;
826 : }
827 21 : raw_erase(i0);
828 21 : h_.on_erase_all(id);
829 21 : return n;
830 : }
831 :
832 : // return i-th field absolute offset
833 : std::size_t
834 443 : fields_base::
835 : offset(
836 : std::size_t i) const noexcept
837 : {
838 443 : if(i == 0)
839 143 : return h_.prefix;
840 300 : if(i < h_.count)
841 348 : return h_.prefix +
842 174 : h_.tab_()[0-(static_cast<std::ptrdiff_t>(i) + 1)].np;
843 : // make final CRLF the last "field"
844 : //BOOST_ASSERT(i == h_.count);
845 126 : return h_.size - 2;
846 : }
847 :
848 : // return i-th field absolute length
849 : std::size_t
850 21 : fields_base::
851 : length(
852 : std::size_t i) const noexcept
853 : {
854 : return
855 21 : offset(i + 1) -
856 21 : offset(i);
857 : }
858 :
859 : //------------------------------------------------
860 :
861 : // erase n fields matching id
862 : // without updating metadata
863 : void
864 0 : fields_base::
865 : raw_erase_n(
866 : field id,
867 : std::size_t n) noexcept
868 : {
869 : // iterate in reverse
870 0 : auto e = &h_.tab()[h_.count];
871 0 : auto const e0 = &h_.tab()[0];
872 0 : while(n > 0)
873 : {
874 0 : BOOST_ASSERT(e != e0);
875 0 : ++e; // decrement
876 0 : if(e->id == id)
877 : {
878 0 : raw_erase(e0 - e);
879 0 : --n;
880 : }
881 : }
882 0 : }
883 :
884 : } // http_proto
885 : } // boost
|