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