xref: /openbmc/bmcweb/http/http2_connection.hpp (revision b84653e71c342f965da5a631b8d2a4766a1606a7)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 #include "bmcweb_config.h"
5 
6 #include "async_resp.hpp"
7 #include "authentication.hpp"
8 #include "complete_response_fields.hpp"
9 #include "forward_unauthorized.hpp"
10 #include "http_body.hpp"
11 #include "http_connect_types.hpp"
12 #include "http_request.hpp"
13 #include "http_response.hpp"
14 #include "logging.hpp"
15 
16 // NOLINTNEXTLINE(misc-include-cleaner)
17 #include "nghttp2_adapters.hpp"
18 #include "sessions.hpp"
19 
20 #include <nghttp2/nghttp2.h>
21 #include <unistd.h>
22 
23 #include <boost/asio/buffer.hpp>
24 #include <boost/asio/ssl/stream.hpp>
25 #include <boost/beast/core/error.hpp>
26 #include <boost/beast/http/field.hpp>
27 #include <boost/beast/http/fields.hpp>
28 #include <boost/beast/http/message.hpp>
29 #include <boost/beast/http/verb.hpp>
30 #include <boost/optional/optional.hpp>
31 #include <boost/system/error_code.hpp>
32 #include <boost/url/url_view.hpp>
33 
34 #include <array>
35 #include <bit>
36 #include <cstddef>
37 #include <cstdint>
38 #include <functional>
39 #include <map>
40 #include <memory>
41 #include <optional>
42 #include <span>
43 #include <string>
44 #include <string_view>
45 #include <type_traits>
46 #include <utility>
47 #include <vector>
48 
49 namespace crow
50 {
51 
52 struct Http2StreamData
53 {
54     std::shared_ptr<Request> req = std::make_shared<Request>();
55     std::optional<bmcweb::HttpBody::reader> reqReader;
56     std::string accept;
57     std::string acceptEnc;
58     Response res;
59     std::optional<bmcweb::HttpBody::writer> writer;
60 };
61 
62 template <typename Adaptor, typename Handler>
63 class HTTP2Connection :
64     public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>>
65 {
66     using self_type = HTTP2Connection<Adaptor, Handler>;
67 
68   public:
HTTP2Connection(boost::asio::ssl::stream<Adaptor> && adaptorIn,Handler * handlerIn,std::function<std::string ()> & getCachedDateStrF,HttpType httpTypeIn,const std::shared_ptr<persistent_data::UserSession> & mtlsSessionIn)69     HTTP2Connection(
70         boost::asio::ssl::stream<Adaptor>&& adaptorIn, Handler* handlerIn,
71         std::function<std::string()>& getCachedDateStrF, HttpType httpTypeIn,
72         const std::shared_ptr<persistent_data::UserSession>& mtlsSessionIn) :
73         httpType(httpTypeIn), adaptor(std::move(adaptorIn)),
74         ngSession(initializeNghttp2Session()), handler(handlerIn),
75         getCachedDateStr(getCachedDateStrF), mtlsSession(mtlsSessionIn)
76     {}
77 
start()78     void start()
79     {
80         // Create the control stream
81         streams[0];
82 
83         if (sendServerConnectionHeader() != 0)
84         {
85             BMCWEB_LOG_ERROR("send_server_connection_header failed");
86             return;
87         }
88         doRead();
89     }
90 
startFromSettings(std::string_view http2UpgradeSettings)91     void startFromSettings(std::string_view http2UpgradeSettings)
92     {
93         int ret = ngSession.sessionUpgrade2(http2UpgradeSettings,
94                                             false /*head_request*/);
95         if (ret != 0)
96         {
97             BMCWEB_LOG_ERROR("Failed to load upgrade header");
98             return;
99         }
100         // Create the control stream
101         streams[0];
102 
103         if (sendServerConnectionHeader() != 0)
104         {
105             BMCWEB_LOG_ERROR("send_server_connection_header failed");
106             return;
107         }
108         doRead();
109     }
110 
sendServerConnectionHeader()111     int sendServerConnectionHeader()
112     {
113         BMCWEB_LOG_DEBUG("send_server_connection_header()");
114 
115         uint32_t maxStreams = 4;
116 
117         // Both of these settings were found experimentally to allow a single
118         // fast stream to upload at a rate equivalent to http1.1  They will
119         // likely be tuned in the future.
120         uint32_t maxFrameSize = 1 << 14;
121         uint32_t windowSize = 1 << 20;
122         std::array<nghttp2_settings_entry, 4> iv = {{
123             {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams},
124             {NGHTTP2_SETTINGS_ENABLE_PUSH, 0},
125             // Set an approximately 1MB window size
126             {NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE, windowSize},
127             {NGHTTP2_SETTINGS_MAX_FRAME_SIZE, maxFrameSize},
128         }};
129         if (ngSession.setLocalWindowSize(NGHTTP2_FLAG_NONE, 0, 1 << 20) != 0)
130         {
131             BMCWEB_LOG_ERROR("Failed to set local window size");
132         }
133         int rv = ngSession.submitSettings(iv);
134         if (rv != 0)
135         {
136             BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
137             return -1;
138         }
139         writeBuffer();
140         return 0;
141     }
142 
fileReadCallback(nghttp2_session *,int32_t streamId,uint8_t * buf,size_t length,uint32_t * dataFlags,nghttp2_data_source *,void * userPtr)143     static ssize_t fileReadCallback(
144         nghttp2_session* /* session */, int32_t streamId, uint8_t* buf,
145         size_t length, uint32_t* dataFlags, nghttp2_data_source* /*source*/,
146         void* userPtr)
147     {
148         self_type& self = userPtrToSelf(userPtr);
149 
150         auto streamIt = self.streams.find(streamId);
151         if (streamIt == self.streams.end())
152         {
153             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
154         }
155         Http2StreamData& stream = streamIt->second;
156         BMCWEB_LOG_DEBUG("File read callback length: {}", length);
157         if (!stream.writer)
158         {
159             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
160         }
161         boost::beast::error_code ec;
162         boost::optional<std::pair<boost::asio::const_buffer, bool>> out =
163             stream.writer->getWithMaxSize(ec, length);
164         if (ec)
165         {
166             BMCWEB_LOG_CRITICAL("Failed to get buffer");
167             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
168         }
169         if (!out)
170         {
171             BMCWEB_LOG_ERROR("Empty file, setting EOF");
172             *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
173             return 0;
174         }
175 
176         BMCWEB_LOG_DEBUG("Send chunk of size: {}", out->first.size());
177         if (length < out->first.size())
178         {
179             BMCWEB_LOG_CRITICAL(
180                 "Buffer overflow that should never happen happened");
181             // Should never happen because of length limit on get() above
182             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
183         }
184         boost::asio::mutable_buffer writeableBuf(buf, length);
185         BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size());
186         size_t copied = boost::asio::buffer_copy(writeableBuf, out->first);
187         if (copied != out->first.size())
188         {
189             BMCWEB_LOG_ERROR(
190                 "Couldn't copy all {} bytes into buffer, only copied {}",
191                 out->first.size(), copied);
192             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
193         }
194 
195         if (!out->second)
196         {
197             BMCWEB_LOG_DEBUG("Setting EOF flag");
198             *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
199         }
200         return static_cast<ssize_t>(copied);
201     }
202 
headerFromStringViews(std::string_view name,std::string_view value,uint8_t flags)203     nghttp2_nv headerFromStringViews(std::string_view name,
204                                      std::string_view value, uint8_t flags)
205     {
206         uint8_t* nameData = std::bit_cast<uint8_t*>(name.data());
207         uint8_t* valueData = std::bit_cast<uint8_t*>(value.data());
208         return {nameData, valueData, name.size(), value.size(), flags};
209     }
210 
sendResponse(Response & completedRes,int32_t streamId)211     int sendResponse(Response& completedRes, int32_t streamId)
212     {
213         BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId);
214 
215         auto it = streams.find(streamId);
216         if (it == streams.end())
217         {
218             close();
219             return -1;
220         }
221         Http2StreamData& stream = it->second;
222         Response& res = stream.res;
223         res = std::move(completedRes);
224 
225         completeResponseFields(stream.accept, stream.acceptEnc, res);
226         res.addHeader(boost::beast::http::field::date, getCachedDateStr());
227         boost::urls::url_view urlView;
228         if (stream.req != nullptr)
229         {
230             urlView = stream.req->url();
231         }
232         res.preparePayload(urlView);
233 
234         boost::beast::http::fields& fields = res.fields();
235         std::string code = std::to_string(res.resultInt());
236         std::vector<nghttp2_nv> hdr;
237         hdr.emplace_back(
238             headerFromStringViews(":status", code, NGHTTP2_NV_FLAG_NONE));
239         for (const boost::beast::http::fields::value_type& header : fields)
240         {
241             hdr.emplace_back(headerFromStringViews(
242                 header.name_string(), header.value(), NGHTTP2_NV_FLAG_NONE));
243         }
244         http::response<bmcweb::HttpBody>& fbody = res.response;
245         stream.writer.emplace(fbody.base(), fbody.body());
246 
247         nghttp2_data_provider dataPrd{
248             .source = {.fd = 0},
249             .read_callback = fileReadCallback,
250         };
251 
252         int rv = ngSession.submitResponse(streamId, hdr, &dataPrd);
253         if (rv != 0)
254         {
255             BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
256             close();
257             return -1;
258         }
259         writeBuffer();
260 
261         return 0;
262     }
263 
initializeNghttp2Session()264     nghttp2_session initializeNghttp2Session()
265     {
266         nghttp2_session_callbacks callbacks;
267         callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic);
268         callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic);
269         callbacks.setOnHeaderCallback(onHeaderCallbackStatic);
270         callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic);
271         callbacks.setOnDataChunkRecvCallback(onDataChunkRecvStatic);
272 
273         nghttp2_session session(callbacks);
274         session.setUserData(this);
275 
276         return session;
277     }
278 
onRequestRecv(int32_t streamId)279     int onRequestRecv(int32_t streamId)
280     {
281         BMCWEB_LOG_DEBUG("on_request_recv");
282 
283         auto it = streams.find(streamId);
284         if (it == streams.end())
285         {
286             close();
287             return -1;
288         }
289         auto& reqReader = it->second.reqReader;
290         if (reqReader)
291         {
292             boost::beast::error_code ec;
293             bmcweb::HttpBody::reader::finish(ec);
294             if (ec)
295             {
296                 BMCWEB_LOG_CRITICAL("Failed to finalize payload");
297                 close();
298                 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
299             }
300         }
301         Request& thisReq = *it->second.req;
302         using boost::beast::http::field;
303         it->second.accept = thisReq.getHeaderValue(field::accept);
304         it->second.acceptEnc = thisReq.getHeaderValue(field::accept_encoding);
305 
306         BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq),
307                          thisReq.url().encoded_path());
308 
309         Response& thisRes = it->second.res;
310 
311         thisRes.setCompleteRequestHandler(
312             [weakSelf = weak_from_this(), streamId](Response& completeRes) {
313                 BMCWEB_LOG_DEBUG("res.completeRequestHandler called");
314                 if (auto self = weakSelf.lock(); self)
315                 {
316                     if (self->sendResponse(completeRes, streamId) != 0)
317                     {
318                         self->close();
319                         return;
320                     }
321                 }
322             });
323 
324         auto asyncResp =
325             std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res));
326         if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH)
327         {
328             thisReq.session = authentication::authenticate(
329                 {}, asyncResp->res, thisReq.method(), thisReq.req, mtlsSession);
330             if (!authentication::isOnAllowlist(thisReq.url().path(),
331                                                thisReq.method()) &&
332                 thisReq.session == nullptr)
333             {
334                 BMCWEB_LOG_WARNING("Authentication failed");
335                 forward_unauthorized::sendUnauthorized(
336                     thisReq.url().encoded_path(),
337                     thisReq.getHeaderValue("X-Requested-With"),
338                     thisReq.getHeaderValue("Accept"), asyncResp->res);
339                 return 0;
340             }
341         }
342         std::string_view expectedEtag =
343             thisReq.getHeaderValue(boost::beast::http::field::if_none_match);
344         BMCWEB_LOG_DEBUG("Setting expected etag {}", expectedEtag);
345         if (!expectedEtag.empty())
346         {
347             asyncResp->res.setExpectedEtag(expectedEtag);
348         }
349         handler->handle(it->second.req, asyncResp);
350         return 0;
351     }
352 
onDataChunkRecvCallback(uint8_t,int32_t streamId,const uint8_t * data,size_t len)353     int onDataChunkRecvCallback(uint8_t /*flags*/, int32_t streamId,
354                                 const uint8_t* data, size_t len)
355     {
356         auto thisStream = streams.find(streamId);
357         if (thisStream == streams.end())
358         {
359             BMCWEB_LOG_ERROR("Unknown stream{}", streamId);
360             close();
361             return -1;
362         }
363 
364         std::optional<bmcweb::HttpBody::reader>& reqReader =
365             thisStream->second.reqReader;
366         if (!reqReader)
367         {
368             reqReader.emplace(
369                 bmcweb::HttpBody::reader(thisStream->second.req->req.base(),
370                                          thisStream->second.req->req.body()));
371         }
372         boost::beast::error_code ec;
373         reqReader->put(boost::asio::const_buffer(data, len), ec);
374         if (ec)
375         {
376             BMCWEB_LOG_CRITICAL("Failed to write payload");
377             return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
378         }
379         return 0;
380     }
381 
onDataChunkRecvStatic(nghttp2_session *,uint8_t flags,int32_t streamId,const uint8_t * data,size_t len,void * userData)382     static int onDataChunkRecvStatic(
383         nghttp2_session* /* session */, uint8_t flags, int32_t streamId,
384         const uint8_t* data, size_t len, void* userData)
385     {
386         BMCWEB_LOG_DEBUG("onDataChunkRecvStatic");
387         if (userData == nullptr)
388         {
389             BMCWEB_LOG_CRITICAL("user data was null?");
390             return NGHTTP2_ERR_CALLBACK_FAILURE;
391         }
392         return userPtrToSelf(userData).onDataChunkRecvCallback(
393             flags, streamId, data, len);
394     }
395 
onFrameRecvCallback(const nghttp2_frame & frame)396     int onFrameRecvCallback(const nghttp2_frame& frame)
397     {
398         BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type));
399         switch (frame.hd.type)
400         {
401             case NGHTTP2_DATA:
402             case NGHTTP2_HEADERS:
403                 // Check that the client request has finished
404                 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0)
405                 {
406                     return onRequestRecv(frame.hd.stream_id);
407                 }
408                 break;
409             default:
410                 break;
411         }
412         return 0;
413     }
414 
onFrameRecvCallbackStatic(nghttp2_session *,const nghttp2_frame * frame,void * userData)415     static int onFrameRecvCallbackStatic(nghttp2_session* /* session */,
416                                          const nghttp2_frame* frame,
417                                          void* userData)
418     {
419         BMCWEB_LOG_DEBUG("on_frame_recv_callback.  Frame type {}",
420                          static_cast<int>(frame->hd.type));
421         if (userData == nullptr)
422         {
423             BMCWEB_LOG_CRITICAL("user data was null?");
424             return NGHTTP2_ERR_CALLBACK_FAILURE;
425         }
426         if (frame == nullptr)
427         {
428             BMCWEB_LOG_CRITICAL("frame was null?");
429             return NGHTTP2_ERR_CALLBACK_FAILURE;
430         }
431         return userPtrToSelf(userData).onFrameRecvCallback(*frame);
432     }
433 
userPtrToSelf(void * userData)434     static self_type& userPtrToSelf(void* userData)
435     {
436         // This method exists to keep the unsafe reinterpret cast in one
437         // place.
438         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
439         return *reinterpret_cast<self_type*>(userData);
440     }
441 
onStreamCloseCallbackStatic(nghttp2_session *,int32_t streamId,uint32_t,void * userData)442     static int onStreamCloseCallbackStatic(nghttp2_session* /* session */,
443                                            int32_t streamId,
444                                            uint32_t /*unused*/, void* userData)
445     {
446         BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId);
447         if (userData == nullptr)
448         {
449             BMCWEB_LOG_CRITICAL("user data was null?");
450             return NGHTTP2_ERR_CALLBACK_FAILURE;
451         }
452         if (userPtrToSelf(userData).streams.erase(streamId) <= 0)
453         {
454             return -1;
455         }
456         return 0;
457     }
458 
onHeaderCallback(const nghttp2_frame & frame,std::span<const uint8_t> name,std::span<const uint8_t> value)459     int onHeaderCallback(const nghttp2_frame& frame,
460                          std::span<const uint8_t> name,
461                          std::span<const uint8_t> value)
462     {
463         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
464         std::string_view nameSv(reinterpret_cast<const char*>(name.data()),
465                                 name.size());
466         // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
467         std::string_view valueSv(reinterpret_cast<const char*>(value.data()),
468                                  value.size());
469 
470         BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv,
471                          valueSv);
472         if (frame.hd.type != NGHTTP2_HEADERS)
473         {
474             return 0;
475         }
476         if (frame.headers.cat != NGHTTP2_HCAT_REQUEST)
477         {
478             return 0;
479         }
480         auto thisStream = streams.find(frame.hd.stream_id);
481         if (thisStream == streams.end())
482         {
483             BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id);
484             close();
485             return -1;
486         }
487 
488         Request& thisReq = *thisStream->second.req;
489 
490         if (nameSv == ":path")
491         {
492             thisReq.target(valueSv);
493         }
494         else if (nameSv == ":method")
495         {
496             boost::beast::http::verb verb =
497                 boost::beast::http::string_to_verb(valueSv);
498             if (verb == boost::beast::http::verb::unknown)
499             {
500                 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv);
501                 verb = boost::beast::http::verb::trace;
502             }
503             thisReq.method(verb);
504         }
505         else if (nameSv.starts_with(":"))
506         {
507             // Ignore all other http2 headers
508             // :scheme and :authority are other valid http2 fields that might
509             // show up here.
510         }
511         else
512         {
513             thisReq.addHeader(nameSv, valueSv);
514         }
515         return 0;
516     }
517 
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)518     static int onHeaderCallbackStatic(
519         nghttp2_session* /* session */, const nghttp2_frame* frame,
520         const uint8_t* name, size_t namelen, const uint8_t* value,
521         size_t vallen, uint8_t /* flags */, void* userData)
522     {
523         if (userData == nullptr)
524         {
525             BMCWEB_LOG_CRITICAL("user data was null?");
526             return NGHTTP2_ERR_CALLBACK_FAILURE;
527         }
528         if (frame == nullptr)
529         {
530             BMCWEB_LOG_CRITICAL("frame was null?");
531             return NGHTTP2_ERR_CALLBACK_FAILURE;
532         }
533         if (name == nullptr)
534         {
535             BMCWEB_LOG_CRITICAL("name was null?");
536             return NGHTTP2_ERR_CALLBACK_FAILURE;
537         }
538         if (value == nullptr)
539         {
540             BMCWEB_LOG_CRITICAL("value was null?");
541             return NGHTTP2_ERR_CALLBACK_FAILURE;
542         }
543         return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen},
544                                                         {value, vallen});
545     }
546 
onBeginHeadersCallback(const nghttp2_frame & frame)547     int onBeginHeadersCallback(const nghttp2_frame& frame)
548     {
549         if (frame.hd.type == NGHTTP2_HEADERS &&
550             frame.headers.cat == NGHTTP2_HCAT_REQUEST)
551         {
552             BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id);
553 
554             streams[frame.hd.stream_id];
555             if (ngSession.setLocalWindowSize(
556                     NGHTTP2_FLAG_NONE, frame.hd.stream_id, 16384 * 32) != 0)
557             {
558                 BMCWEB_LOG_ERROR("Failed to set local window size");
559             }
560         }
561         return 0;
562     }
563 
onBeginHeadersCallbackStatic(nghttp2_session *,const nghttp2_frame * frame,void * userData)564     static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */,
565                                             const nghttp2_frame* frame,
566                                             void* userData)
567     {
568         BMCWEB_LOG_DEBUG("on_begin_headers_callback");
569         if (userData == nullptr)
570         {
571             BMCWEB_LOG_CRITICAL("user data was null?");
572             return NGHTTP2_ERR_CALLBACK_FAILURE;
573         }
574         if (frame == nullptr)
575         {
576             BMCWEB_LOG_CRITICAL("frame was null?");
577             return NGHTTP2_ERR_CALLBACK_FAILURE;
578         }
579         return userPtrToSelf(userData).onBeginHeadersCallback(*frame);
580     }
581 
afterWriteBuffer(const std::shared_ptr<self_type> & self,const boost::system::error_code & ec,size_t sendLength)582     static void afterWriteBuffer(const std::shared_ptr<self_type>& self,
583                                  const boost::system::error_code& ec,
584                                  size_t sendLength)
585     {
586         self->isWriting = false;
587         BMCWEB_LOG_DEBUG("Sent {}", sendLength);
588         if (ec)
589         {
590             self->close();
591             return;
592         }
593         self->writeBuffer();
594     }
595 
writeBuffer()596     void writeBuffer()
597     {
598         if (isWriting)
599         {
600             return;
601         }
602         std::span<const uint8_t> data = ngSession.memSend();
603         if (data.empty())
604         {
605             return;
606         }
607         isWriting = true;
608         if (httpType == HttpType::HTTPS)
609         {
610             boost::asio::async_write(
611                 adaptor, boost::asio::const_buffer(data.data(), data.size()),
612                 std::bind_front(afterWriteBuffer, shared_from_this()));
613         }
614         else if (httpType == HttpType::HTTP)
615         {
616             boost::asio::async_write(
617                 adaptor.next_layer(),
618                 boost::asio::const_buffer(data.data(), data.size()),
619                 std::bind_front(afterWriteBuffer, shared_from_this()));
620         }
621     }
622 
close()623     void close()
624     {
625         adaptor.next_layer().close();
626     }
627 
afterDoRead(const std::shared_ptr<self_type> &,const boost::system::error_code & ec,size_t bytesTransferred)628     void afterDoRead(const std::shared_ptr<self_type>& /*self*/,
629                      const boost::system::error_code& ec,
630                      size_t bytesTransferred)
631     {
632         BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
633                          bytesTransferred);
634 
635         if (ec)
636         {
637             // EOF is normal when client closes HTTP/2 connection
638             // Only log non-EOF errors
639             if (ec != boost::asio::error::eof)
640             {
641                 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
642                                  ec.message());
643             }
644             close();
645             BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this));
646             return;
647         }
648         std::span<uint8_t> bufferSpan{inBuffer.data(), bytesTransferred};
649 
650         ssize_t readLen = ngSession.memRecv(bufferSpan);
651         if (readLen < 0)
652         {
653             BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", readLen);
654             close();
655             return;
656         }
657         writeBuffer();
658 
659         doRead();
660     }
661 
doRead()662     void doRead()
663     {
664         BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
665         if (httpType == HttpType::HTTPS)
666         {
667             adaptor.async_read_some(boost::asio::buffer(inBuffer),
668                                     std::bind_front(&self_type::afterDoRead,
669                                                     this, shared_from_this()));
670         }
671         else if (httpType == HttpType::HTTP)
672         {
673             adaptor.next_layer().async_read_some(
674                 boost::asio::buffer(inBuffer),
675                 std::bind_front(&self_type::afterDoRead, this,
676                                 shared_from_this()));
677         }
678     }
679 
680     // A mapping from http2 stream ID to Stream Data
681     std::map<int32_t, Http2StreamData> streams;
682 
683     std::array<uint8_t, 8192> inBuffer{};
684 
685     HttpType httpType = HttpType::BOTH;
686     boost::asio::ssl::stream<Adaptor> adaptor;
687     bool isWriting = false;
688 
689     nghttp2_session ngSession;
690 
691     Handler* handler;
692     std::function<std::string()>& getCachedDateStr;
693 
694     std::shared_ptr<persistent_data::UserSession> mtlsSession;
695 
696     using std::enable_shared_from_this<
697         HTTP2Connection<Adaptor, Handler>>::shared_from_this;
698 
699     using std::enable_shared_from_this<
700         HTTP2Connection<Adaptor, Handler>>::weak_from_this;
701 };
702 } // namespace crow
703