Repository: randombit/botan
Pull request #3765 (2023-10-18 13:08:15)

Introduce <botan/boost_compat.h> as feature flag
This introduces `#include <botan/boost_compat.h>` that defines `BOTAN_MINIMUM_SUPPORTED_BOOST_VERSION` as the minimum version of boost to be usable by this version of Botan. If the available boost headers are new enough, `BOTAN_FOUND_COMPATIBLE_BOOST_VERSION` is defined as well. In this case, it is safe to `#include <botan/asio_stream.h>`, otherwise a macro-error would be generated.

Usage example:

```C++
#include <botan/boost_compat.h>

#if defined(BOTAN_FOUND_COMPATIBLE_BOOST_VERSION)
#include <botan/asio_stream.h>
// profit...
#else
// fallback...
#endif

```


Patch:
diff --git a/doc/api_ref/tls.rst b/doc/api_ref/tls.rst
index 1c47fc338..a8d4c8199 100644
--- a/doc/api_ref/tls.rst
+++ b/doc/api_ref/tls.rst
@@ -1191,7 +1191,13 @@ TLS Stream
 
 :cpp:class:`TLS::Stream` offers a Boost.Asio compatible wrapper around :cpp:class:`TLS::Client` and :cpp:class:`TLS::Server`.
 It can be used as an alternative to Boost.Asio's `ssl::stream <https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/reference/ssl__stream.html>`_ with minor adjustments to the using code.
-It offers the following interface:
+
+To use the asio stream wrapper, a relatively recent version of boost is required.
+Include ``botan/boost_compat.h`` and check that ``BOTAN_FOUND_COMPATIBLE_BOOST_VERSION``
+is defined before including ``botan/asio_stream.h`` to be ensure compatibility at
+compile time of your application.
+
+The asio Stream offers the following interface:
 
 .. cpp:class:: template <class StreamLayer, class ChannelT> TLS::Stream
 
diff --git a/src/examples/tls_stream_client.cpp b/src/examples/tls_stream_client.cpp
index f617f5063..e880ec401 100644
--- a/src/examples/tls_stream_client.cpp
+++ b/src/examples/tls_stream_client.cpp
@@ -1,14 +1,17 @@
 #include <iostream>
 
-#include <botan/asio_stream.h>
-#include <botan/auto_rng.h>
-#include <botan/certstor_system.h>
-#include <botan/tls.h>
+#include <botan/boost_compat.h>
+#if defined(BOTAN_FOUND_COMPATIBLE_BOOST_VERSION)
 
-#include <boost/asio.hpp>
-#include <boost/beast.hpp>
-#include <boost/bind.hpp>
-#include <utility>
+   #include <botan/asio_stream.h>
+   #include <botan/auto_rng.h>
+   #include <botan/certstor_system.h>
+   #include <botan/tls.h>
+
+   #include <boost/asio.hpp>
+   #include <boost/beast.hpp>
+   #include <boost/bind.hpp>
+   #include <utility>
 
 namespace http = boost::beast::http;
 namespace ap = boost::asio::placeholders;
@@ -113,3 +116,12 @@ int main() {
 
    return 0;
 }
+
+#else
+
+int main() {
+   std::cout << "Your boost version is too old, sorry.\n";
+   return 1;
+}
+
+#endif
diff --git a/src/lib/tls/asio/asio_async_ops.h b/src/lib/tls/asio/asio_async_ops.h
index de887744f..cad138a3c 100644
--- a/src/lib/tls/asio/asio_async_ops.h
+++ b/src/lib/tls/asio/asio_async_ops.h
@@ -9,10 +9,8 @@
 #ifndef BOTAN_ASIO_ASYNC_OPS_H_
 #define BOTAN_ASIO_ASYNC_OPS_H_
 
-#include <botan/types.h>
-
-#include <boost/version.hpp>
-#if BOOST_VERSION >= 107300
+#include <botan/boost_compat.h>
+#if defined(BOTAN_FOUND_COMPATIBLE_BOOST_VERSION)
 
    #include <botan/asio_error.h>
 
@@ -124,7 +122,7 @@ class AsyncReadOperation : public AsyncBase<Handler, typename Stream::executor_t
 
       void operator()(boost::system::error_code ec, std::size_t bytes_transferred, bool isContinuation = true) {
          reenter(this) {
-            if(!ec && bytes_transferred > 0) {
+            if(bytes_transferred > 0 && !ec) {
                // We have received encrypted data from the network, now hand it to TLS::Channel for decryption.
                boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytes_transferred};
                m_stream.process_encrypted_data(read_buffer, ec);
@@ -138,13 +136,13 @@ class AsyncReadOperation : public AsyncBase<Handler, typename Stream::executor_t
                ec = StreamError::StreamTruncated;
             }
 
-            if(!ec && !m_stream.has_received_data() && boost::asio::buffer_size(m_buffers) > 0) {
+            if(!m_stream.has_received_data() && !ec && boost::asio::buffer_size(m_buffers) > 0) {
                // The channel did not decrypt a complete record yet, we need more data from the socket.
                m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this));
                return;
             }
 
-            if(!ec && m_stream.has_received_data()) {
+            if(m_stream.has_received_data() && !ec) {
                // The channel has decrypted a TLS record, now copy it to the output buffers.
                m_decodedBytes = m_stream.copy_received_data(m_buffers);
             }
@@ -202,7 +200,7 @@ class AsyncWriteOperation : public AsyncBase<Handler, typename Stream::executor_
             // Note: bytes_transferred will be zero on first call
             m_stream.consume_send_buffer(bytes_transferred);
 
-            if(!ec && m_stream.has_data_to_send()) {
+            if(m_stream.has_data_to_send() && !ec) {
                m_stream.next_layer().async_write_some(m_stream.send_buffer(), std::move(*this));
                return;
             }
@@ -258,13 +256,13 @@ class AsyncHandshakeOperation : public AsyncBase<Handler, typename Stream::execu
                ec = StreamError::StreamTruncated;
             }
 
-            if(!ec && bytesTransferred > 0) {
+            if(bytesTransferred > 0 && !ec) {
                // Provide encrypted TLS data received from the network to TLS::Channel for decryption
                boost::asio::const_buffer read_buffer{m_stream.input_buffer().data(), bytesTransferred};
                m_stream.process_encrypted_data(read_buffer, ec);
             }
 
-            if(!ec && m_stream.has_data_to_send()) {
+            if(m_stream.has_data_to_send() && !ec) {
                // Write encrypted TLS data provided by the TLS::Channel on the wire
 
                // Note: we construct `AsyncWriteOperation` with 0 as its last parameter (`plainBytesTransferred`). This
@@ -278,7 +276,7 @@ class AsyncHandshakeOperation : public AsyncBase<Handler, typename Stream::execu
                return;
             }
 
-            if(!ec && !m_stream.native_handle()->is_handshake_complete()) {
+            if(!m_stream.native_handle()->is_handshake_complete() && !ec) {
                // Read more encrypted TLS data from the network
                m_stream.next_layer().async_read_some(m_stream.input_buffer(), std::move(*this));
                return;
@@ -305,5 +303,5 @@ class AsyncHandshakeOperation : public AsyncBase<Handler, typename Stream::execu
 
    #include <boost/asio/unyield.hpp>
 
-#endif  // BOOST_VERSION
+#endif
 #endif  // BOTAN_ASIO_ASYNC_OPS_H_
diff --git a/src/lib/tls/asio/asio_context.h b/src/lib/tls/asio/asio_context.h
index 7f55bb40a..cc68a2875 100644
--- a/src/lib/tls/asio/asio_context.h
+++ b/src/lib/tls/asio/asio_context.h
@@ -9,10 +9,8 @@
 #ifndef BOTAN_ASIO_TLS_CONTEXT_H_
 #define BOTAN_ASIO_TLS_CONTEXT_H_
 
-#include <botan/types.h>
-
-#include <boost/version.hpp>
-#if BOOST_VERSION >= 107300
+#include <botan/boost_compat.h>
+#if defined(BOTAN_FOUND_COMPATIBLE_BOOST_VERSION)
 
    #include <functional>
 
@@ -99,5 +97,5 @@ class Context {
 
 }  // namespace Botan::TLS
 
-#endif  // BOOST_VERSION
+#endif
 #endif  // BOTAN_ASIO_TLS_CONTEXT_H_
diff --git a/src/lib/tls/asio/asio_error.h b/src/lib/tls/asio/asio_error.h
index 9e38c41a4..39a1132eb 100644
--- a/src/lib/tls/asio/asio_error.h
+++ b/src/lib/tls/asio/asio_error.h
@@ -9,10 +9,8 @@
 #ifndef BOTAN_ASIO_ERROR_H_
 #define BOTAN_ASIO_ERROR_H_
 
-#include <botan/types.h>
-
-#include <boost/version.hpp>
-#if BOOST_VERSION >= 107300
+#include <botan/boost_compat.h>
+#if defined(BOTAN_FOUND_COMPATIBLE_BOOST_VERSION)
 
    #include <boost/system/system_error.hpp>
 
@@ -122,5 +120,5 @@ struct is_error_code_enum<Botan::ErrorType> {
 
 }  // namespace boost::system
 
-#endif  // BOOST_VERSION
+#endif
 #endif  // BOTAN_ASIO_ERROR_H_
diff --git a/src/lib/tls/asio/asio_stream.h b/src/lib/tls/asio/asio_stream.h
index 11c2c50bd..9e6bac7b3 100644
--- a/src/lib/tls/asio/asio_stream.h
+++ b/src/lib/tls/asio/asio_stream.h
@@ -10,12 +10,10 @@
 #ifndef BOTAN_ASIO_STREAM_H_
 #define BOTAN_ASIO_STREAM_H_
 
-#include <botan/types.h>
-
-#include <boost/version.hpp>
-
-// First version of boost asio that is prepared to use C++20 concepts
-#if BOOST_VERSION >= 107300
+#include <botan/boost_compat.h>
+#if !defined(BOTAN_FOUND_COMPATIBLE_BOOST_VERSION)
+   #error Available boost headers are too old for the boost asio stream.
+#else
 
    #include <botan/asio_async_ops.h>
    #include <botan/asio_context.h>
@@ -800,5 +798,5 @@ class Stream {
 
 }  // namespace Botan::TLS
 
-#endif  // BOOST_VERSION
+#endif
 #endif  // BOTAN_ASIO_STREAM_H_
diff --git a/src/lib/utils/boost/boost_compat.h b/src/lib/utils/boost/boost_compat.h
new file mode 100644
index 000000000..64346bc9e
--- /dev/null
+++ b/src/lib/utils/boost/boost_compat.h
@@ -0,0 +1,47 @@
+/*
+* Checks compatibility between the existing headers of Botan and boost
+* (C) 2023 Jack Lloyd
+*     2023 René Meusel - Rohde & Schwarz Cybersecurity
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_BOOST_COMPAT_H_
+#define BOTAN_BOOST_COMPAT_H_
+
+#include <botan/build.h>
+
+#if defined(BOTAN_HAS_BOOST_ASIO)
+
+   #include <boost/version.hpp>
+
+   /** @brief minimum supported boost version for the TLS ASIO wrapper
+    *
+    *  BOOST_VERSION % 100 is the patch level
+    *  BOOST_VERSION / 100 % 1000 is the minor version
+    *  BOOST_VERSION / 100000 is the major version
+    *
+    * Botan may still work with older versions of boost. Though, the asio TLS
+    * wrapper won't work with versions older than the one specified below.
+    *
+    * Also note the changelog with rationales for the required versions:
+    *
+    * until Botan 3.2.0
+    *   1.66.0 - first version to be compatible with Networking TS (N4656) and boost::beast
+    *
+    * as of Botan 3.3.0
+    *   1.73.0 - first version supporting the C++20 concepts syntax
+    */
+   #define BOTAN_MINIMUM_SUPPORTED_BOOST_VERSION 107300
+
+   #if BOOST_VERSION >= BOTAN_MINIMUM_SUPPORTED_BOOST_VERSION
+
+      /**
+       * Indicates that the local boost and botan headers are compatible.
+       */
+      #define BOTAN_FOUND_COMPATIBLE_BOOST_VERSION 1
+
+   #endif
+
+#endif
+#endif
diff --git a/src/lib/utils/boost/info.txt b/src/lib/utils/boost/info.txt
index 261ffb651..aa5b11e89 100644
--- a/src/lib/utils/boost/info.txt
+++ b/src/lib/utils/boost/info.txt
@@ -8,6 +8,10 @@ name -> "Boost"
 
 load_on vendor
 
+<header:public>
+boost_compat.h
+</header:public>
+
 <libs>
 linux -> rt
 mingw -> ws2_32
diff --git a/src/lib/utils/os_utils.cpp b/src/lib/utils/os_utils.cpp
index 2ccdc2967..d3985ab49 100644
--- a/src/lib/utils/os_utils.cpp
+++ b/src/lib/utils/os_utils.cpp
@@ -630,7 +630,7 @@ void OS::set_thread_name(std::thread& thread, const std::string& name) {
    #elif defined(BOTAN_TARGET_OS_IS_OPENBSD)
    static_cast<void>(pthread_set_name_np(thread.native_handle(), name.c_str()));
    #elif defined(BOTAN_TARGET_OS_IS_NETBSD)
-   static_cast<void>(pthread_setname_np(thread.native_handle(), "%s", const_cast<char*>(name.c_str())));
+   static_cast<void>(pthread_set_name_np(thread.native_handle(), "%s", const_cast<char*>(name.c_str())));
    #elif defined(BOTAN_TARGET_OS_HAS_WIN32) && defined(BOTAN_BUILD_COMPILER_IS_MSVC)
    typedef HRESULT(WINAPI * std_proc)(HANDLE, PCWSTR);
    HMODULE kern = GetModuleHandleA("KernelBase.dll");


Test patch:
diff --git a/src/tests/test_tls_stream_integration.cpp b/src/tests/test_tls_stream_integration.cpp
index cfc9abfc1..cbc73a89d 100644
--- a/src/tests/test_tls_stream_integration.cpp
+++ b/src/tests/test_tls_stream_integration.cpp
@@ -11,8 +11,8 @@
 
 #if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_TLS_ASIO_STREAM) && defined(BOTAN_TARGET_OS_HAS_THREADS)
 
-   #include <boost/version.hpp>
-   #if BOOST_VERSION >= 107300
+   #include <botan/boost_compat.h>
+   #if defined(BOTAN_FOUND_COMPATIBLE_BOOST_VERSION)
 
       #include <functional>
       #include <memory>
@@ -835,5 +835,5 @@ BOTAN_REGISTER_TEST("tls", "tls_stream_integration", Tls_Stream_Integration_Test
 
 }  // namespace Botan_Tests
 
-   #endif  // BOOST_VERSION
-#endif     // BOTAN_HAS_TLS && BOTAN_HAS_BOOST_ASIO
+   #endif
+#endif  // BOTAN_HAS_TLS && BOTAN_HAS_BOOST_ASIO
diff --git a/src/tests/unit_asio_stream.cpp b/src/tests/unit_asio_stream.cpp
index 4961f2db1..1d3bfdc05 100644
--- a/src/tests/unit_asio_stream.cpp
+++ b/src/tests/unit_asio_stream.cpp
@@ -10,12 +10,12 @@
 
 #if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_TLS_ASIO_STREAM)
 
-   #include <botan/asio_stream.h>
-   #include <botan/tls_callbacks.h>
-   #include <botan/tls_session_manager_noop.h>
+   #include <botan/boost_compat.h>
+   #if defined(BOTAN_FOUND_COMPATIBLE_BOOST_VERSION)
 
-   #include <boost/version.hpp>
-   #if BOOST_VERSION >= 107300
+      #include <botan/asio_stream.h>
+      #include <botan/tls_callbacks.h>
+      #include <botan/tls_session_manager_noop.h>
 
       #include <boost/beast/_experimental/test/stream.hpp>
       #include <boost/bind.hpp>
@@ -736,5 +736,5 @@ BOTAN_REGISTER_TEST("tls", "tls_asio_stream", Asio_Stream_Tests);
 
 }  // namespace Botan_Tests
 
-   #endif  // BOOST_VERSION
-#endif     // BOTAN_HAS_TLS && BOTAN_HAS_BOOST_ASIO
+   #endif
+#endif  // BOTAN_HAS_TLS && BOTAN_HAS_TLS_ASIO_STREAM