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