Unit test Connection

Boost asio provides a test stream object that we can use to begin unit
testing the connection object.  This patchset uses it to re-enable
some simple http1.1 tests.  There's some features that have snuck into
the connection class that aren't compatible with a stream (like ip
address getting), so unfortunately we do need the connection class to
be aware if it's in test mode, but that tradeoff seems worthwhile.

Tested: Unit test pass.

Change-Id: Id8b1f8866582b58502dbafe6139f841bf64b8ef3
Signed-off-by: Ed Tanous <edtanous@google.com>
diff --git a/http/app.hpp b/http/app.hpp
index d9c88b9..1a7af83 100644
--- a/http/app.hpp
+++ b/http/app.hpp
@@ -10,10 +10,8 @@
 
 #include <boost/asio/io_context.hpp>
 #include <boost/asio/ip/tcp.hpp>
-#ifdef BMCWEB_ENABLE_SSL
 #include <boost/asio/ssl/context.hpp>
 #include <boost/beast/ssl/ssl_stream.hpp>
-#endif
 
 #include <chrono>
 #include <cstdint>
@@ -29,19 +27,13 @@
 
 namespace crow
 {
-#ifdef BMCWEB_ENABLE_SSL
-using ssl_context_t = boost::asio::ssl::context;
-#endif
 class App
 {
   public:
-#ifdef BMCWEB_ENABLE_SSL
     using ssl_socket_t = boost::beast::ssl_stream<boost::asio::ip::tcp::socket>;
     using ssl_server_t = Server<App, ssl_socket_t>;
-#else
     using socket_t = boost::asio::ip::tcp::socket;
     using server_t = Server<App, socket_t>;
-#endif
 
     explicit App(std::shared_ptr<boost::asio::io_context> ioIn =
                      std::make_shared<boost::asio::io_context>()) :
@@ -94,12 +86,6 @@
         return *this;
     }
 
-    App& bindaddr(std::string bindaddr)
-    {
-        bindaddrStr = std::move(bindaddr);
-        return *this;
-    }
-
     void validate()
     {
         router.validate();
@@ -111,8 +97,8 @@
 #ifdef BMCWEB_ENABLE_SSL
         if (-1 == socketFd)
         {
-            sslServer = std::make_unique<ssl_server_t>(
-                this, bindaddrStr, portUint, sslContext, io);
+            sslServer = std::make_unique<ssl_server_t>(this, portUint,
+                                                       sslContext, io);
         }
         else
         {
@@ -125,8 +111,7 @@
 
         if (-1 == socketFd)
         {
-            server = std::make_unique<server_t>(this, bindaddrStr, portUint,
-                                                nullptr, io);
+            server = std::make_unique<server_t>(this, portUint, nullptr, io);
         }
         else
         {
@@ -158,7 +143,6 @@
         return router.getRoutes(parent);
     }
 
-#ifdef BMCWEB_ENABLE_SSL
     App& ssl(std::shared_ptr<boost::asio::ssl::context>&& ctx)
     {
         sslContext = std::move(ctx);
@@ -167,21 +151,7 @@
         return *this;
     }
 
-    std::shared_ptr<ssl_context_t> sslContext = nullptr;
-
-#else
-    template <typename T>
-    App& ssl(T&&)
-    {
-        // We can't call .ssl() member function unless BMCWEB_ENABLE_SSL is
-        // defined.
-        static_assert(
-            // make static_assert dependent to T; always false
-            std::is_base_of<T, void>::value,
-            "Define BMCWEB_ENABLE_SSL to enable ssl support.");
-        return *this;
-    }
-#endif
+    std::shared_ptr<boost::asio::ssl::context> sslContext = nullptr;
 
     boost::asio::io_context& ioContext()
     {
@@ -195,7 +165,6 @@
 #else
     uint16_t portUint = 80;
 #endif
-    std::string bindaddrStr = "0.0.0.0";
     int socketFd = -1;
     Router router;
 
diff --git a/http/http_connection.hpp b/http/http_connection.hpp
index 1ec80ae..c64d511 100644
--- a/http/http_connection.hpp
+++ b/http/http_connection.hpp
@@ -17,6 +17,7 @@
 #include <boost/asio/ip/tcp.hpp>
 #include <boost/asio/ssl/stream.hpp>
 #include <boost/asio/steady_timer.hpp>
+#include <boost/beast/_experimental/test/stream.hpp>
 #include <boost/beast/core/buffers_generator.hpp>
 #include <boost/beast/core/flat_static_buffer.hpp>
 #include <boost/beast/http/error.hpp>
@@ -44,6 +45,14 @@
 
 constexpr uint32_t httpHeaderLimit = 8192;
 
+template <typename>
+struct IsTls : std::false_type
+{};
+
+template <typename T>
+struct IsTls<boost::beast::ssl_stream<T>> : std::true_type
+{};
+
 template <typename Adaptor, typename Handler>
 class Connection :
     public std::enable_shared_from_this<Connection<Adaptor, Handler>>
@@ -112,31 +121,34 @@
 
     void prepareMutualTls()
     {
-        std::error_code error;
-        std::filesystem::path caPath(ensuressl::trustStorePath);
-        auto caAvailable = !std::filesystem::is_empty(caPath, error);
-        caAvailable = caAvailable && !error;
-        if (caAvailable && persistent_data::SessionStore::getInstance()
-                               .getAuthMethodsConfig()
-                               .tls)
+        if constexpr (IsTls<Adaptor>::value)
         {
-            adaptor.set_verify_mode(boost::asio::ssl::verify_peer);
-            std::string id = "bmcweb";
-
-            const char* cStr = id.c_str();
-            // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
-            const auto* idC = reinterpret_cast<const unsigned char*>(cStr);
-            int ret = SSL_set_session_id_context(
-                adaptor.native_handle(), idC,
-                static_cast<unsigned int>(id.length()));
-            if (ret == 0)
+            std::error_code error;
+            std::filesystem::path caPath(ensuressl::trustStorePath);
+            auto caAvailable = !std::filesystem::is_empty(caPath, error);
+            caAvailable = caAvailable && !error;
+            if (caAvailable && persistent_data::SessionStore::getInstance()
+                                   .getAuthMethodsConfig()
+                                   .tls)
             {
-                BMCWEB_LOG_ERROR("{} failed to set SSL id", logPtr(this));
-            }
-        }
+                adaptor.set_verify_mode(boost::asio::ssl::verify_peer);
+                std::string id = "bmcweb";
 
-        adaptor.set_verify_callback(
-            std::bind_front(&self_type::tlsVerifyCallback, this));
+                const char* cStr = id.c_str();
+                // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
+                const auto* idC = reinterpret_cast<const unsigned char*>(cStr);
+                int ret = SSL_set_session_id_context(
+                    adaptor.native_handle(), idC,
+                    static_cast<unsigned int>(id.length()));
+                if (ret == 0)
+                {
+                    BMCWEB_LOG_ERROR("{} failed to set SSL id", logPtr(this));
+                }
+            }
+
+            adaptor.set_verify_callback(
+                std::bind_front(&self_type::tlsVerifyCallback, this));
+        }
     }
 
     Adaptor& socket()
@@ -157,9 +169,7 @@
 
         // TODO(ed) Abstract this to a more clever class with the idea of an
         // asynchronous "start"
-        if constexpr (std::is_same_v<Adaptor,
-                                     boost::beast::ssl_stream<
-                                         boost::asio::ip::tcp::socket>>)
+        if constexpr (IsTls<Adaptor>::value)
         {
             adaptor.async_handshake(boost::asio::ssl::stream_base::server,
                                     [this, self(shared_from_this())](
@@ -252,20 +262,23 @@
             return;
         }
         keepAlive = thisReq.keepAlive();
-#ifndef BMCWEB_INSECURE_DISABLE_AUTHX
-        if (!crow::authentication::isOnAllowlist(req->url().path(),
-                                                 req->method()) &&
-            thisReq.session == nullptr)
+        if constexpr (!std::is_same_v<Adaptor, boost::beast::test::stream>)
         {
-            BMCWEB_LOG_WARNING("Authentication failed");
-            forward_unauthorized::sendUnauthorized(
-                req->url().encoded_path(),
-                req->getHeaderValue("X-Requested-With"),
-                req->getHeaderValue("Accept"), res);
-            completeRequest(res);
-            return;
-        }
+#ifndef BMCWEB_INSECURE_DISABLE_AUTHX
+            if (!crow::authentication::isOnAllowlist(req->url().path(),
+                                                     req->method()) &&
+                thisReq.session == nullptr)
+            {
+                BMCWEB_LOG_WARNING("Authentication failed");
+                forward_unauthorized::sendUnauthorized(
+                    req->url().encoded_path(),
+                    req->getHeaderValue("X-Requested-With"),
+                    req->getHeaderValue("Accept"), res);
+                completeRequest(res);
+                return;
+            }
 #endif // BMCWEB_INSECURE_DISABLE_AUTHX
+        }
         auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
         BMCWEB_LOG_DEBUG("Setting completion handler");
         asyncResp->res.setCompleteRequestHandler(
@@ -309,12 +322,14 @@
 
     bool isAlive()
     {
-        if constexpr (std::is_same_v<Adaptor,
-                                     boost::beast::ssl_stream<
-                                         boost::asio::ip::tcp::socket>>)
+        if constexpr (IsTls<Adaptor>::value)
         {
             return adaptor.next_layer().is_open();
         }
+        else if constexpr (std::is_same_v<Adaptor, boost::beast::test::stream>)
+        {
+            return true;
+        }
         else
         {
             return adaptor.is_open();
@@ -322,9 +337,7 @@
     }
     void close()
     {
-        if constexpr (std::is_same_v<Adaptor,
-                                     boost::beast::ssl_stream<
-                                         boost::asio::ip::tcp::socket>>)
+        if constexpr (IsTls<Adaptor>::value)
         {
             adaptor.next_layer().close();
             if (mtlsSession != nullptr)
@@ -384,18 +397,22 @@
     {
         boost::system::error_code ec;
         BMCWEB_LOG_DEBUG("Fetch the client IP address");
-        boost::asio::ip::tcp::endpoint endpoint =
-            boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec);
 
-        if (ec)
+        if constexpr (!std::is_same_v<Adaptor, boost::beast::test::stream>)
         {
-            // If remote endpoint fails keep going. "ClientOriginIPAddress"
-            // will be empty.
-            BMCWEB_LOG_ERROR("Failed to get the client's IP Address. ec : {}",
-                             ec);
-            return ec;
+            boost::asio::ip::tcp::endpoint endpoint =
+                boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec);
+
+            if (ec)
+            {
+                // If remote endpoint fails keep going. "ClientOriginIPAddress"
+                // will be empty.
+                BMCWEB_LOG_ERROR(
+                    "Failed to get the client's IP Address. ec : {}", ec);
+                return ec;
+            }
+            ip = endpoint.address();
         }
-        ip = endpoint.address();
         return ec;
     }
 
@@ -457,27 +474,31 @@
             {
                 BMCWEB_LOG_DEBUG("Unable to get client IP");
             }
-#ifndef BMCWEB_INSECURE_DISABLE_AUTHX
-            boost::beast::http::verb method = parser->get().method();
-            userSession = crow::authentication::authenticate(
-                ip, res, method, parser->get().base(), mtlsSession);
-
-            bool loggedIn = userSession != nullptr;
-            if (!loggedIn)
+            if constexpr (!std::is_same_v<Adaptor, boost::beast::test::stream>)
             {
-                const boost::optional<uint64_t> contentLength =
-                    parser->content_length();
-                if (contentLength && *contentLength > loggedOutPostBodyLimit)
-                {
-                    BMCWEB_LOG_DEBUG("Content length greater than limit {}",
-                                     *contentLength);
-                    close();
-                    return;
-                }
+#ifndef BMCWEB_INSECURE_DISABLE_AUTHX
+                boost::beast::http::verb method = parser->get().method();
+                userSession = crow::authentication::authenticate(
+                    ip, res, method, parser->get().base(), mtlsSession);
 
-                BMCWEB_LOG_DEBUG("Starting quick deadline");
-            }
+                bool loggedIn = userSession != nullptr;
+                if (!loggedIn)
+                {
+                    const boost::optional<uint64_t> contentLength =
+                        parser->content_length();
+                    if (contentLength &&
+                        *contentLength > loggedOutPostBodyLimit)
+                    {
+                        BMCWEB_LOG_DEBUG("Content length greater than limit {}",
+                                         *contentLength);
+                        close();
+                        return;
+                    }
+
+                    BMCWEB_LOG_DEBUG("Starting quick deadline");
+                }
 #endif // BMCWEB_INSECURE_DISABLE_AUTHX
+            }
 
             if (parser->is_done())
             {
diff --git a/http/http_server.hpp b/http/http_server.hpp
index b290ad7..2a6bd9f 100644
--- a/http/http_server.hpp
+++ b/http/http_server.hpp
@@ -38,14 +38,14 @@
         adaptorCtx(std::move(adaptorCtxIn))
     {}
 
-    Server(Handler* handlerIn, const std::string& bindaddr, uint16_t port,
+    Server(Handler* handlerIn, uint16_t port,
            const std::shared_ptr<boost::asio::ssl::context>& adaptorCtxIn,
            const std::shared_ptr<boost::asio::io_context>& io =
                std::make_shared<boost::asio::io_context>()) :
         Server(handlerIn,
                std::make_unique<boost::asio::ip::tcp::acceptor>(
                    *io, boost::asio::ip::tcp::endpoint(
-                            boost::asio::ip::make_address(bindaddr), port)),
+                            boost::asio::ip::make_address("0.0.0.0"), port)),
                adaptorCtxIn, io)
     {}