xref: /openbmc/bmcweb/http/http2_connection.hpp (revision ebe4c574caac9dfd8b2754bda51c0cf869f1978f)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3fca2cbeaSEd Tanous #pragma once
4fca2cbeaSEd Tanous #include "bmcweb_config.h"
5fca2cbeaSEd Tanous 
6fca2cbeaSEd Tanous #include "async_resp.hpp"
7fca2cbeaSEd Tanous #include "authentication.hpp"
8fca2cbeaSEd Tanous #include "complete_response_fields.hpp"
9d7857201SEd Tanous #include "forward_unauthorized.hpp"
10325310d3SEd Tanous #include "http_body.hpp"
11*ebe4c574SEd Tanous #include "http_connect_types.hpp"
12d7857201SEd Tanous #include "http_request.hpp"
13fca2cbeaSEd Tanous #include "http_response.hpp"
14fca2cbeaSEd Tanous #include "logging.hpp"
15fca2cbeaSEd Tanous 
16d7857201SEd Tanous // NOLINTNEXTLINE(misc-include-cleaner)
17d7857201SEd Tanous #include "nghttp2_adapters.hpp"
18d7857201SEd Tanous 
19d7857201SEd Tanous #include <nghttp2/nghttp2.h>
20d7857201SEd Tanous #include <unistd.h>
21d7857201SEd Tanous 
22d7857201SEd Tanous #include <boost/asio/buffer.hpp>
23fca2cbeaSEd Tanous #include <boost/asio/ssl/stream.hpp>
24d7857201SEd Tanous #include <boost/beast/core/error.hpp>
25d7857201SEd Tanous #include <boost/beast/http/field.hpp>
26d7857201SEd Tanous #include <boost/beast/http/fields.hpp>
27d7857201SEd Tanous #include <boost/beast/http/message.hpp>
28d7857201SEd Tanous #include <boost/beast/http/verb.hpp>
29d7857201SEd Tanous #include <boost/optional/optional.hpp>
30d0882189SEd Tanous #include <boost/system/error_code.hpp>
31fca2cbeaSEd Tanous 
32d0882189SEd Tanous #include <array>
33d7857201SEd Tanous #include <bit>
34d7857201SEd Tanous #include <cstddef>
35d7857201SEd Tanous #include <cstdint>
36d0882189SEd Tanous #include <functional>
37d7857201SEd Tanous #include <map>
38d0882189SEd Tanous #include <memory>
39d7857201SEd Tanous #include <optional>
40d7857201SEd Tanous #include <span>
4189cda63dSEd Tanous #include <string>
42d7857201SEd Tanous #include <string_view>
43796ba93bSEd Tanous #include <type_traits>
44d7857201SEd Tanous #include <utility>
45fca2cbeaSEd Tanous #include <vector>
46fca2cbeaSEd Tanous 
47fca2cbeaSEd Tanous namespace crow
48fca2cbeaSEd Tanous {
49fca2cbeaSEd Tanous 
50fca2cbeaSEd Tanous struct Http2StreamData
51fca2cbeaSEd Tanous {
52102a4cdaSJonathan Doman     std::shared_ptr<Request> req = std::make_shared<Request>();
53325310d3SEd Tanous     std::optional<bmcweb::HttpBody::reader> reqReader;
5489cda63dSEd Tanous     std::string accept;
5547f2934cSEd Tanous     Response res;
56b2896149SEd Tanous     std::optional<bmcweb::HttpBody::writer> writer;
57fca2cbeaSEd Tanous };
58fca2cbeaSEd Tanous 
59fca2cbeaSEd Tanous template <typename Adaptor, typename Handler>
60fca2cbeaSEd Tanous class HTTP2Connection :
61fca2cbeaSEd Tanous     public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>>
62fca2cbeaSEd Tanous {
63fca2cbeaSEd Tanous     using self_type = HTTP2Connection<Adaptor, Handler>;
64fca2cbeaSEd Tanous 
65fca2cbeaSEd Tanous   public:
HTTP2Connection(boost::asio::ssl::stream<Adaptor> && adaptorIn,Handler * handlerIn,std::function<std::string ()> & getCachedDateStrF,HttpType httpTypeIn)66*ebe4c574SEd Tanous     HTTP2Connection(boost::asio::ssl::stream<Adaptor>&& adaptorIn,
67*ebe4c574SEd Tanous                     Handler* handlerIn,
68*ebe4c574SEd Tanous                     std::function<std::string()>& getCachedDateStrF,
69*ebe4c574SEd Tanous                     HttpType httpTypeIn) :
70*ebe4c574SEd Tanous         httpType(httpTypeIn), adaptor(std::move(adaptorIn)),
71*ebe4c574SEd Tanous         ngSession(initializeNghttp2Session()), handler(handlerIn),
72*ebe4c574SEd Tanous         getCachedDateStr(getCachedDateStrF)
73fca2cbeaSEd Tanous     {}
74fca2cbeaSEd Tanous 
start()75fca2cbeaSEd Tanous     void start()
76fca2cbeaSEd Tanous     {
77fca2cbeaSEd Tanous         // Create the control stream
78f42e8590SEd Tanous         streams[0];
79fca2cbeaSEd Tanous 
80fca2cbeaSEd Tanous         if (sendServerConnectionHeader() != 0)
81fca2cbeaSEd Tanous         {
8262598e31SEd Tanous             BMCWEB_LOG_ERROR("send_server_connection_header failed");
83fca2cbeaSEd Tanous             return;
84fca2cbeaSEd Tanous         }
85fca2cbeaSEd Tanous         doRead();
86fca2cbeaSEd Tanous     }
87fca2cbeaSEd Tanous 
startFromSettings(std::string_view http2UpgradeSettings)88cd7dbb30SEd Tanous     void startFromSettings(std::string_view http2UpgradeSettings)
89cd7dbb30SEd Tanous     {
90cd7dbb30SEd Tanous         int ret = ngSession.sessionUpgrade2(http2UpgradeSettings,
91cd7dbb30SEd Tanous                                             false /*head_request*/);
92cd7dbb30SEd Tanous         if (ret != 0)
93cd7dbb30SEd Tanous         {
94cd7dbb30SEd Tanous             BMCWEB_LOG_ERROR("Failed to load upgrade header");
95cd7dbb30SEd Tanous             return;
96cd7dbb30SEd Tanous         }
97cd7dbb30SEd Tanous         // Create the control stream
98cd7dbb30SEd Tanous         streams[0];
99cd7dbb30SEd Tanous 
100cd7dbb30SEd Tanous         if (sendServerConnectionHeader() != 0)
101cd7dbb30SEd Tanous         {
102cd7dbb30SEd Tanous             BMCWEB_LOG_ERROR("send_server_connection_header failed");
103cd7dbb30SEd Tanous             return;
104cd7dbb30SEd Tanous         }
105cd7dbb30SEd Tanous         doRead();
106cd7dbb30SEd Tanous     }
107cd7dbb30SEd Tanous 
sendServerConnectionHeader()108fca2cbeaSEd Tanous     int sendServerConnectionHeader()
109fca2cbeaSEd Tanous     {
11062598e31SEd Tanous         BMCWEB_LOG_DEBUG("send_server_connection_header()");
111fca2cbeaSEd Tanous 
112fca2cbeaSEd Tanous         uint32_t maxStreams = 4;
113fca2cbeaSEd Tanous         std::array<nghttp2_settings_entry, 2> iv = {
114fca2cbeaSEd Tanous             {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams},
115fca2cbeaSEd Tanous              {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}}};
116fca2cbeaSEd Tanous         int rv = ngSession.submitSettings(iv);
117fca2cbeaSEd Tanous         if (rv != 0)
118fca2cbeaSEd Tanous         {
11962598e31SEd Tanous             BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
120fca2cbeaSEd Tanous             return -1;
121fca2cbeaSEd Tanous         }
122d0882189SEd Tanous         writeBuffer();
123fca2cbeaSEd Tanous         return 0;
124fca2cbeaSEd Tanous     }
125fca2cbeaSEd Tanous 
fileReadCallback(nghttp2_session *,int32_t streamId,uint8_t * buf,size_t length,uint32_t * dataFlags,nghttp2_data_source *,void * userPtr)126504af5a0SPatrick Williams     static ssize_t fileReadCallback(
127504af5a0SPatrick Williams         nghttp2_session* /* session */, int32_t streamId, uint8_t* buf,
128504af5a0SPatrick Williams         size_t length, uint32_t* dataFlags, nghttp2_data_source* /*source*/,
129504af5a0SPatrick Williams         void* userPtr)
130fca2cbeaSEd Tanous     {
131f42e8590SEd Tanous         self_type& self = userPtrToSelf(userPtr);
132f42e8590SEd Tanous 
133f42e8590SEd Tanous         auto streamIt = self.streams.find(streamId);
134f42e8590SEd Tanous         if (streamIt == self.streams.end())
135fca2cbeaSEd Tanous         {
136fca2cbeaSEd Tanous             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
137fca2cbeaSEd Tanous         }
138f42e8590SEd Tanous         Http2StreamData& stream = streamIt->second;
13962598e31SEd Tanous         BMCWEB_LOG_DEBUG("File read callback length: {}", length);
140d547d8d2SEd Tanous         if (!stream.writer)
141d547d8d2SEd Tanous         {
142d547d8d2SEd Tanous             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
143d547d8d2SEd Tanous         }
14452e31629SEd Tanous         boost::beast::error_code ec;
14552e31629SEd Tanous         boost::optional<std::pair<boost::asio::const_buffer, bool>> out =
14652e31629SEd Tanous             stream.writer->getWithMaxSize(ec, length);
14752e31629SEd Tanous         if (ec)
14827b0cf90SEd Tanous         {
149325310d3SEd Tanous             BMCWEB_LOG_CRITICAL("Failed to get buffer");
15027b0cf90SEd Tanous             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
15127b0cf90SEd Tanous         }
15252e31629SEd Tanous         if (!out)
153fca2cbeaSEd Tanous         {
154325310d3SEd Tanous             BMCWEB_LOG_ERROR("Empty file, setting EOF");
15552e31629SEd Tanous             *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
15652e31629SEd Tanous             return 0;
15752e31629SEd Tanous         }
15852e31629SEd Tanous 
15952e31629SEd Tanous         BMCWEB_LOG_DEBUG("Send chunk of size: {}", out->first.size());
16052e31629SEd Tanous         if (length < out->first.size())
16152e31629SEd Tanous         {
162325310d3SEd Tanous             BMCWEB_LOG_CRITICAL(
163325310d3SEd Tanous                 "Buffer overflow that should never happen happened");
16452e31629SEd Tanous             // Should never happen because of length limit on get() above
16552e31629SEd Tanous             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
16652e31629SEd Tanous         }
167325310d3SEd Tanous         boost::asio::mutable_buffer writeableBuf(buf, length);
16852e31629SEd Tanous         BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size());
169325310d3SEd Tanous         size_t copied = boost::asio::buffer_copy(writeableBuf, out->first);
170325310d3SEd Tanous         if (copied != out->first.size())
171325310d3SEd Tanous         {
172325310d3SEd Tanous             BMCWEB_LOG_ERROR(
173325310d3SEd Tanous                 "Couldn't copy all {} bytes into buffer, only copied {}",
174325310d3SEd Tanous                 out->first.size(), copied);
175325310d3SEd Tanous             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
176325310d3SEd Tanous         }
17752e31629SEd Tanous 
17852e31629SEd Tanous         if (!out->second)
17952e31629SEd Tanous         {
180325310d3SEd Tanous             BMCWEB_LOG_DEBUG("Setting EOF flag");
181fca2cbeaSEd Tanous             *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
182fca2cbeaSEd Tanous         }
183325310d3SEd Tanous         return static_cast<ssize_t>(copied);
184fca2cbeaSEd Tanous     }
185fca2cbeaSEd Tanous 
headerFromStringViews(std::string_view name,std::string_view value,uint8_t flags)186fca2cbeaSEd Tanous     nghttp2_nv headerFromStringViews(std::string_view name,
18752e31629SEd Tanous                                      std::string_view value, uint8_t flags)
188fca2cbeaSEd Tanous     {
189fca2cbeaSEd Tanous         uint8_t* nameData = std::bit_cast<uint8_t*>(name.data());
190fca2cbeaSEd Tanous         uint8_t* valueData = std::bit_cast<uint8_t*>(value.data());
19152e31629SEd Tanous         return {nameData, valueData, name.size(), value.size(), flags};
192fca2cbeaSEd Tanous     }
193fca2cbeaSEd Tanous 
sendResponse(Response & completedRes,int32_t streamId)194fca2cbeaSEd Tanous     int sendResponse(Response& completedRes, int32_t streamId)
195fca2cbeaSEd Tanous     {
19662598e31SEd Tanous         BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId);
197fca2cbeaSEd Tanous 
198fca2cbeaSEd Tanous         auto it = streams.find(streamId);
199fca2cbeaSEd Tanous         if (it == streams.end())
200fca2cbeaSEd Tanous         {
201fca2cbeaSEd Tanous             close();
202fca2cbeaSEd Tanous             return -1;
203fca2cbeaSEd Tanous         }
204499b5b4dSEd Tanous         Http2StreamData& stream = it->second;
205499b5b4dSEd Tanous         Response& res = stream.res;
206499b5b4dSEd Tanous         res = std::move(completedRes);
207499b5b4dSEd Tanous 
20889cda63dSEd Tanous         completeResponseFields(stream.accept, res);
209499b5b4dSEd Tanous         res.addHeader(boost::beast::http::field::date, getCachedDateStr());
210499b5b4dSEd Tanous         res.preparePayload();
211499b5b4dSEd Tanous 
212499b5b4dSEd Tanous         boost::beast::http::fields& fields = res.fields();
213499b5b4dSEd Tanous         std::string code = std::to_string(res.resultInt());
214fca2cbeaSEd Tanous         std::vector<nghttp2_nv> hdr;
21552e31629SEd Tanous         hdr.emplace_back(
21652e31629SEd Tanous             headerFromStringViews(":status", code, NGHTTP2_NV_FLAG_NONE));
217fca2cbeaSEd Tanous         for (const boost::beast::http::fields::value_type& header : fields)
218fca2cbeaSEd Tanous         {
21952e31629SEd Tanous             hdr.emplace_back(headerFromStringViews(
220d0882189SEd Tanous                 header.name_string(), header.value(), NGHTTP2_NV_FLAG_NONE));
221fca2cbeaSEd Tanous         }
222b2896149SEd Tanous         http::response<bmcweb::HttpBody>& fbody = res.response;
22352e31629SEd Tanous         stream.writer.emplace(fbody.base(), fbody.body());
224fca2cbeaSEd Tanous 
225fca2cbeaSEd Tanous         nghttp2_data_provider dataPrd{
226f42e8590SEd Tanous             .source = {.fd = 0},
227fca2cbeaSEd Tanous             .read_callback = fileReadCallback,
228fca2cbeaSEd Tanous         };
229fca2cbeaSEd Tanous 
230fca2cbeaSEd Tanous         int rv = ngSession.submitResponse(streamId, hdr, &dataPrd);
231fca2cbeaSEd Tanous         if (rv != 0)
232fca2cbeaSEd Tanous         {
23362598e31SEd Tanous             BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
234fca2cbeaSEd Tanous             close();
235fca2cbeaSEd Tanous             return -1;
236fca2cbeaSEd Tanous         }
237d0882189SEd Tanous         writeBuffer();
238fca2cbeaSEd Tanous 
239fca2cbeaSEd Tanous         return 0;
240fca2cbeaSEd Tanous     }
241fca2cbeaSEd Tanous 
initializeNghttp2Session()242fca2cbeaSEd Tanous     nghttp2_session initializeNghttp2Session()
243fca2cbeaSEd Tanous     {
244fca2cbeaSEd Tanous         nghttp2_session_callbacks callbacks;
245fca2cbeaSEd Tanous         callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic);
246fca2cbeaSEd Tanous         callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic);
247fca2cbeaSEd Tanous         callbacks.setOnHeaderCallback(onHeaderCallbackStatic);
248fca2cbeaSEd Tanous         callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic);
249325310d3SEd Tanous         callbacks.setOnDataChunkRecvCallback(onDataChunkRecvStatic);
250fca2cbeaSEd Tanous 
251fca2cbeaSEd Tanous         nghttp2_session session(callbacks);
252fca2cbeaSEd Tanous         session.setUserData(this);
253fca2cbeaSEd Tanous 
254fca2cbeaSEd Tanous         return session;
255fca2cbeaSEd Tanous     }
256fca2cbeaSEd Tanous 
onRequestRecv(int32_t streamId)257fca2cbeaSEd Tanous     int onRequestRecv(int32_t streamId)
258fca2cbeaSEd Tanous     {
25962598e31SEd Tanous         BMCWEB_LOG_DEBUG("on_request_recv");
260fca2cbeaSEd Tanous 
261fca2cbeaSEd Tanous         auto it = streams.find(streamId);
262fca2cbeaSEd Tanous         if (it == streams.end())
263fca2cbeaSEd Tanous         {
264fca2cbeaSEd Tanous             close();
265fca2cbeaSEd Tanous             return -1;
266fca2cbeaSEd Tanous         }
267d547d8d2SEd Tanous         auto& reqReader = it->second.reqReader;
268d547d8d2SEd Tanous         if (reqReader)
269325310d3SEd Tanous         {
270325310d3SEd Tanous             boost::beast::error_code ec;
271daadfb2eSEd Tanous             bmcweb::HttpBody::reader::finish(ec);
272325310d3SEd Tanous             if (ec)
273325310d3SEd Tanous             {
274325310d3SEd Tanous                 BMCWEB_LOG_CRITICAL("Failed to finalize payload");
275325310d3SEd Tanous                 close();
276325310d3SEd Tanous                 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
277325310d3SEd Tanous             }
278325310d3SEd Tanous         }
279102a4cdaSJonathan Doman         crow::Request& thisReq = *it->second.req;
28089cda63dSEd Tanous         it->second.accept = thisReq.getHeaderValue("Accept");
28189cda63dSEd Tanous 
28262598e31SEd Tanous         BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq),
28362598e31SEd Tanous                          thisReq.url().encoded_path());
284fca2cbeaSEd Tanous 
285f42e8590SEd Tanous         crow::Response& thisRes = it->second.res;
286fca2cbeaSEd Tanous 
287fca2cbeaSEd Tanous         thisRes.setCompleteRequestHandler(
288fca2cbeaSEd Tanous             [this, streamId](Response& completeRes) {
28962598e31SEd Tanous                 BMCWEB_LOG_DEBUG("res.completeRequestHandler called");
290fca2cbeaSEd Tanous                 if (sendResponse(completeRes, streamId) != 0)
291fca2cbeaSEd Tanous                 {
292fca2cbeaSEd Tanous                     close();
293fca2cbeaSEd Tanous                     return;
294fca2cbeaSEd Tanous                 }
295fca2cbeaSEd Tanous             });
296fca2cbeaSEd Tanous         auto asyncResp =
297f42e8590SEd Tanous             std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res));
29883328316SEd Tanous         if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH)
29983328316SEd Tanous         {
300325310d3SEd Tanous             thisReq.session = crow::authentication::authenticate(
30152c15028SEd Tanous                 {}, asyncResp->res, thisReq.method(), thisReq.req, nullptr);
302325310d3SEd Tanous             if (!crow::authentication::isOnAllowlist(thisReq.url().path(),
303325310d3SEd Tanous                                                      thisReq.method()) &&
304325310d3SEd Tanous                 thisReq.session == nullptr)
305325310d3SEd Tanous             {
306325310d3SEd Tanous                 BMCWEB_LOG_WARNING("Authentication failed");
307325310d3SEd Tanous                 forward_unauthorized::sendUnauthorized(
308325310d3SEd Tanous                     thisReq.url().encoded_path(),
309325310d3SEd Tanous                     thisReq.getHeaderValue("X-Requested-With"),
31052c15028SEd Tanous                     thisReq.getHeaderValue("Accept"), asyncResp->res);
31183328316SEd Tanous                 return 0;
312325310d3SEd Tanous             }
31383328316SEd Tanous         }
31483328316SEd Tanous         std::string_view expected =
31583328316SEd Tanous             thisReq.getHeaderValue(boost::beast::http::field::if_none_match);
316499b5b4dSEd Tanous         BMCWEB_LOG_DEBUG("Setting expected hash {}", expected);
317499b5b4dSEd Tanous         if (!expected.empty())
318499b5b4dSEd Tanous         {
319499b5b4dSEd Tanous             asyncResp->res.setExpectedHash(expected);
320499b5b4dSEd Tanous         }
321102a4cdaSJonathan Doman         handler->handle(it->second.req, asyncResp);
322fca2cbeaSEd Tanous         return 0;
323fca2cbeaSEd Tanous     }
324fca2cbeaSEd Tanous 
onDataChunkRecvCallback(uint8_t,int32_t streamId,const uint8_t * data,size_t len)325325310d3SEd Tanous     int onDataChunkRecvCallback(uint8_t /*flags*/, int32_t streamId,
326325310d3SEd Tanous                                 const uint8_t* data, size_t len)
327325310d3SEd Tanous     {
328325310d3SEd Tanous         auto thisStream = streams.find(streamId);
329325310d3SEd Tanous         if (thisStream == streams.end())
330325310d3SEd Tanous         {
331325310d3SEd Tanous             BMCWEB_LOG_ERROR("Unknown stream{}", streamId);
332325310d3SEd Tanous             close();
333325310d3SEd Tanous             return -1;
334325310d3SEd Tanous         }
335d547d8d2SEd Tanous 
336d547d8d2SEd Tanous         std::optional<bmcweb::HttpBody::reader>& reqReader =
337d547d8d2SEd Tanous             thisStream->second.reqReader;
338d547d8d2SEd Tanous         if (!reqReader)
339325310d3SEd Tanous         {
3408e5cc7bdSEd Tanous             reqReader.emplace(
341102a4cdaSJonathan Doman                 bmcweb::HttpBody::reader(thisStream->second.req->req.base(),
342102a4cdaSJonathan Doman                                          thisStream->second.req->req.body()));
343325310d3SEd Tanous         }
344325310d3SEd Tanous         boost::beast::error_code ec;
345d547d8d2SEd Tanous         reqReader->put(boost::asio::const_buffer(data, len), ec);
346325310d3SEd Tanous         if (ec)
347325310d3SEd Tanous         {
348325310d3SEd Tanous             BMCWEB_LOG_CRITICAL("Failed to write payload");
349325310d3SEd Tanous             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
350325310d3SEd Tanous         }
351325310d3SEd Tanous         return 0;
352325310d3SEd Tanous     }
353325310d3SEd Tanous 
onDataChunkRecvStatic(nghttp2_session *,uint8_t flags,int32_t streamId,const uint8_t * data,size_t len,void * userData)354bd79bce8SPatrick Williams     static int onDataChunkRecvStatic(
355bd79bce8SPatrick Williams         nghttp2_session* /* session */, uint8_t flags, int32_t streamId,
356bd79bce8SPatrick Williams         const uint8_t* data, size_t len, void* userData)
357325310d3SEd Tanous     {
358325310d3SEd Tanous         BMCWEB_LOG_DEBUG("on_frame_recv_callback");
359325310d3SEd Tanous         if (userData == nullptr)
360325310d3SEd Tanous         {
361325310d3SEd Tanous             BMCWEB_LOG_CRITICAL("user data was null?");
362325310d3SEd Tanous             return NGHTTP2_ERR_CALLBACK_FAILURE;
363325310d3SEd Tanous         }
364bd79bce8SPatrick Williams         return userPtrToSelf(userData).onDataChunkRecvCallback(
365bd79bce8SPatrick Williams             flags, streamId, data, len);
366325310d3SEd Tanous     }
367325310d3SEd Tanous 
onFrameRecvCallback(const nghttp2_frame & frame)368fca2cbeaSEd Tanous     int onFrameRecvCallback(const nghttp2_frame& frame)
369fca2cbeaSEd Tanous     {
37062598e31SEd Tanous         BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type));
371fca2cbeaSEd Tanous         switch (frame.hd.type)
372fca2cbeaSEd Tanous         {
373fca2cbeaSEd Tanous             case NGHTTP2_DATA:
374fca2cbeaSEd Tanous             case NGHTTP2_HEADERS:
375fca2cbeaSEd Tanous                 // Check that the client request has finished
376fca2cbeaSEd Tanous                 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0)
377fca2cbeaSEd Tanous                 {
378fca2cbeaSEd Tanous                     return onRequestRecv(frame.hd.stream_id);
379fca2cbeaSEd Tanous                 }
380fca2cbeaSEd Tanous                 break;
381fca2cbeaSEd Tanous             default:
382fca2cbeaSEd Tanous                 break;
383fca2cbeaSEd Tanous         }
384fca2cbeaSEd Tanous         return 0;
385fca2cbeaSEd Tanous     }
386fca2cbeaSEd Tanous 
onFrameRecvCallbackStatic(nghttp2_session *,const nghttp2_frame * frame,void * userData)387fca2cbeaSEd Tanous     static int onFrameRecvCallbackStatic(nghttp2_session* /* session */,
388fca2cbeaSEd Tanous                                          const nghttp2_frame* frame,
389fca2cbeaSEd Tanous                                          void* userData)
390fca2cbeaSEd Tanous     {
39162598e31SEd Tanous         BMCWEB_LOG_DEBUG("on_frame_recv_callback");
392fca2cbeaSEd Tanous         if (userData == nullptr)
393fca2cbeaSEd Tanous         {
39462598e31SEd Tanous             BMCWEB_LOG_CRITICAL("user data was null?");
395fca2cbeaSEd Tanous             return NGHTTP2_ERR_CALLBACK_FAILURE;
396fca2cbeaSEd Tanous         }
397fca2cbeaSEd Tanous         if (frame == nullptr)
398fca2cbeaSEd Tanous         {
39962598e31SEd Tanous             BMCWEB_LOG_CRITICAL("frame was null?");
400fca2cbeaSEd Tanous             return NGHTTP2_ERR_CALLBACK_FAILURE;
401fca2cbeaSEd Tanous         }
402fca2cbeaSEd Tanous         return userPtrToSelf(userData).onFrameRecvCallback(*frame);
403fca2cbeaSEd Tanous     }
404fca2cbeaSEd Tanous 
userPtrToSelf(void * userData)405fca2cbeaSEd Tanous     static self_type& userPtrToSelf(void* userData)
406fca2cbeaSEd Tanous     {
407fca2cbeaSEd Tanous         // This method exists to keep the unsafe reinterpret cast in one
408fca2cbeaSEd Tanous         // place.
409fca2cbeaSEd Tanous         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
410fca2cbeaSEd Tanous         return *reinterpret_cast<self_type*>(userData);
411fca2cbeaSEd Tanous     }
412fca2cbeaSEd Tanous 
onStreamCloseCallbackStatic(nghttp2_session *,int32_t streamId,uint32_t,void * userData)413fca2cbeaSEd Tanous     static int onStreamCloseCallbackStatic(nghttp2_session* /* session */,
414fca2cbeaSEd Tanous                                            int32_t streamId,
415fca2cbeaSEd Tanous                                            uint32_t /*unused*/, void* userData)
416fca2cbeaSEd Tanous     {
41762598e31SEd Tanous         BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId);
418fca2cbeaSEd Tanous         if (userData == nullptr)
419fca2cbeaSEd Tanous         {
42062598e31SEd Tanous             BMCWEB_LOG_CRITICAL("user data was null?");
421fca2cbeaSEd Tanous             return NGHTTP2_ERR_CALLBACK_FAILURE;
422fca2cbeaSEd Tanous         }
423f42e8590SEd Tanous         if (userPtrToSelf(userData).streams.erase(streamId) <= 0)
424fca2cbeaSEd Tanous         {
425fca2cbeaSEd Tanous             return -1;
426fca2cbeaSEd Tanous         }
427fca2cbeaSEd Tanous         return 0;
428fca2cbeaSEd Tanous     }
429fca2cbeaSEd Tanous 
onHeaderCallback(const nghttp2_frame & frame,std::span<const uint8_t> name,std::span<const uint8_t> value)430fca2cbeaSEd Tanous     int onHeaderCallback(const nghttp2_frame& frame,
431fca2cbeaSEd Tanous                          std::span<const uint8_t> name,
432fca2cbeaSEd Tanous                          std::span<const uint8_t> value)
433fca2cbeaSEd Tanous     {
434fca2cbeaSEd Tanous         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
435fca2cbeaSEd Tanous         std::string_view nameSv(reinterpret_cast<const char*>(name.data()),
436fca2cbeaSEd Tanous                                 name.size());
437fca2cbeaSEd Tanous         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
438fca2cbeaSEd Tanous         std::string_view valueSv(reinterpret_cast<const char*>(value.data()),
439fca2cbeaSEd Tanous                                  value.size());
440fca2cbeaSEd Tanous 
44162598e31SEd Tanous         BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv,
44262598e31SEd Tanous                          valueSv);
443a07e9819SEd Tanous         if (frame.hd.type != NGHTTP2_HEADERS)
444fca2cbeaSEd Tanous         {
445a07e9819SEd Tanous             return 0;
446a07e9819SEd Tanous         }
447fca2cbeaSEd Tanous         if (frame.headers.cat != NGHTTP2_HCAT_REQUEST)
448fca2cbeaSEd Tanous         {
449a07e9819SEd Tanous             return 0;
450fca2cbeaSEd Tanous         }
451fca2cbeaSEd Tanous         auto thisStream = streams.find(frame.hd.stream_id);
452fca2cbeaSEd Tanous         if (thisStream == streams.end())
453fca2cbeaSEd Tanous         {
45462598e31SEd Tanous             BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id);
455fca2cbeaSEd Tanous             close();
456fca2cbeaSEd Tanous             return -1;
457fca2cbeaSEd Tanous         }
458fca2cbeaSEd Tanous 
459102a4cdaSJonathan Doman         crow::Request& thisReq = *thisStream->second.req;
460fca2cbeaSEd Tanous 
461fca2cbeaSEd Tanous         if (nameSv == ":path")
462fca2cbeaSEd Tanous         {
463fca2cbeaSEd Tanous             thisReq.target(valueSv);
464fca2cbeaSEd Tanous         }
465fca2cbeaSEd Tanous         else if (nameSv == ":method")
466fca2cbeaSEd Tanous         {
467fca2cbeaSEd Tanous             boost::beast::http::verb verb =
468fca2cbeaSEd Tanous                 boost::beast::http::string_to_verb(valueSv);
469fca2cbeaSEd Tanous             if (verb == boost::beast::http::verb::unknown)
470fca2cbeaSEd Tanous             {
47162598e31SEd Tanous                 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv);
47250bfc917SEd Tanous                 verb = boost::beast::http::verb::trace;
473fca2cbeaSEd Tanous             }
4741873a04fSMyung Bae             thisReq.method(verb);
475fca2cbeaSEd Tanous         }
476fca2cbeaSEd Tanous         else if (nameSv == ":scheme")
477fca2cbeaSEd Tanous         {
478fca2cbeaSEd Tanous             // Nothing to check on scheme
479fca2cbeaSEd Tanous         }
480fca2cbeaSEd Tanous         else
481fca2cbeaSEd Tanous         {
4821873a04fSMyung Bae             thisReq.addHeader(nameSv, valueSv);
483fca2cbeaSEd Tanous         }
484fca2cbeaSEd Tanous         return 0;
485fca2cbeaSEd Tanous     }
486fca2cbeaSEd Tanous 
onHeaderCallbackStatic(nghttp2_session *,const nghttp2_frame * frame,const uint8_t * name,size_t namelen,const uint8_t * value,size_t vallen,uint8_t,void * userData)487bd79bce8SPatrick Williams     static int onHeaderCallbackStatic(
488bd79bce8SPatrick Williams         nghttp2_session* /* session */, const nghttp2_frame* frame,
489bd79bce8SPatrick Williams         const uint8_t* name, size_t namelen, const uint8_t* value,
490bd79bce8SPatrick Williams         size_t vallen, uint8_t /* flags */, void* userData)
491fca2cbeaSEd Tanous     {
492fca2cbeaSEd Tanous         if (userData == nullptr)
493fca2cbeaSEd Tanous         {
49462598e31SEd Tanous             BMCWEB_LOG_CRITICAL("user data was null?");
495fca2cbeaSEd Tanous             return NGHTTP2_ERR_CALLBACK_FAILURE;
496fca2cbeaSEd Tanous         }
497fca2cbeaSEd Tanous         if (frame == nullptr)
498fca2cbeaSEd Tanous         {
49962598e31SEd Tanous             BMCWEB_LOG_CRITICAL("frame was null?");
500fca2cbeaSEd Tanous             return NGHTTP2_ERR_CALLBACK_FAILURE;
501fca2cbeaSEd Tanous         }
502fca2cbeaSEd Tanous         if (name == nullptr)
503fca2cbeaSEd Tanous         {
50462598e31SEd Tanous             BMCWEB_LOG_CRITICAL("name was null?");
505fca2cbeaSEd Tanous             return NGHTTP2_ERR_CALLBACK_FAILURE;
506fca2cbeaSEd Tanous         }
507fca2cbeaSEd Tanous         if (value == nullptr)
508fca2cbeaSEd Tanous         {
50962598e31SEd Tanous             BMCWEB_LOG_CRITICAL("value was null?");
510fca2cbeaSEd Tanous             return NGHTTP2_ERR_CALLBACK_FAILURE;
511fca2cbeaSEd Tanous         }
512fca2cbeaSEd Tanous         return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen},
513fca2cbeaSEd Tanous                                                         {value, vallen});
514fca2cbeaSEd Tanous     }
515fca2cbeaSEd Tanous 
onBeginHeadersCallback(const nghttp2_frame & frame)516fca2cbeaSEd Tanous     int onBeginHeadersCallback(const nghttp2_frame& frame)
517fca2cbeaSEd Tanous     {
518fca2cbeaSEd Tanous         if (frame.hd.type == NGHTTP2_HEADERS &&
519fca2cbeaSEd Tanous             frame.headers.cat == NGHTTP2_HCAT_REQUEST)
520fca2cbeaSEd Tanous         {
52162598e31SEd Tanous             BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id);
522fca2cbeaSEd Tanous 
5233a499414SEd Tanous             streams.emplace(frame.hd.stream_id, Http2StreamData());
524fca2cbeaSEd Tanous         }
525fca2cbeaSEd Tanous         return 0;
526fca2cbeaSEd Tanous     }
527fca2cbeaSEd Tanous 
onBeginHeadersCallbackStatic(nghttp2_session *,const nghttp2_frame * frame,void * userData)528fca2cbeaSEd Tanous     static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */,
529fca2cbeaSEd Tanous                                             const nghttp2_frame* frame,
530fca2cbeaSEd Tanous                                             void* userData)
531fca2cbeaSEd Tanous     {
53262598e31SEd Tanous         BMCWEB_LOG_DEBUG("on_begin_headers_callback");
533fca2cbeaSEd Tanous         if (userData == nullptr)
534fca2cbeaSEd Tanous         {
53562598e31SEd Tanous             BMCWEB_LOG_CRITICAL("user data was null?");
536fca2cbeaSEd Tanous             return NGHTTP2_ERR_CALLBACK_FAILURE;
537fca2cbeaSEd Tanous         }
538fca2cbeaSEd Tanous         if (frame == nullptr)
539fca2cbeaSEd Tanous         {
54062598e31SEd Tanous             BMCWEB_LOG_CRITICAL("frame was null?");
541fca2cbeaSEd Tanous             return NGHTTP2_ERR_CALLBACK_FAILURE;
542fca2cbeaSEd Tanous         }
543fca2cbeaSEd Tanous         return userPtrToSelf(userData).onBeginHeadersCallback(*frame);
544fca2cbeaSEd Tanous     }
545fca2cbeaSEd Tanous 
afterWriteBuffer(const std::shared_ptr<self_type> & self,const boost::system::error_code & ec,size_t sendLength)546fca2cbeaSEd Tanous     static void afterWriteBuffer(const std::shared_ptr<self_type>& self,
547fca2cbeaSEd Tanous                                  const boost::system::error_code& ec,
548fca2cbeaSEd Tanous                                  size_t sendLength)
549fca2cbeaSEd Tanous     {
550fca2cbeaSEd Tanous         self->isWriting = false;
55162598e31SEd Tanous         BMCWEB_LOG_DEBUG("Sent {}", sendLength);
552fca2cbeaSEd Tanous         if (ec)
553fca2cbeaSEd Tanous         {
554fca2cbeaSEd Tanous             self->close();
555fca2cbeaSEd Tanous             return;
556fca2cbeaSEd Tanous         }
557fca2cbeaSEd Tanous         self->writeBuffer();
558fca2cbeaSEd Tanous     }
559fca2cbeaSEd Tanous 
writeBuffer()560fca2cbeaSEd Tanous     void writeBuffer()
561fca2cbeaSEd Tanous     {
562fca2cbeaSEd Tanous         if (isWriting)
563fca2cbeaSEd Tanous         {
564fca2cbeaSEd Tanous             return;
565fca2cbeaSEd Tanous         }
566d0882189SEd Tanous         std::span<const uint8_t> data = ngSession.memSend();
567d0882189SEd Tanous         if (data.empty())
568fca2cbeaSEd Tanous         {
569fca2cbeaSEd Tanous             return;
570fca2cbeaSEd Tanous         }
571fca2cbeaSEd Tanous         isWriting = true;
572*ebe4c574SEd Tanous         if (httpType == HttpType::HTTPS)
573*ebe4c574SEd Tanous         {
574325310d3SEd Tanous             boost::asio::async_write(
575325310d3SEd Tanous                 adaptor, boost::asio::const_buffer(data.data(), data.size()),
576fca2cbeaSEd Tanous                 std::bind_front(afterWriteBuffer, shared_from_this()));
577fca2cbeaSEd Tanous         }
578*ebe4c574SEd Tanous         else if (httpType == HttpType::HTTP)
579*ebe4c574SEd Tanous         {
580*ebe4c574SEd Tanous             boost::asio::async_write(
581*ebe4c574SEd Tanous                 adaptor.next_layer(),
582*ebe4c574SEd Tanous                 boost::asio::const_buffer(data.data(), data.size()),
583*ebe4c574SEd Tanous                 std::bind_front(afterWriteBuffer, shared_from_this()));
584*ebe4c574SEd Tanous         }
585*ebe4c574SEd Tanous     }
586fca2cbeaSEd Tanous 
close()587fca2cbeaSEd Tanous     void close()
588fca2cbeaSEd Tanous     {
589fca2cbeaSEd Tanous         adaptor.next_layer().close();
590fca2cbeaSEd Tanous     }
591fca2cbeaSEd Tanous 
afterDoRead(const std::shared_ptr<self_type> &,const boost::system::error_code & ec,size_t bytesTransferred)592d0882189SEd Tanous     void afterDoRead(const std::shared_ptr<self_type>& /*self*/,
593d0882189SEd Tanous                      const boost::system::error_code& ec,
594d0882189SEd Tanous                      size_t bytesTransferred)
595fca2cbeaSEd Tanous     {
59662598e31SEd Tanous         BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
59762598e31SEd Tanous                          bytesTransferred);
598fca2cbeaSEd Tanous 
599fca2cbeaSEd Tanous         if (ec)
600fca2cbeaSEd Tanous         {
60162598e31SEd Tanous             BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
60262598e31SEd Tanous                              ec.message());
603fca2cbeaSEd Tanous             close();
60462598e31SEd Tanous             BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this));
605fca2cbeaSEd Tanous             return;
606fca2cbeaSEd Tanous         }
607d0882189SEd Tanous         std::span<uint8_t> bufferSpan{inBuffer.data(), bytesTransferred};
608d0882189SEd Tanous 
609fca2cbeaSEd Tanous         ssize_t readLen = ngSession.memRecv(bufferSpan);
610d0882189SEd Tanous         if (readLen < 0)
611fca2cbeaSEd Tanous         {
612d0882189SEd Tanous             BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", readLen);
613fca2cbeaSEd Tanous             close();
614fca2cbeaSEd Tanous             return;
615fca2cbeaSEd Tanous         }
616d0882189SEd Tanous         writeBuffer();
617fca2cbeaSEd Tanous 
618fca2cbeaSEd Tanous         doRead();
619d0882189SEd Tanous     }
620d0882189SEd Tanous 
doRead()621d0882189SEd Tanous     void doRead()
622d0882189SEd Tanous     {
623d0882189SEd Tanous         BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
624*ebe4c574SEd Tanous         if (httpType == HttpType::HTTPS)
625*ebe4c574SEd Tanous         {
626*ebe4c574SEd Tanous             adaptor.async_read_some(boost::asio::buffer(inBuffer),
627*ebe4c574SEd Tanous                                     std::bind_front(&self_type::afterDoRead,
628*ebe4c574SEd Tanous                                                     this, shared_from_this()));
629*ebe4c574SEd Tanous         }
630*ebe4c574SEd Tanous         else if (httpType == HttpType::HTTP)
631*ebe4c574SEd Tanous         {
632*ebe4c574SEd Tanous             adaptor.next_layer().async_read_some(
633d0882189SEd Tanous                 boost::asio::buffer(inBuffer),
634*ebe4c574SEd Tanous                 std::bind_front(&self_type::afterDoRead, this,
635*ebe4c574SEd Tanous                                 shared_from_this()));
636*ebe4c574SEd Tanous         }
637fca2cbeaSEd Tanous     }
638fca2cbeaSEd Tanous 
639fca2cbeaSEd Tanous     // A mapping from http2 stream ID to Stream Data
64052e31629SEd Tanous     std::map<int32_t, Http2StreamData> streams;
641fca2cbeaSEd Tanous 
642d0882189SEd Tanous     std::array<uint8_t, 8192> inBuffer{};
643fca2cbeaSEd Tanous 
644*ebe4c574SEd Tanous     HttpType httpType = HttpType::BOTH;
645*ebe4c574SEd Tanous     boost::asio::ssl::stream<Adaptor> adaptor;
646fca2cbeaSEd Tanous     bool isWriting = false;
647fca2cbeaSEd Tanous 
648fca2cbeaSEd Tanous     nghttp2_session ngSession;
649fca2cbeaSEd Tanous 
650fca2cbeaSEd Tanous     Handler* handler;
651fca2cbeaSEd Tanous     std::function<std::string()>& getCachedDateStr;
652fca2cbeaSEd Tanous 
653fca2cbeaSEd Tanous     using std::enable_shared_from_this<
654fca2cbeaSEd Tanous         HTTP2Connection<Adaptor, Handler>>::shared_from_this;
655fca2cbeaSEd Tanous 
656fca2cbeaSEd Tanous     using std::enable_shared_from_this<
657fca2cbeaSEd Tanous         HTTP2Connection<Adaptor, Handler>>::weak_from_this;
658fca2cbeaSEd Tanous };
659fca2cbeaSEd Tanous } // namespace crow
660