diff --git a/doc/qbk/07_concepts/BuffersGenerator.qbk b/doc/qbk/07_concepts/BuffersGenerator.qbk new file mode 100644 index 00000000..9b442896 --- /dev/null +++ b/doc/qbk/07_concepts/BuffersGenerator.qbk @@ -0,0 +1,140 @@ +[/ + Copyright (c) 2016-2022 Vinnie Falco (vinnie dot falco at gmail dot com) + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + Official repository: https://github.com/boostorg/beast +] + +[section:BuffersGenerator BuffersGenerator] + +A [*BuffersGenerator] provides a generalized interface for generating +serialized data for sequential processing. + +The generator will be asked to produce buffers. The consuming code will signal +how much of the data has been consumed, and repeatedly query for buffers until +no more data is available, or the generator indicates an error condition. + +Overloads of [link beast.ref.boost__beast__write `write`] and [link +beast.ref.boost__beast__async_write `async_write`] operations are provided as +free functions. These operations will consume the output of a +[*BuffersGenerator] and process the data by writing them to a +__SyncWriteStream__ or __AsyncWriteStream__ respectively. + +[heading Associated Types] + +* [link beast.ref.boost__beast__is_buffers_generator `is_buffers_generator`] +* __ConstBufferSequence__ + +[heading Requirements] + +In this table: + +* `G` denotes a type meeting the requirements of [*BuffersGenerator]. +* `g` denotes a value of type `G`. +* `n` is a value of type `std::size_t`. +* `ec` is a value of type [link beast.ref.boost__beast__error_code `error_code&`]. + +[table Valid expressions +[[Expression] [Type] [Semantics, Pre/Post-conditions]] +[ + [`G::const_buffers_type`] + [] + [ + A type which meets the requirements of __ConstBufferSequence__. + This is the type of buffer returned by `g.prepare(ec)`. + ] +][ + [`g.is_done()`] + [`bool`] + [ + Called to ask the generator for its completion status. + + A generator has completed when no new buffer will be produced and + previously produced buffers have been fully consumed. + + [*Note:] The result of invoking `prepare` on `g` once it has completed + is unspecified. + + This member function does not alter the state of the generator. + ] +][ + [`g.prepare(ec)`] + [`G::const_buffers_type`] + [ + Called to ask the generator to produce buffers containing data for + processing. + + The returned value is the __ConstBufferSequence__ representing + unconsumed data. + + The function will ensure that `ec.failed()` is `false` if there was no + error or set to the appropriate error code if there was one. + + If no unconsumed data is available, this operation shall make progress + to eventually reach completion. + + The result of invoking `prepare` after completion or encountered + error(s) is defined by the generator implementation. It can not be + assumed to be meaningful or safe to do so, in general. + + The capacity of the buffer returned is defined by the generator + implementation. + + [*Note:] Any buffers obtained by previous calls to `prepare` are + invalidated. + ] +][ + [`g.consume(n)`] + [] + [ + This function is called to signal that the consumer (caller) of the + generator has processed part of the data returned by the previous call + to `prepare`. + + The value of `n` indicates how much of the data processed (in bytes). + + When `n` exceeds the number of bytes returned from the last call to + `prepare`, `consume` shall behave as if `n` was equal to that number. + + Remaining unconsumed data will be returned from subsequent calls to + `prepare`. + + [*Note:] Any buffers obtained by previous calls to `prepare` are + invalidated. + ] +][ + [`is_buffers_generator`] + [`std::true_type`] + [ + An alias for `std::true_type` for `G`, otherwise an alias + for `std::false_type`. + ] +]] + +[heading Exemplar] + +``` + // A buffer sequence generator + struct BuffersGenerator + { + using const_buffers_type = net::const_buffer; + + bool is_done(); + const_buffers_type prepare( error_code& ec ); + void consume( std::size_t n ); + }; + + static_assert( + is_buffers_generator::value, ""); +``` + +[heading Models] + +[heading Algorithms] + +* [link beast.ref.boost__beast__async_write `async_write`] +* [link beast.ref.boost__beast__write `write`] + +[endsect] diff --git a/doc/qbk/07_concepts/_concepts.qbk b/doc/qbk/07_concepts/_concepts.qbk index 4856312f..ac35fd3e 100644 --- a/doc/qbk/07_concepts/_concepts.qbk +++ b/doc/qbk/07_concepts/_concepts.qbk @@ -15,6 +15,7 @@ This section describes all of the concepts defined by the library. [include BodyReader.qbk] [include BodyWriter.qbk] [include BufferSequence.qbk] +[include BuffersGenerator.qbk] [include DynamicBuffer.qbk] [include Fields.qbk] [include FieldsWriter.qbk] diff --git a/doc/qbk/main.qbk b/doc/qbk/main.qbk index 4ab551cb..eb346fe9 100644 --- a/doc/qbk/main.qbk +++ b/doc/qbk/main.qbk @@ -95,6 +95,7 @@ [def __BodyReader__ [link beast.concepts.BodyReader ['BodyReader]]] [def __BodyWriter__ [link beast.concepts.BodyWriter ['BodyWriter]]] [def __BufferSequence__ [link beast.concepts.BufferSequence ['BufferSequence]]] +[def __BuffersGenerator__ [link beast.concepts.BufferSequence ['BuffersGenerator]]] [def __DynamicBuffer__ [link beast.concepts.DynamicBuffer ['DynamicBuffer]]] [def __Fields__ [link beast.concepts.Fields ['Fields]]] [def __FieldsWriter__ [link beast.concepts.FieldsWriter ['FieldsWriter]]] diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index 0b40c44f..3367c8ef 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -76,15 +76,15 @@ Type Traits executor_type - lowest_layer_type has_get_executor is_async_read_stream - is_async_write_stream is_async_stream + is_async_write_stream is_file is_sync_read_stream is_sync_stream is_sync_write_stream + lowest_layer_type SSL @@ -141,6 +141,7 @@ Functions + async_write buffer_bytes buffers_cat buffers_front @@ -152,12 +153,14 @@ ostream read_size read_size_or_throw + write Type Traits - buffers_type buffers_iterator_type + buffers_type + is_buffers_generator is_const_buffer_sequence is_mutable_buffer_sequence @@ -165,6 +168,7 @@ Concepts BufferSequence + BuffersGenerator DynamicBuffer diff --git a/doc/xsl/custom-overrides.xsl b/doc/xsl/custom-overrides.xsl index c91aff4f..f9c6d78d 100644 --- a/doc/xsl/custom-overrides.xsl +++ b/doc/xsl/custom-overrides.xsl @@ -23,6 +23,7 @@ 'Body', 'BufferSequence', 'BufferSequence', (: TODO: Was this intended to be 'BufferSequence_' ?? :) + 'BuffersGenerator', 'CompletionCondition', 'CompletionHandler', 'CompletionToken', diff --git a/include/boost/beast/core.hpp b/include/boost/beast/core.hpp index e6dd3be8..c7d1c4f6 100644 --- a/include/boost/beast/core.hpp +++ b/include/boost/beast/core.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/include/boost/beast/core/buffers_generator.hpp b/include/boost/beast/core/buffers_generator.hpp new file mode 100644 index 00000000..b57b603f --- /dev/null +++ b/include/boost/beast/core/buffers_generator.hpp @@ -0,0 +1,206 @@ +// +// Copyright (c) 2022 Seth Heeren (sgheeren at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_CORE_BUFFERS_GENERATOR_HPP +#define BOOST_BEAST_CORE_BUFFERS_GENERATOR_HPP + +#include + +#include +#include +#include + +#include + +namespace boost { +namespace beast { + +/** Determine if type satisfies the BuffersGenerator requirements. + + This metafunction is used to determine if the specified type meets the + requirements for a buffers generator. + + The static member `value` will evaluate to `true` if so, `false` otherwise. + + @tparam T a type to check +*/ +#ifdef BOOST_BEAST_DOXYGEN +template +struct is_buffers_generator + : integral_constant +{ +}; +#else +template +struct is_buffers_generator + : std::false_type +{ +}; + +template +struct is_buffers_generator< + T, detail::void_t().is_done()), + typename T::const_buffers_type( + std::declval().prepare( + std::declval())), + std::declval().consume( + std::size_t{}) + )>> : std::true_type +{ +}; +#endif + +/** Write all output from a BuffersGenerator to a stream. + + This function is used to write all of the buffers generated + by a caller-provided BuffersGenerator to a stream. The call + will block until one of the following conditions is true: + + @li The generator returns an empty buffers sequence. + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's `write_some` function. + + @param stream The stream to which the data is to be written. + The type must support the SyncWriteStream concept. + + @param generator The generator to use. + + @param ec Set to the error, if any occurred. + + @return The number of bytes written to the stream. + + @see BuffersGenerator +*/ +template< + class SyncWriteStream, + class BuffersGenerator +#if ! BOOST_BEAST_DOXYGEN + , + typename std::enable_if:: + type>::value>::type* = nullptr +#endif + > +std::size_t +write( + SyncWriteStream& stream, + BuffersGenerator&& generator, + beast::error_code& ec); + +/** Write all output from a BuffersGenerator to a stream. + + This function is used to write all of the buffers generated + by a caller-provided BuffersGenerator to a stream. The call + will block until one of the following conditions is true: + + @li The generator returns an empty buffers sequence. + + @li An error occurs. + + This operation is implemented in terms of one or more calls + to the stream's `write_some` function. + + @param stream The stream to which the data is to be written. + The type must support the SyncWriteStream concept. + + @param generator The generator to use. + + @return The number of bytes written to the stream. + + @throws system_error Thrown on failure. + + @see BuffersGenerator +*/ +template< + class SyncWriteStream, + class BuffersGenerator +#if ! BOOST_BEAST_DOXYGEN + , + typename std::enable_if:: + type>::value>::type* = nullptr +#endif + > +std::size_t +write( + SyncWriteStream& stream, + BuffersGenerator&& generator); + +/** Write all output from a BuffersGenerator asynchronously to a + stream. + + This function is used to write all of the buffers generated + by a caller-provided @ref BuffersGenerator to a stream. The + function call always returns immediately. The asynchronous + operation will continue until one of the following + conditions is true: + + @li The generator returns an empty buffers sequence. + + @li An error occurs. + + This operation is implemented in terms of zero or more calls + to the stream's `async_write_some` function, and is known as + a composed operation. The program must ensure that + the stream performs no other writes until this operation + completes. + + @param stream The stream to which the data is to be written. + The type must support the SyncWriteStream concept. + + @param generator The generator to use. + + @param handler The completion handler to invoke when the + operation completes. The implementation takes ownership of + the handler by performing a decay-copy. The equivalent + function signature of the handler must be: + @code + void handler( + error_code const& error, // result of operation + std::size_t bytes_transferred // the number of bytes written to the stream + ); + @endcode + Regardless of whether the asynchronous operation completes + immediately or not, the handler will not be invoked from + within this function. Invocation of the handler will be + performed in a manner equivalent to using `net::post`. + + @see BuffersGenerator +*/ +template< + class AsyncWriteStream, + class BuffersGenerator, + class CompletionToken +#if !BOOST_BEAST_DOXYGEN + , + typename std::enable_if::value>::type* = nullptr +#endif + > +auto +async_write( + AsyncWriteStream& stream, + BuffersGenerator generator, + CompletionToken&& token) // + -> typename net::async_result< + typename std::decay< + CompletionToken>::type, + void(error_code, std::size_t)>:: + return_type; + +} // namespace beast +} // namespace boost + +#include + +#endif diff --git a/include/boost/beast/core/impl/buffers_generator.hpp b/include/boost/beast/core/impl/buffers_generator.hpp new file mode 100644 index 00000000..602911f0 --- /dev/null +++ b/include/boost/beast/core/impl/buffers_generator.hpp @@ -0,0 +1,176 @@ +// +// Copyright (c) 2022 Seth Heeren (sgheeren at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +#ifndef BOOST_BEAST_CORE_IMPL_BUFFERS_GENERATOR_HPP +#define BOOST_BEAST_CORE_IMPL_BUFFERS_GENERATOR_HPP + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace boost { +namespace beast { + +namespace detail { + +template < + class AsyncWriteStream, + class BuffersGenerator> +struct write_buffers_generator_op + : boost::asio::coroutine +{ + write_buffers_generator_op( + AsyncWriteStream& s, BuffersGenerator g) + : s_(s) + , g_(std::move(g)) + { + } + + template + void operator()( + Self& self, error_code ec = {}, std::size_t n = 0) + { + BOOST_ASIO_CORO_REENTER(*this) + { + while(! g_.is_done()) + { + BOOST_ASIO_CORO_YIELD + { + auto cb = g_.prepare(ec); + if(ec) + goto complete; + s_.async_write_some( + cb, std::move(self)); + } + if(ec) + goto complete; + + g_.consume(n); + + total_ += n; + } + + complete: + self.complete(ec, total_); + } + } + +private: + AsyncWriteStream& s_; + BuffersGenerator g_; + std::size_t total_ = 0; +}; + +} // detail + +template< + class SyncWriteStream, + class BuffersGenerator, + typename std::enable_if< // + is_buffers_generator::type>::value>::type* /*= nullptr*/ + > +size_t +write( + SyncWriteStream& stream, + BuffersGenerator&& generator, + beast::error_code& ec) +{ + static_assert( + is_sync_write_stream::value, + "SyncWriteStream type requirements not met"); + + ec.clear(); + size_t total = 0; + while(! generator.is_done()) + { + auto cb = generator.prepare(ec); + if(ec) + break; + + size_t n = net::write(stream, cb, ec); + + if(ec) + break; + + generator.consume(n); + total += n; + } + + return total; +} + +//---------------------------------------------------------- + +template< + class SyncWriteStream, + class BuffersGenerator, + typename std::enable_if::type>::value>:: + type* /*= nullptr*/ + > +std::size_t +write(SyncWriteStream& stream, BuffersGenerator&& generator) +{ + static_assert( + is_sync_write_stream::value, + "SyncWriteStream type requirements not met"); + beast::error_code ec; + std::size_t n = write( + stream, std::forward(generator), ec); + if(ec) + BOOST_THROW_EXCEPTION(system_error{ ec }); + return n; +} + +//---------------------------------------------------------- + +template< + class AsyncWriteStream, + class BuffersGenerator, + class CompletionToken, + typename std::enable_if::value>::type* /*= nullptr*/ + > +auto +async_write( + AsyncWriteStream& stream, + BuffersGenerator generator, + CompletionToken&& token) -> + typename net::async_result< + typename std::decay::type, + void(error_code, std::size_t)>::return_type +{ + static_assert( + beast::is_async_write_stream::value, + "AsyncWriteStream type requirements not met"); + + return net::async_compose< // + CompletionToken, + void(error_code, std::size_t)>( + detail::write_buffers_generator_op< + AsyncWriteStream, + BuffersGenerator>{ stream, std::move(generator) }, + token, + stream); +} + +} // namespace beast +} // namespace boost + +#endif diff --git a/test/beast/core/CMakeLists.txt b/test/beast/core/CMakeLists.txt index 3f332814..f4d36459 100644 --- a/test/beast/core/CMakeLists.txt +++ b/test/beast/core/CMakeLists.txt @@ -35,6 +35,7 @@ add_executable (tests-beast-core buffered_read_stream.cpp buffers_adaptor.cpp buffers_cat.cpp + buffers_generator.cpp buffers_prefix.cpp buffers_range.cpp buffers_suffix.cpp diff --git a/test/beast/core/Jamfile b/test/beast/core/Jamfile index e251293b..16ac22d1 100644 --- a/test/beast/core/Jamfile +++ b/test/beast/core/Jamfile @@ -26,6 +26,7 @@ local SOURCES = buffered_read_stream.cpp buffers_adaptor.cpp buffers_cat.cpp + buffers_generator.cpp buffers_prefix.cpp buffers_range.cpp buffers_suffix.cpp diff --git a/test/beast/core/buffers_generator.cpp b/test/beast/core/buffers_generator.cpp new file mode 100644 index 00000000..15169329 --- /dev/null +++ b/test/beast/core/buffers_generator.cpp @@ -0,0 +1,336 @@ +// +// Copyright (c) 2022 Seth Heeren (sgheeren at gmail dot com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/boostorg/beast +// + +// Test that header file is self-contained. +#include + +#include "test_buffer.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace boost { +namespace beast { + +namespace detail { + +struct test_buffers_generator { + using underlying_buffer_sequence = std::array; + using const_buffers_type = buffers_suffix; + std::size_t iterations_ = 5; + bool verbose_ = false; + error_code emulate_error_; + + test_buffers_generator( + error_code emulate_error = {}, bool verbose = false) + : verbose_(verbose) + , emulate_error_(emulate_error) + { + } + + const_buffers_type cur_{}; + + bool + is_done() const + { + return iterations_ == 0 && ! buffer_bytes(cur_); + } + + const_buffers_type + prepare(error_code& ec) + { + ec = {}; + BEAST_EXPECT(! is_done()); + if(verbose_) + std::clog + << "prepare, iterations_:" << iterations_ + << " '" << buffers_to_string(cur_) << "' "; + if (!buffer_bytes(cur_)) { + if (iterations_) { + cur_ = const_buffers_type( + underlying_buffer_sequence{ + net::buffer("abcde", iterations_), + net::buffer("12345", iterations_), + }); + iterations_ -= 1; + } + if(emulate_error_ && iterations_ == 3) + { + ec = emulate_error_; // generate the specified error + } + } + + if (verbose_) + std::clog << " -> '" << buffers_to_string(cur_) + << "'\n"; + return const_buffers_type{ cur_ }; + } + + void consume(std::size_t n) { + cur_.consume(n); + } +}; + +} // namespace detail + +class buffers_generator_test : public unit_test::suite +{ +public: + void + testMinimalGenerator(error_code emulate_error) + { + static_assert( + is_buffers_generator< + detail::test_buffers_generator>::value, + "buffers_generator not modeled"); + + detail::test_buffers_generator gen{emulate_error}; + error_code ec; + + std::vector actual; + + while(! gen.is_done()) + { + detail::test_buffers_generator::const_buffers_type b = + gen.prepare(ec); + + if (ec) { + BEAST_EXPECT(emulate_error == ec); + // In test we ignore the error because we know that's okay. + // + // For general models of BuffersGenerator behaviour is + // unspecified when using a generator after receiving an error. + } + + actual.push_back(buffers_to_string(b)); + + gen.consume(3); // okay if > buffer_bytes + } + BEAST_EXPECT(! ec.failed()); + + if(! emulate_error) + { + BEAST_EXPECT( + (actual == + std::vector{ + "abcde12345", "de12345", "2345", "5", + "abcd1234", "d1234", "34", "abc123", "123", + "ab12", "2", "a1" })); + } + } + + void + testWrite(error_code emulate_error) + { + net::io_context ioc; + test::stream out(ioc), in(ioc); + test::connect(out, in); + + { + detail::test_buffers_generator gen{emulate_error}; + + beast::error_code ec; + auto total = write(out, gen, ec); + + BEAST_EXPECT(ec == emulate_error); + + if(! emulate_error) + { + BEAST_EXPECT(total == 30); + + BEAST_EXPECT(5 == out.nwrite()); + BEAST_EXPECT(30 == in.nwrite_bytes()); + BEAST_EXPECT( + "abcde12345abcd1234abc123ab12a1" == in.str()); + } else + { + BEAST_EXPECT(total == 10); + + BEAST_EXPECT(1 == out.nwrite()); + BEAST_EXPECT(10 == in.nwrite_bytes()); + BEAST_EXPECT("abcde12345" == in.str()); + } + } + + in.clear(); + + { + error_code ec; + auto total = write(out, detail::test_buffers_generator{emulate_error}, ec); + + BEAST_EXPECT(ec == emulate_error); + + if(! emulate_error) + { + BEAST_EXPECT(total == 30); + BEAST_EXPECT("abcde12345abcd1234abc123ab12a1" == in.str()); + } else + { + BEAST_EXPECT(total == 10); + BEAST_EXPECT("abcde12345" == in.str()); + } + } + } + + void + testWriteException(error_code emulate_error) + { + net::io_context ioc; + test::stream out(ioc), in(ioc); + { + test::connect(out, in); + + detail::test_buffers_generator gen{emulate_error}; + + try { + auto total = write(out, gen); + if (emulate_error) + BEAST_EXPECT(!"unreachable"); + BEAST_EXPECT(total == 30); + } catch(system_error const& se) + { + BEAST_EXPECT(se.code() == emulate_error); + } + } + + if(! emulate_error) + { + BEAST_EXPECT(5 == out.nwrite()); + BEAST_EXPECT(30 == in.nwrite_bytes()); + BEAST_EXPECT( + "abcde12345abcd1234abc123ab12a1" == in.str()); + } else + { + BEAST_EXPECT(1 == out.nwrite()); + BEAST_EXPECT(10 == in.nwrite_bytes()); + BEAST_EXPECT("abcde12345" == in.str()); + } + } + + void + testAsyncWrite(error_code emulate_error) + { + net::io_context ioc; + test::stream out(ioc), in(ioc); + { + test::connect(out, in); + + detail::test_buffers_generator gen{emulate_error}; + + async_write( + out, + gen, + [&](error_code ec, std::size_t total) + { + BEAST_EXPECT(ec == emulate_error); + if(! emulate_error) + { + BEAST_EXPECT(total == 30); + } else + { + BEAST_EXPECT(total == 10); + } + }); + + ioc.run(); + } + + if(! emulate_error) + { + BEAST_EXPECT(5 == out.nwrite()); + BEAST_EXPECT(30 == in.nwrite_bytes()); + BEAST_EXPECT( + "abcde12345abcd1234abc123ab12a1" == in.str()); + } else + { + BEAST_EXPECT(1 == out.nwrite()); + BEAST_EXPECT(10 == in.nwrite_bytes()); + BEAST_EXPECT("abcde12345" == in.str()); + } + } + + void + testWriteFail() + { + net::io_context ioc; + test::fail_count fc { 3 }; + test::stream out(ioc, fc), in(ioc); + { + test::connect(out, in); + + detail::test_buffers_generator gen; + + try { + /*auto total =*/ write(out, gen); + BEAST_EXPECT(! "unreachable"); + } catch(system_error const& se) + { + BEAST_EXPECT(se.code() == test::error::test_failure); + } + } + + BEAST_EXPECT(3 == out.nwrite()); + BEAST_EXPECT(18 == in.nwrite_bytes()); // first two writes 10+8 + BEAST_EXPECT("abcde12345abcd1234" == in.str()); + } + + void + testAsyncWriteFail() + { + net::io_context ioc; + test::fail_count fc { 3 }; + test::stream out(ioc, fc), in(ioc); + { + test::connect(out, in); + + detail::test_buffers_generator gen; + + async_write( + out, gen, [&](error_code ec, std::size_t total) { + BEAST_EXPECT(total == 18); + BEAST_EXPECT(ec == test::error::test_failure); + }); + + ioc.run(); + } + + BEAST_EXPECT(3 == out.nwrite()); + + BEAST_EXPECT(18 == in.nwrite_bytes()); // first two writes 10+8 + BEAST_EXPECT("abcde12345abcd1234" == in.str()); + } + + void + run() override + { + for(error_code emulate_error : + {error_code{}, error_code{error::timeout}}) + { + testMinimalGenerator(emulate_error); + testWrite(emulate_error); + testWriteException(emulate_error); + testAsyncWrite(emulate_error); + } + + testWriteFail(); + testAsyncWriteFail(); + } +}; + +BEAST_DEFINE_TESTSUITE(beast,core,buffers_generator); + +} // beast +} // boost