Line data Source code
1 : //
2 : // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
3 : // Copyright (c) 2024 Christian Mazakas
4 : // Copyright (c) 2024 Mohammad Nejati
5 : //
6 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 : //
9 : // Official repository: https://github.com/cppalliance/http_proto
10 : //
11 :
12 : #include <boost/http_proto/detail/except.hpp>
13 : #include <boost/http_proto/message_view_base.hpp>
14 : #include <boost/http_proto/serializer.hpp>
15 : #include <boost/http_proto/service/zlib_service.hpp>
16 :
17 : #include "detail/filter.hpp"
18 :
19 : #include <boost/buffers/algorithm.hpp>
20 : #include <boost/buffers/buffer_copy.hpp>
21 : #include <boost/buffers/buffer_size.hpp>
22 : #include <boost/core/ignore_unused.hpp>
23 :
24 : #include <stddef.h>
25 :
26 : namespace boost {
27 : namespace http_proto {
28 :
29 : namespace {
30 : class deflator_filter
31 : : public http_proto::detail::filter
32 : {
33 : zlib::stream& deflator_;
34 :
35 : public:
36 49 : deflator_filter(
37 : context& ctx,
38 : http_proto::detail::workspace& ws,
39 : bool use_gzip)
40 196 : : deflator_{ ctx.get_service<zlib::service>()
41 49 : .make_deflator(ws, -1, use_gzip ? 31 : 15, 8) }
42 : {
43 49 : }
44 :
45 : virtual filter::results
46 23641 : on_process(
47 : buffers::mutable_buffer out,
48 : buffers::const_buffer in,
49 : bool more) override
50 : {
51 23641 : auto flush =
52 23641 : more ? zlib::flush::none : zlib::flush::finish;
53 23641 : filter::results results;
54 :
55 : for(;;)
56 : {
57 45713 : auto params = zlib::params{in.data(), in.size(),
58 45713 : out.data(), out.size() };
59 45713 : auto ec = deflator_.write(params, flush);
60 :
61 45713 : results.in_bytes += in.size() - params.avail_in;
62 45713 : results.out_bytes += out.size() - params.avail_out;
63 :
64 56682 : if( ec.failed() &&
65 56682 : ec != zlib::error::buf_err)
66 : {
67 1 : results.ec = ec;
68 23641 : return results;
69 : }
70 :
71 45712 : if( ec == zlib::error::stream_end )
72 : {
73 96 : results.finished = true;
74 96 : return results;
75 : }
76 :
77 45616 : in = buffers::suffix(in, params.avail_in);
78 45616 : out = buffers::suffix(out, params.avail_out);
79 :
80 45616 : if( out.size() == 0 )
81 1416 : return results;
82 :
83 44200 : if( in.size() == 0 )
84 : {
85 44200 : if( results.out_bytes == 0 &&
86 : flush == zlib::flush::none )
87 : {
88 : // TODO: Is flush::block the right choice?
89 : // We might need a filter::flush() interface
90 : // so that the caller can decide when to flush.
91 22072 : flush = zlib::flush::block;
92 22072 : continue;
93 : }
94 22128 : return results;
95 : }
96 22072 : }
97 : }
98 : };
99 : } // namespace
100 :
101 : void
102 0 : consume_buffers(
103 : buffers::const_buffer*& p,
104 : std::size_t& n,
105 : std::size_t bytes)
106 : {
107 0 : while(n > 0)
108 : {
109 0 : if(bytes < p->size())
110 : {
111 0 : *p += bytes;
112 0 : return;
113 : }
114 0 : bytes -= p->size();
115 0 : ++p;
116 0 : --n;
117 : }
118 :
119 : // Precondition violation
120 0 : if(bytes > 0)
121 0 : detail::throw_invalid_argument();
122 : }
123 :
124 : template<class MutableBuffers>
125 : void
126 6240 : write_chunk_header(
127 : MutableBuffers const& dest0,
128 : std::size_t size) noexcept
129 : {
130 : static constexpr char hexdig[] =
131 : "0123456789ABCDEF";
132 : char buf[18];
133 6240 : auto p = buf + 16;
134 106080 : for(std::size_t i = 16; i--;)
135 : {
136 99840 : *--p = hexdig[size & 0xf];
137 99840 : size >>= 4;
138 : }
139 6240 : buf[16] = '\r';
140 6240 : buf[17] = '\n';
141 6240 : auto n = buffers::buffer_copy(
142 : dest0,
143 12480 : buffers::const_buffer(
144 : buf, sizeof(buf)));
145 : ignore_unused(n);
146 6240 : BOOST_ASSERT(n == 18);
147 6240 : BOOST_ASSERT(
148 : buffers::buffer_size(dest0) == n);
149 6240 : }
150 :
151 : template<class DynamicBuffer>
152 : void
153 : write_chunk_close(DynamicBuffer& db)
154 : {
155 : db.commit(
156 : buffers::buffer_copy(
157 : db.prepare(2),
158 : buffers::const_buffer("\r\n", 2)));
159 : }
160 :
161 : template<class DynamicBuffer>
162 : void
163 : write_last_chunk(DynamicBuffer& db)
164 : {
165 : db.commit(
166 : buffers::buffer_copy(
167 : db.prepare(5),
168 : buffers::const_buffer("0\r\n\r\n", 5)));
169 : }
170 :
171 : //------------------------------------------------
172 :
173 45 : serializer::
174 : ~serializer()
175 : {
176 45 : }
177 :
178 0 : serializer::
179 : serializer(
180 : serializer&&) noexcept = default;
181 :
182 11 : serializer::
183 : serializer(
184 11 : context& ctx)
185 11 : : serializer(ctx, 65536)
186 : {
187 11 : }
188 :
189 45 : serializer::
190 : serializer(
191 : context& ctx,
192 45 : std::size_t buffer_size)
193 45 : : ws_(buffer_size)
194 45 : , ctx_(ctx)
195 : {
196 45 : }
197 :
198 : void
199 56 : serializer::
200 : reset() noexcept
201 : {
202 56 : chunk_header_ = {};
203 56 : chunk_close_ = {};
204 56 : last_chunk_ = {};
205 56 : filter_ = nullptr;
206 56 : more_ = false;
207 56 : is_done_ = false;
208 56 : is_chunked_ = false;
209 56 : is_expect_continue_ = false;
210 56 : is_compressed_ = false;
211 56 : filter_done_ = false;
212 56 : in_ = nullptr;
213 56 : out_ = nullptr;
214 56 : ws_.clear();
215 56 : }
216 :
217 : //------------------------------------------------
218 :
219 : auto
220 12479 : serializer::
221 : prepare() ->
222 : system::result<
223 : const_buffers_type>
224 : {
225 : // Precondition violation
226 12479 : if( is_done_ )
227 1 : detail::throw_logic_error();
228 :
229 : // Expect: 100-continue
230 12478 : if( is_expect_continue_ )
231 : {
232 4 : if( !is_header_done_ )
233 2 : return const_buffers_type(hp_, 1);
234 2 : is_expect_continue_ = false;
235 2 : BOOST_HTTP_PROTO_RETURN_EC(
236 : error::expect_100_continue);
237 : }
238 :
239 12474 : if( st_ == style::empty )
240 9 : return const_buffers_type(
241 6 : prepped_.data(), prepped_.size());
242 :
243 12471 : if( st_ == style::buffers && !filter_ )
244 9 : return const_buffers_type(
245 6 : prepped_.data(), prepped_.size());
246 :
247 : // TODO: This is a temporary solution until we refactor
248 : // the implementation for efficient partial buffer consumption.
249 12468 : if( is_chunked_ && buffers::buffer_size(prepped_) && is_header_done_ )
250 21 : return const_buffers_type(
251 14 : prepped_.data(), prepped_.size());
252 :
253 12461 : auto& input = *in_;
254 12461 : auto& output = *out_;
255 12461 : if( st_ == style::source && more_ )
256 : {
257 5494 : auto results = src_->read(
258 5494 : input.prepare(input.capacity()));
259 5494 : if(results.ec.failed())
260 : {
261 1 : is_done_ = true;
262 1 : return results.ec;
263 : }
264 5493 : more_ = !results.finished;
265 5493 : input.commit(results.bytes);
266 : }
267 :
268 30451 : if( st_ == style::stream &&
269 17970 : more_ &&
270 5510 : in_->size() == 0 )
271 1 : BOOST_HTTP_PROTO_RETURN_EC(error::need_data);
272 :
273 : bool has_avail_out =
274 24873 : ((!filter_ && (more_ || input.size() > 0)) ||
275 12414 : (filter_ && !filter_done_));
276 :
277 25057 : auto get_input = [&]() -> buffers::const_buffer
278 : {
279 25057 : if( st_ == style::buffers )
280 : {
281 3081 : if( buffers::buffer_size(buf_) == 0 )
282 48 : return {};
283 :
284 3033 : auto buf = *(buf_.data());
285 3033 : BOOST_ASSERT(buf.size() > 0);
286 3033 : return buf;
287 : }
288 : else
289 : {
290 21976 : if( input.size() == 0 )
291 11016 : return {};
292 :
293 10960 : auto cbs = input.data();
294 10960 : auto buf = *cbs.begin();
295 10960 : if( buf.size() == 0 )
296 : {
297 0 : auto p = cbs.begin();
298 0 : ++p;
299 0 : buf = *p;
300 : }
301 10960 : if( buf.size() == 0 )
302 0 : detail::throw_logic_error();
303 10960 : return buf;
304 : }
305 12459 : };
306 :
307 25057 : auto get_output = [&]() -> buffers::mutable_buffer
308 : {
309 25057 : auto mbs = output.prepare(output.capacity());
310 25057 : auto buf = *mbs.begin();
311 25057 : if( buf.size() == 0 )
312 : {
313 1416 : auto p = mbs.begin();
314 1416 : ++p;
315 1416 : buf = *p;
316 : }
317 25057 : return buf;
318 12459 : };
319 :
320 23640 : auto consume = [&](std::size_t n)
321 : {
322 23640 : if( st_ == style::buffers )
323 : {
324 1664 : buf_.consume(n);
325 1664 : if( buffers::buffer_size(buf_) == 0 )
326 56 : more_ = false;
327 : }
328 : else
329 21976 : input.consume(n);
330 36099 : };
331 :
332 12459 : std::size_t num_written = 0;
333 12459 : if( !filter_ )
334 50 : num_written += input.size();
335 : else
336 : {
337 : for(;;)
338 : {
339 25057 : auto in = get_input();
340 25057 : auto out = get_output();
341 25057 : if( out.size() == 0 )
342 : {
343 1416 : if( output.size() == 0 )
344 0 : detail::throw_logic_error();
345 12408 : break;
346 : }
347 :
348 23641 : auto rs = filter_->process(
349 23641 : out, in, more_);
350 :
351 23641 : if(rs.ec.failed())
352 : {
353 1 : is_done_ = true;
354 1 : return rs.ec;
355 : }
356 :
357 23640 : if( rs.finished )
358 96 : filter_done_ = true;
359 :
360 23640 : consume(rs.in_bytes);
361 :
362 23640 : if( rs.out_bytes == 0 )
363 10992 : break;
364 :
365 12648 : num_written += rs.out_bytes;
366 12648 : output.commit(rs.out_bytes);
367 12648 : }
368 : }
369 :
370 : // end:
371 12458 : std::size_t n = 0;
372 12458 : if( !is_header_done_ )
373 : {
374 58 : BOOST_ASSERT(hp_ == &prepped_[0]);
375 58 : ++n;
376 : }
377 : else
378 12400 : prepped_.reset(prepped_.capacity());
379 :
380 12458 : if( !is_chunked_ )
381 : {
382 18648 : for(buffers::const_buffer const& b : output.data())
383 12432 : prepped_[n++] = b;
384 : }
385 : else
386 : {
387 6242 : if( has_avail_out )
388 : {
389 6239 : write_chunk_header(
390 6239 : chunk_header_, num_written);
391 6239 : prepped_[n++] = chunk_header_;
392 :
393 18717 : for(buffers::const_buffer const& b : output.data())
394 12478 : prepped_[n++] = b;
395 :
396 6239 : prepped_[n++] = chunk_close_;
397 : }
398 :
399 6242 : if( (filter_ && filter_done_) ||
400 6218 : (!filter_ && !more_) )
401 29 : prepped_[n++] = last_chunk_;
402 : }
403 :
404 : auto cbs = const_buffers_type(
405 12458 : prepped_.data(), prepped_.size());
406 :
407 12458 : BOOST_ASSERT(buffers::buffer_size(cbs) > 0);
408 12458 : return cbs;
409 : }
410 :
411 : void
412 14224 : serializer::
413 : consume(
414 : std::size_t n)
415 : {
416 : // Precondition violation
417 14224 : if( is_done_ && n != 0 )
418 1 : detail::throw_logic_error();
419 :
420 14223 : if( is_expect_continue_ )
421 : {
422 : // Cannot consume more than
423 : // the header on 100-continue
424 3 : if( n > hp_->size() )
425 1 : detail::throw_invalid_argument();
426 : }
427 :
428 14222 : if( !is_header_done_ )
429 : {
430 : // consume header
431 76 : if( n < hp_->size() )
432 : {
433 11 : prepped_.consume(n);
434 11 : return;
435 : }
436 65 : n -= hp_->size();
437 65 : prepped_.consume(hp_->size());
438 65 : is_header_done_ = true;
439 : }
440 :
441 14211 : prepped_.consume(n);
442 14211 : if( out_ )
443 : {
444 14197 : BOOST_ASSERT(st_ != style::empty);
445 14197 : out_->consume(n);
446 : }
447 14211 : auto is_empty = (buffers::buffer_size(prepped_) == 0);
448 :
449 14211 : if( st_ == style::buffers && !filter_ && is_empty )
450 4 : more_ = false;
451 :
452 14211 : if( st_ == style::empty &&
453 4 : is_empty &&
454 4 : !is_expect_continue_ )
455 3 : more_ = false;
456 :
457 14211 : if( is_empty )
458 12472 : is_done_ = filter_ ? filter_done_ : !more_;
459 : }
460 :
461 : void
462 24 : serializer::
463 : use_deflate_encoding()
464 : {
465 : // can only apply one encoding
466 24 : if(filter_)
467 0 : detail::throw_logic_error();
468 :
469 24 : is_compressed_ = true;
470 24 : filter_ = &ws_.emplace<deflator_filter>(ctx_, ws_, false);
471 24 : }
472 :
473 : void
474 25 : serializer::
475 : use_gzip_encoding()
476 : {
477 : // can only apply one encoding
478 25 : if( filter_ )
479 0 : detail::throw_logic_error();
480 :
481 25 : is_compressed_ = true;
482 25 : filter_ = &ws_.emplace<deflator_filter>(ctx_, ws_, true);
483 25 : }
484 :
485 : //------------------------------------------------
486 :
487 : void
488 7 : serializer::
489 : copy(
490 : buffers::const_buffer* dest,
491 : buffers::const_buffer const* src,
492 : std::size_t n) noexcept
493 : {
494 14 : while(n--)
495 7 : *dest++ = *src++;
496 7 : }
497 :
498 : void
499 75 : serializer::
500 : start_init(
501 : message_view_base const& m)
502 : {
503 : // VFALCO what do we do with
504 : // metadata error code failures?
505 : // m.ph_->md.maybe_throw();
506 :
507 75 : auto const& md = m.metadata();
508 :
509 75 : is_done_ = false;
510 75 : is_header_done_ = false;
511 75 : is_expect_continue_ = md.expect.is_100_continue;
512 :
513 : // Transfer-Encoding
514 : {
515 75 : auto const& te = md.transfer_encoding;
516 75 : is_chunked_ = te.is_chunked;
517 : }
518 :
519 75 : if( is_chunked_)
520 : {
521 31 : auto* p = ws_.reserve_front(chunked_overhead_);
522 31 : chunk_header_ =
523 31 : buffers::mutable_buffer(p, chunk_header_len_);
524 31 : chunk_close_ =
525 62 : buffers::mutable_buffer(
526 31 : p + chunk_header_len_, crlf_len_);
527 31 : last_chunk_ =
528 62 : buffers::mutable_buffer(
529 31 : p + chunk_header_len_ + crlf_len_,
530 : last_chunk_len_);
531 :
532 31 : buffers::buffer_copy(
533 31 : chunk_close_, buffers::const_buffer("\r\n", 2));
534 31 : buffers::buffer_copy(
535 31 : last_chunk_,
536 62 : buffers::const_buffer("0\r\n\r\n", 5));
537 : }
538 75 : }
539 :
540 : void
541 4 : serializer::
542 : start_empty(
543 : message_view_base const& m)
544 : {
545 4 : start_init(m);
546 :
547 4 : st_ = style::empty;
548 4 : more_ = true;
549 :
550 4 : if(! is_chunked_)
551 : {
552 3 : prepped_ = make_array(
553 : 1); // header
554 : }
555 : else
556 : {
557 1 : prepped_ = make_array(
558 : 1 + // header
559 : 1); // final chunk
560 :
561 : // Buffer is too small
562 1 : if(ws_.size() < 5)
563 0 : detail::throw_length_error();
564 :
565 : buffers::mutable_buffer dest(
566 1 : ws_.data(), 5);
567 1 : buffers::buffer_copy(
568 : dest,
569 1 : buffers::const_buffer(
570 : "0\r\n\r\n", 5));
571 1 : prepped_[1] = dest;
572 : }
573 :
574 4 : hp_ = &prepped_[0];
575 4 : *hp_ = { m.ph_->cbuf, m.ph_->size };
576 4 : }
577 :
578 : void
579 24 : serializer::
580 : start_buffers(
581 : message_view_base const& m)
582 : {
583 24 : st_ = style::buffers;
584 24 : tmp1_ = {};
585 :
586 24 : if( !filter_ && !is_chunked_ )
587 : {
588 6 : prepped_ = make_array(
589 : 1 + // header
590 6 : buf_.size()); // user input
591 :
592 6 : hp_ = &prepped_[0];
593 6 : *hp_ = { m.ph_->cbuf, m.ph_->size };
594 :
595 6 : copy(&prepped_[1], buf_.data(), buf_.size());
596 :
597 6 : more_ = (buffers::buffer_size(buf_) > 0);
598 6 : return;
599 : }
600 :
601 18 : if( !filter_ && is_chunked_ )
602 : {
603 1 : if( buffers::buffer_size(buf_) == 0 )
604 : {
605 0 : prepped_ = make_array(
606 : 1 + // header
607 : 1); // last chunk
608 :
609 0 : hp_ = &prepped_[0];
610 0 : *hp_ = { m.ph_->cbuf, m.ph_->size };
611 0 : prepped_[1] = last_chunk_;
612 0 : more_ = false;
613 0 : return;
614 : }
615 :
616 2 : write_chunk_header(
617 1 : chunk_header_, buffers::buffer_size(buf_));
618 :
619 1 : prepped_ = make_array(
620 : 1 + // header
621 : 1 + // chunk header
622 1 : buf_.size() + // user input
623 : 1 + // chunk close
624 : 1); // last chunk
625 :
626 1 : hp_ = &prepped_[0];
627 1 : *hp_ = { m.ph_->cbuf, m.ph_->size };
628 1 : prepped_[1] = chunk_header_;
629 1 : copy(&prepped_[2], buf_.data(), buf_.size());
630 :
631 1 : prepped_[prepped_.size() - 2] = chunk_close_;
632 1 : prepped_[prepped_.size() - 1] = last_chunk_;
633 1 : more_ = true;
634 1 : return;
635 : }
636 :
637 17 : if( is_chunked_ )
638 : {
639 8 : prepped_ = make_array(
640 : 1 + // header
641 : 1 + // chunk header
642 : 2 + // tmp
643 : 1 + // chunk close
644 : 1); // last chunk
645 : }
646 : else
647 9 : prepped_ = make_array(
648 : 1 + // header
649 : 2); // tmp
650 :
651 17 : hp_ = &prepped_[0];
652 17 : *hp_ = { m.ph_->cbuf, m.ph_->size };
653 17 : tmp0_ = { ws_.data(), ws_.size() };
654 17 : out_ = &tmp0_;
655 17 : in_ = out_;
656 17 : more_ = true;
657 : }
658 :
659 : void
660 25 : serializer::
661 : start_source(
662 : message_view_base const& m,
663 : source* src)
664 : {
665 25 : st_ = style::source;
666 25 : src_ = src;
667 :
668 25 : if( is_chunked_ )
669 : {
670 10 : prepped_ = make_array(
671 : 1 + // header
672 : 1 + // chunk header
673 : 2 + // tmp
674 : 1 + // chunk close
675 : 1); // last chunk
676 : }
677 : else
678 15 : prepped_ = make_array(
679 : 1 + // header
680 : 2); // tmp
681 :
682 25 : if( !filter_ )
683 : {
684 9 : tmp0_ = { ws_.data(), ws_.size() };
685 9 : if( tmp0_.capacity() < 1 )
686 0 : detail::throw_length_error();
687 :
688 9 : in_ = &tmp0_;
689 9 : out_ = &tmp0_;
690 : }
691 : else
692 : {
693 16 : auto n = ws_.size() / 2;
694 16 : auto* p = ws_.reserve_front(n);
695 16 : tmp1_ = buffers::circular_buffer(p, n);
696 :
697 16 : tmp0_ = { ws_.data(), ws_.size() };
698 16 : if( tmp0_.capacity() < 1 )
699 0 : detail::throw_length_error();
700 :
701 16 : in_ = &tmp1_;
702 16 : out_ = &tmp0_;
703 : }
704 :
705 25 : hp_ = &prepped_[0];
706 25 : *hp_ = { m.ph_->cbuf, m.ph_->size };
707 25 : more_ = true;
708 25 : }
709 :
710 : auto
711 22 : serializer::
712 : start_stream(
713 : message_view_base const& m) ->
714 : stream
715 : {
716 22 : start_init(m);
717 :
718 22 : st_ = style::stream;
719 22 : if( is_chunked_ )
720 : {
721 11 : prepped_ = make_array(
722 : 1 + // header
723 : 1 + // chunk header
724 : 2 + // tmp
725 : 1 + // chunk close
726 : 1); // last chunk
727 : }
728 : else
729 11 : prepped_ = make_array(
730 : 1 + // header
731 : 2); // tmp
732 :
733 22 : if( !filter_ )
734 : {
735 6 : tmp0_ = { ws_.data(), ws_.size() };
736 6 : if( tmp0_.capacity() < 1 )
737 0 : detail::throw_length_error();
738 :
739 6 : in_ = &tmp0_;
740 6 : out_ = &tmp0_;
741 : }
742 : else
743 : {
744 16 : auto n = ws_.size() / 2;
745 16 : auto* p = ws_.reserve_front(n);
746 16 : tmp1_ = buffers::circular_buffer(p, n);
747 :
748 16 : tmp0_ = { ws_.data(), ws_.size() };
749 16 : if( tmp0_.capacity() < 1 )
750 0 : detail::throw_length_error();
751 :
752 16 : in_ = &tmp1_;
753 16 : out_ = &tmp0_;
754 : }
755 :
756 22 : hp_ = &prepped_[0];
757 22 : *hp_ = { m.ph_->cbuf, m.ph_->size };
758 22 : more_ = true;
759 22 : return stream{*this};
760 : }
761 :
762 : //------------------------------------------------
763 :
764 : std::size_t
765 139 : serializer::
766 : stream::
767 : capacity() const noexcept
768 : {
769 139 : return sr_->in_->capacity();
770 : }
771 :
772 : std::size_t
773 72 : serializer::
774 : stream::
775 : size() const noexcept
776 : {
777 72 : return sr_->in_->size();
778 : }
779 :
780 : bool
781 63 : serializer::
782 : stream::
783 : is_full() const noexcept
784 : {
785 63 : return capacity() == 0;
786 : }
787 :
788 : auto
789 5512 : serializer::
790 : stream::
791 : prepare() const ->
792 : buffers_type
793 : {
794 5512 : return sr_->in_->prepare(sr_->in_->capacity());
795 : }
796 :
797 : void
798 5512 : serializer::
799 : stream::
800 : commit(std::size_t n) const
801 : {
802 : // the stream must make a non-zero amount of bytes
803 : // available to the serializer
804 5512 : if( n == 0 )
805 1 : detail::throw_logic_error();
806 :
807 5511 : sr_->in_->commit(n);
808 5511 : }
809 :
810 : void
811 25 : serializer::
812 : stream::
813 : close() const
814 : {
815 : // Precondition violation
816 25 : if(! sr_->more_ )
817 4 : detail::throw_logic_error();
818 21 : sr_->more_ = false;
819 21 : }
820 :
821 : //------------------------------------------------
822 :
823 : } // http_proto
824 : } // boost
|