Support h2c upgrade

h2c upgrade is a mechanism for supporting http/2 on connections that
might not support alpn [1].  This is done by the client specifying
Connection: upgrade
Upgrade: h2c

This looks very similar to a websocket upgrade, which h2c replacing
websocket.  Because of this, the existing upgrade code needs some
upgrades to avoid parsing twice.

Tested:
```
curl -u root:0penBmc --http2 -k http://192.168.7.2:443/redfish/v1/SessionService/Sessions
```

Succeeds and verbose logging shows that http upgrade succeeded

websocket_test.py in the scripts directory connects and reports events

[1] https://datatracker.ietf.org/doc/html/rfc7540#section-11.8

Change-Id: I8f76e355f99f21337d310ef2f345e6aaa253b48b
Signed-off-by: Ed Tanous <etanous@nvidia.com>
diff --git a/http/http2_connection.hpp b/http/http2_connection.hpp
index f0826d2..e982fb2 100644
--- a/http/http2_connection.hpp
+++ b/http/http2_connection.hpp
@@ -81,6 +81,26 @@
         doRead();
     }
 
+    void startFromSettings(std::string_view http2UpgradeSettings)
+    {
+        int ret = ngSession.sessionUpgrade2(http2UpgradeSettings,
+                                            false /*head_request*/);
+        if (ret != 0)
+        {
+            BMCWEB_LOG_ERROR("Failed to load upgrade header");
+            return;
+        }
+        // Create the control stream
+        streams[0];
+
+        if (sendServerConnectionHeader() != 0)
+        {
+            BMCWEB_LOG_ERROR("send_server_connection_header failed");
+            return;
+        }
+        doRead();
+    }
+
     int sendServerConnectionHeader()
     {
         BMCWEB_LOG_DEBUG("send_server_connection_header()");
diff --git a/http/http_connection.hpp b/http/http_connection.hpp
index 7069331..a6773bc 100644
--- a/http/http_connection.hpp
+++ b/http/http_connection.hpp
@@ -16,6 +16,7 @@
 #include "mutual_tls.hpp"
 #include "sessions.hpp"
 #include "str_utility.hpp"
+#include "utility.hpp"
 
 #include <boost/asio/error.hpp>
 #include <boost/asio/ip/tcp.hpp>
@@ -31,6 +32,7 @@
 #include <boost/beast/http/message_generator.hpp>
 #include <boost/beast/http/parser.hpp>
 #include <boost/beast/http/read.hpp>
+#include <boost/beast/http/rfc7230.hpp>
 #include <boost/beast/http/status.hpp>
 #include <boost/beast/http/verb.hpp>
 #include <boost/none.hpp>
@@ -254,6 +256,69 @@
         instance.body_limit(boost::none);
     }
 
+    // returns whether connection was upgraded
+    bool doUpgrade(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
+    {
+        using boost::beast::http::field;
+        using boost::beast::http::token_list;
+
+        bool isSse =
+            isContentTypeAllowed(req->getHeaderValue("Accept"),
+                                 http_helpers::ContentType::EventStream, false);
+
+        bool isWebsocket = false;
+        bool isH2c = false;
+        // Check connection header is upgrade
+        if (token_list{req->req[field::connection]}.exists("upgrade"))
+        {
+            BMCWEB_LOG_DEBUG("{} Connection: Upgrade header was present",
+                             logPtr(this));
+            // Parse if upgrade is h2c or websocket
+            token_list upgrade{req->req[field::upgrade]};
+            isWebsocket = upgrade.exists("websocket");
+            isH2c = upgrade.exists("h2c");
+            BMCWEB_LOG_DEBUG("{} Upgrade isWebsocket: {} isH2c: {}",
+                             logPtr(this), isWebsocket, isH2c);
+        }
+
+        if (BMCWEB_EXPERIMENTAL_HTTP2 && isH2c)
+        {
+            std::string_view base64settings = req->req[field::http2_settings];
+            if (utility::base64Decode<true>(base64settings, http2settings))
+            {
+                res.result(boost::beast::http::status::switching_protocols);
+                res.addHeader(boost::beast::http::field::connection, "Upgrade");
+                res.addHeader(boost::beast::http::field::upgrade, "h2c");
+            }
+        }
+
+        // websocket and SSE are only allowed on GET
+        if (req->req.method() == boost::beast::http::verb::get)
+        {
+            if (isWebsocket || isSse)
+            {
+                asyncResp->res.setCompleteRequestHandler(
+                    [self(shared_from_this())](crow::Response& thisRes) {
+                        if (thisRes.result() != boost::beast::http::status::ok)
+                        {
+                            // When any error occurs before handle upgradation,
+                            // the result in response will be set to respective
+                            // error. By default the Result will be OK (200),
+                            // which implies successful handle upgrade. Response
+                            // needs to be sent over this connection only on
+                            // failure.
+                            self->completeRequest(thisRes);
+                            return;
+                        }
+                    });
+                BMCWEB_LOG_INFO("{} Upgrading socket", logPtr(this));
+                handler->handleUpgrade(req, asyncResp, std::move(adaptor));
+                return true;
+            }
+        }
+        return false;
+    }
+
     void handle()
     {
         std::error_code reqEc;
@@ -323,30 +388,8 @@
             [self(shared_from_this())](crow::Response& thisRes) {
                 self->completeRequest(thisRes);
             });
-        bool isSse =
-            isContentTypeAllowed(req->getHeaderValue("Accept"),
-                                 http_helpers::ContentType::EventStream, false);
-        std::string_view upgradeType(
-            req->getHeaderValue(boost::beast::http::field::upgrade));
-        if ((req->isUpgrade() &&
-             bmcweb::asciiIEquals(upgradeType, "websocket")) ||
-            isSse)
+        if (doUpgrade(asyncResp))
         {
-            asyncResp->res.setCompleteRequestHandler(
-                [self(shared_from_this())](crow::Response& thisRes) {
-                    if (thisRes.result() != boost::beast::http::status::ok)
-                    {
-                        // When any error occurs before handle upgradation,
-                        // the result in response will be set to respective
-                        // error. By default the Result will be OK (200),
-                        // which implies successful handle upgrade. Response
-                        // needs to be sent over this connection only on
-                        // failure.
-                        self->completeRequest(thisRes);
-                        return;
-                    }
-                });
-            handler->handleUpgrade(req, asyncResp, std::move(adaptor));
             return;
         }
         std::string_view expected =
@@ -680,6 +723,14 @@
             return;
         }
 
+        if (res.result() == boost::beast::http::status::switching_protocols)
+        {
+            auto http2 = std::make_shared<HTTP2Connection<Adaptor, Handler>>(
+                std::move(adaptor), handler, getCachedDateStr);
+            http2->startFromSettings(http2settings);
+            return;
+        }
+
         if (res.result() == boost::beast::http::status::continue_)
         {
             // Reset the result to ok
@@ -797,7 +848,7 @@
 
     std::shared_ptr<crow::Request> req;
     std::string accept;
-
+    std::string http2settings;
     crow::Response res;
 
     std::shared_ptr<persistent_data::UserSession> userSession;
diff --git a/http/nghttp2_adapters.hpp b/http/nghttp2_adapters.hpp
index 8333b25..ff5e708 100644
--- a/http/nghttp2_adapters.hpp
+++ b/http/nghttp2_adapters.hpp
@@ -9,7 +9,9 @@
 
 #include "logging.hpp"
 
+#include <bit>
 #include <span>
+#include <string_view>
 
 /* This file contains RAII compatible adapters for nghttp2 structures.  They
  * attempt to be as close to a direct call as possible, while keeping the RAII
@@ -139,6 +141,14 @@
         return nghttp2_submit_settings(ptr, NGHTTP2_FLAG_NONE, iv.data(),
                                        iv.size());
     }
+
+    int sessionUpgrade2(std::string_view settingsPayload, bool headRequest)
+    {
+        return nghttp2_session_upgrade2(
+            ptr, std::bit_cast<uint8_t*>(settingsPayload.data()),
+            settingsPayload.size(), headRequest ? 1 : 0, nullptr);
+    }
+
     void setUserData(void* object)
     {
         nghttp2_session_set_user_data(ptr, object);