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 : #ifndef BOOST_HTTP_PROTO_IMPL_FIELDS_BASE_IPP
11 : #define BOOST_HTTP_PROTO_IMPL_FIELDS_BASE_IPP
12 :
13 : #include <boost/http_proto/fields.hpp>
14 : #include <boost/http_proto/field.hpp>
15 : #include <boost/http_proto/detail/copied_strings.hpp>
16 : #include <boost/http_proto/detail/except.hpp>
17 : #include <boost/http_proto/detail/number_string.hpp>
18 : #include <boost/http_proto/detail/move_chars.hpp>
19 : #include <boost/assert.hpp>
20 : #include <boost/assert/source_location.hpp>
21 : #include <string>
22 :
23 : namespace boost {
24 : namespace http_proto {
25 :
26 : class fields_base::
27 : op_t
28 : {
29 : fields_base& self_;
30 : string_view* s0_;
31 : string_view* s1_;
32 : char* buf_ = nullptr;
33 : char const* cbuf_ = nullptr;
34 : std::size_t cap_ = 0;
35 :
36 : public:
37 : explicit
38 601 : op_t(
39 : fields_base& self,
40 : string_view* s0 = nullptr,
41 : string_view* s1 = nullptr) noexcept
42 601 : : self_(self)
43 : , s0_(s0)
44 601 : , s1_(s1)
45 : {
46 601 : }
47 :
48 601 : ~op_t()
49 601 : {
50 601 : if(buf_)
51 62 : delete[] buf_;
52 601 : }
53 :
54 : char const*
55 6 : buf() const noexcept
56 : {
57 6 : return buf_;
58 : }
59 :
60 : char const*
61 112 : cbuf() const noexcept
62 : {
63 112 : return cbuf_;
64 : }
65 :
66 : char*
67 9 : end() const noexcept
68 : {
69 9 : return buf_ + cap_;
70 : }
71 :
72 : table
73 3 : tab() const noexcept
74 : {
75 3 : return table(end());
76 : }
77 :
78 : static
79 : std::size_t
80 : growth(
81 : std::size_t n0,
82 : std::size_t m) noexcept;
83 :
84 : bool
85 : reserve(std::size_t bytes);
86 :
87 : bool
88 : grow(
89 : std::size_t extra_char,
90 : std::size_t extra_field);
91 :
92 : void
93 : copy_prefix(
94 : std::size_t n,
95 : std::size_t i) noexcept;
96 :
97 : void
98 : move_chars(
99 : char* dest,
100 : char const* src,
101 : std::size_t n) const noexcept;
102 : };
103 :
104 : /* Growth functions for containers
105 :
106 : N1 = g( N0, M );
107 :
108 : g = growth function
109 : M = minimum capacity
110 : N0 = old size
111 : N1 = new size
112 : */
113 : std::size_t
114 1129 : fields_base::
115 : op_t::
116 : growth(
117 : std::size_t n0,
118 : std::size_t m) noexcept
119 : {
120 1129 : auto const E = alignof(entry);
121 1129 : auto const m1 =
122 1129 : E * ((m + E - 1) / E);
123 1129 : BOOST_ASSERT(m1 >= m);
124 1129 : if(n0 == 0)
125 : {
126 : // exact
127 940 : return m1;
128 : }
129 189 : if(m1 > n0)
130 117 : return m1;
131 72 : return n0;
132 : }
133 :
134 : bool
135 585 : fields_base::
136 : op_t::
137 : reserve(
138 : std::size_t bytes)
139 : {
140 585 : if(bytes > max_capacity_in_bytes())
141 : {
142 : // max capacity exceeded
143 1 : detail::throw_length_error();
144 : }
145 584 : auto n = growth(
146 584 : self_.h_.cap, bytes);
147 584 : if(n <= self_.h_.cap)
148 49 : return false;
149 535 : auto buf = new char[n];
150 535 : buf_ = self_.h_.buf;
151 535 : cbuf_ = self_.h_.cbuf;
152 535 : cap_ = self_.h_.cap;
153 535 : self_.h_.buf = buf;
154 535 : self_.h_.cbuf = buf;
155 535 : self_.h_.cap = n;
156 535 : return true;
157 : }
158 :
159 : bool
160 547 : fields_base::
161 : op_t::
162 : grow(
163 : std::size_t extra_char,
164 : std::size_t extra_field)
165 : {
166 : // extra_field is naturally limited
167 : // by max_off_t, since each field
168 : // is at least 4 bytes: "X:\r\n"
169 547 : BOOST_ASSERT(
170 : extra_field <= max_off_t &&
171 : extra_field <= static_cast<
172 : std::size_t>(
173 : max_off_t - self_.h_.count));
174 547 : if( extra_char > max_off_t ||
175 545 : extra_char > static_cast<std::size_t>(
176 545 : max_off_t - self_.h_.size))
177 2 : detail::throw_length_error();
178 1090 : auto n1 = growth(
179 545 : self_.h_.cap,
180 : detail::header::bytes_needed(
181 545 : self_.h_.size + extra_char,
182 545 : self_.h_.count + extra_field));
183 545 : return reserve(n1);
184 : }
185 :
186 : void
187 0 : fields_base::
188 : op_t::
189 : copy_prefix(
190 : std::size_t n,
191 : std::size_t i) noexcept
192 : {
193 : // copy first n chars
194 0 : std::memcpy(
195 0 : self_.h_.buf,
196 0 : cbuf_,
197 : n);
198 : // copy first i entries
199 0 : if(i > 0)
200 0 : std::memcpy(
201 0 : self_.h_.tab_() - i,
202 : reinterpret_cast<entry*>(
203 0 : buf_ + cap_) - i,
204 : i * sizeof(entry));
205 0 : }
206 :
207 : void
208 38 : fields_base::
209 : op_t::
210 : move_chars(
211 : char* dest,
212 : char const* src,
213 : std::size_t n) const noexcept
214 : {
215 38 : detail::move_chars(
216 38 : dest, src, n, s0_, s1_);
217 38 : }
218 :
219 : //------------------------------------------------
220 :
221 69 : fields_base::
222 : fields_base(
223 0 : detail::kind k) noexcept
224 0 : : fields_view_base(&h_)
225 69 : , h_(k)
226 : {
227 69 : }
228 :
229 : // copy s and parse it
230 453 : fields_base::
231 : fields_base(
232 : detail::kind k,
233 0 : string_view s)
234 0 : : fields_view_base(&h_)
235 453 : , h_(detail::empty{k})
236 : {
237 453 : auto n = detail::header::count_crlf(s);
238 453 : if(h_.kind == detail::kind::fields)
239 : {
240 197 : if(n < 1)
241 0 : detail::throw_invalid_argument();
242 197 : n -= 1;
243 : }
244 : else
245 : {
246 256 : if(n < 2)
247 0 : detail::throw_invalid_argument();
248 256 : n -= 2;
249 : }
250 906 : op_t op(*this);
251 453 : op.grow(s.size(), n);
252 453 : s.copy(h_.buf, s.size());
253 453 : error_code ec;
254 : // VFALCO This is using defaults?
255 453 : header_limits lim;
256 453 : h_.parse(s.size(), lim, ec);
257 453 : if(ec.failed())
258 0 : detail::throw_system_error(ec);
259 453 : }
260 :
261 : // construct a complete copy of h
262 18 : fields_base::
263 : fields_base(
264 12 : detail::header const& h)
265 12 : : fields_view_base(&h_)
266 18 : , h_(h.kind)
267 : {
268 18 : if(h.is_default())
269 : {
270 6 : BOOST_ASSERT(h.cap == 0);
271 6 : BOOST_ASSERT(h.buf == nullptr);
272 6 : h_ = h;
273 6 : return;
274 : }
275 :
276 : // allocate and copy the buffer
277 24 : op_t op(*this);
278 12 : op.grow(h.size, h.count);
279 12 : h.assign_to(h_);
280 12 : std::memcpy(
281 12 : h_.buf, h.cbuf, h.size);
282 12 : h.copy_table(h_.buf + h_.cap);
283 : }
284 :
285 : //------------------------------------------------
286 :
287 540 : fields_base::
288 552 : ~fields_base()
289 : {
290 540 : if(h_.buf)
291 477 : delete[] h_.buf;
292 540 : }
293 :
294 : //------------------------------------------------
295 : //
296 : // Capacity
297 : //
298 : //------------------------------------------------
299 :
300 : void
301 8 : fields_base::
302 : clear() noexcept
303 : {
304 8 : if(! h_.buf)
305 4 : return;
306 : using H =
307 : detail::header;
308 : auto const& h =
309 4 : *H::get_default(
310 4 : h_.kind);
311 4 : h.assign_to(h_);
312 4 : std::memcpy(
313 4 : h_.buf,
314 4 : h.cbuf,
315 4 : h_.size);
316 : }
317 :
318 : void
319 40 : fields_base::
320 : reserve_bytes(
321 : std::size_t n)
322 : {
323 41 : op_t op(*this);
324 40 : if(! op.reserve(n))
325 25 : return;
326 28 : std::memcpy(
327 14 : h_.buf, op.cbuf(), h_.size);
328 14 : auto const nt =
329 14 : sizeof(entry) * h_.count;
330 14 : if(nt > 0)
331 6 : std::memcpy(
332 6 : h_.buf + h_.cap - nt,
333 6 : op.end() - nt,
334 : nt);
335 : }
336 :
337 : void
338 7 : fields_base::
339 : shrink_to_fit() noexcept
340 : {
341 14 : if(detail::header::bytes_needed(
342 7 : h_.size, h_.count) >=
343 7 : h_.cap)
344 3 : return;
345 8 : fields_base tmp(h_);
346 4 : tmp.h_.swap(h_);
347 : }
348 :
349 : //------------------------------------------------
350 : //
351 : // Modifiers
352 : //
353 : //------------------------------------------------
354 :
355 : std::size_t
356 24 : fields_base::
357 : erase(
358 : field id) noexcept
359 : {
360 24 : BOOST_ASSERT(
361 : id != field::unknown);
362 : #if 1
363 24 : auto const end_ = end();
364 24 : auto it = find_last(end_, id);
365 24 : if(it == end_)
366 3 : return 0;
367 21 : std::size_t n = 1;
368 21 : auto const begin_ = begin();
369 21 : raw_erase(it.i_);
370 57 : while(it != begin_)
371 : {
372 36 : --it;
373 36 : if(it->id == id)
374 : {
375 25 : raw_erase(it.i_);
376 25 : ++n;
377 : }
378 : }
379 21 : h_.on_erase_all(id);
380 21 : return n;
381 : #else
382 : std::size_t n = 0;
383 : auto it0 = find(id);
384 : auto const end_ = end();
385 : if(it0 != end_)
386 : {
387 : auto it1 = it0;
388 : std::size_t total = 0;
389 : std::size_t size = 0;
390 : // [it0, it1) run of id
391 : for(;;)
392 : {
393 : size += length(it1.i_);
394 : ++it1;
395 : if(it1 == end_)
396 : goto finish;
397 : if(it1->id != id)
398 : break;
399 : }
400 : std::memmove(
401 : h_.buf + offset(it0.i_),
402 : h_.buf + offset(it1.i_),
403 : h_.size - offset(it2.i_));
404 :
405 : finish:
406 : h_.size -= size;
407 : h_.count -= n;
408 : }
409 : return n;
410 : #endif
411 : }
412 :
413 : std::size_t
414 18 : fields_base::
415 : erase(
416 : string_view name) noexcept
417 : {
418 18 : auto it0 = find(name);
419 18 : auto const end_ = end();
420 18 : if(it0 == end_)
421 3 : return 0;
422 15 : auto it = end_;
423 15 : std::size_t n = 1;
424 15 : auto const id = it0->id;
425 15 : if(id == field::unknown)
426 : {
427 : // fix self-intersection
428 6 : name = it0->name;
429 :
430 : for(;;)
431 : {
432 24 : --it;
433 24 : if(it == it0)
434 6 : break;
435 18 : if(grammar::ci_is_equal(
436 36 : it->name, name))
437 : {
438 9 : raw_erase(it.i_);
439 9 : ++n;
440 : }
441 : }
442 6 : raw_erase(it.i_);
443 : }
444 : else
445 : {
446 : for(;;)
447 : {
448 21 : --it;
449 21 : if(it == it0)
450 9 : break;
451 12 : if(it->id == id)
452 : {
453 6 : raw_erase(it.i_);
454 6 : ++n;
455 : }
456 : }
457 9 : raw_erase(it.i_);
458 9 : h_.on_erase_all(id);
459 : }
460 15 : return n;
461 : }
462 :
463 : //------------------------------------------------
464 :
465 : void
466 17 : fields_base::
467 : set(
468 : iterator it,
469 : string_view value)
470 : {
471 17 : auto const i = it.i_;
472 17 : auto const& e0 = h_.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 : off_t>(value.size());
549 17 : h_.size = static_cast<
550 17 : off_t>(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 : 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 : string_view name,
605 : 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 79 : fields_base::
670 : insert_impl(
671 : field id,
672 : string_view name,
673 : string_view value,
674 : std::size_t before)
675 : {
676 79 : auto const tab0 = h_.tab_();
677 79 : auto const pos = offset(before);
678 : auto const n =
679 79 : name.size() + // name
680 79 : 1 + // ':'
681 79 : ! value.empty() + // [SP]
682 79 : value.size() + // value
683 79 : 2; // CRLF
684 :
685 158 : op_t op(*this, &name, &value);
686 79 : if(op.grow(n, 1))
687 : {
688 : // reallocated
689 53 : if(pos > 0)
690 45 : std::memcpy(
691 45 : h_.buf,
692 45 : op.cbuf(),
693 : pos);
694 53 : if(before > 0)
695 36 : std::memcpy(
696 18 : h_.tab_() - before,
697 18 : tab0 - before,
698 : before * sizeof(entry));
699 106 : std::memcpy(
700 53 : h_.buf + pos + n,
701 53 : op.cbuf() + pos,
702 53 : 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 77 : auto dest = h_.buf + pos;
715 77 : name.copy(dest, name.size());
716 77 : dest += name.size();
717 77 : *dest++ = ':';
718 77 : if(! value.empty())
719 : {
720 74 : *dest++ = ' ';
721 74 : value.copy(
722 : dest, value.size());
723 74 : dest += value.size();
724 : }
725 77 : *dest++ = '\r';
726 77 : *dest = '\n';
727 : }
728 :
729 : // update table
730 77 : auto const tab = h_.tab_();
731 : {
732 77 : auto i = h_.count - before;
733 77 : 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 77 : auto& e = tab[0 - before - 1];
745 77 : e.np = static_cast<off_t>(
746 77 : pos - h_.prefix);
747 77 : e.nn = static_cast<
748 77 : off_t>(name.size());
749 77 : e.vp = static_cast<off_t>(
750 154 : pos - h_.prefix +
751 77 : name.size() + 1 +
752 77 : ! value.empty());
753 77 : e.vn = static_cast<
754 77 : off_t>(value.size());
755 77 : e.id = id;
756 :
757 : // update container
758 77 : h_.count++;
759 77 : h_.size = static_cast<
760 77 : off_t>(h_.size + n);
761 77 : if( id != field::unknown)
762 68 : h_.on_insert(id, value);
763 77 : }
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 : off_t>(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 437 : fields_base::
835 : offset(
836 : std::size_t i) const noexcept
837 : {
838 437 : if(i == 0)
839 140 : return h_.prefix;
840 297 : if(i < h_.count)
841 348 : return h_.prefix +
842 174 : h_.tab_()[0-(i + 1)].np;
843 : // make final CRLF the last "field"
844 : //BOOST_ASSERT(i == h_.count);
845 123 : 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
886 :
887 : #endif
|