blob: d394889867c781d24ef158865deb03732bbcdf77 [file] [log] [blame]
Ed Tanous40e9b922024-09-10 13:50:16 -07001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright OpenBMC Authors
Ed Tanousfca2cbe2021-01-28 14:49:59 -08003#pragma once
4#include "bmcweb_config.h"
5
6#include "async_resp.hpp"
7#include "authentication.hpp"
8#include "complete_response_fields.hpp"
Ed Tanousd7857202025-01-28 15:32:26 -08009#include "forward_unauthorized.hpp"
Ed Tanous325310d2024-03-15 09:05:04 -070010#include "http_body.hpp"
Ed Tanousebe4c572025-02-08 14:29:53 -080011#include "http_connect_types.hpp"
Ed Tanousd7857202025-01-28 15:32:26 -080012#include "http_request.hpp"
Ed Tanousfca2cbe2021-01-28 14:49:59 -080013#include "http_response.hpp"
Ed Tanousfca2cbe2021-01-28 14:49:59 -080014#include "logging.hpp"
Ed Tanousfca2cbe2021-01-28 14:49:59 -080015
Ed Tanousd7857202025-01-28 15:32:26 -080016// NOLINTNEXTLINE(misc-include-cleaner)
17#include "nghttp2_adapters.hpp"
Ed Tanous3577e442025-08-19 19:34:00 -070018#include "sessions.hpp"
Ed Tanousd7857202025-01-28 15:32:26 -080019
20#include <nghttp2/nghttp2.h>
21#include <unistd.h>
22
23#include <boost/asio/buffer.hpp>
Ed Tanousfca2cbe2021-01-28 14:49:59 -080024#include <boost/asio/ssl/stream.hpp>
Ed Tanousd7857202025-01-28 15:32:26 -080025#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>
Ed Tanousd0882182024-01-26 23:45:25 -080031#include <boost/system/error_code.hpp>
Abiola Asojod23d6342025-06-18 20:15:24 +000032#include <boost/url/url_view.hpp>
Ed Tanousfca2cbe2021-01-28 14:49:59 -080033
Ed Tanousd0882182024-01-26 23:45:25 -080034#include <array>
Ed Tanousd7857202025-01-28 15:32:26 -080035#include <bit>
36#include <cstddef>
37#include <cstdint>
Ed Tanousd0882182024-01-26 23:45:25 -080038#include <functional>
Ed Tanousd7857202025-01-28 15:32:26 -080039#include <map>
Ed Tanousd0882182024-01-26 23:45:25 -080040#include <memory>
Ed Tanousd7857202025-01-28 15:32:26 -080041#include <optional>
42#include <span>
Ed Tanous89cda632024-04-16 08:45:54 -070043#include <string>
Ed Tanousd7857202025-01-28 15:32:26 -080044#include <string_view>
Ed Tanous796ba932020-08-02 04:29:21 +000045#include <type_traits>
Ed Tanousd7857202025-01-28 15:32:26 -080046#include <utility>
Ed Tanousfca2cbe2021-01-28 14:49:59 -080047#include <vector>
48
49namespace crow
50{
51
52struct Http2StreamData
53{
Jonathan Doman102a4cd2024-04-15 16:56:23 -070054 std::shared_ptr<Request> req = std::make_shared<Request>();
Ed Tanous325310d2024-03-15 09:05:04 -070055 std::optional<bmcweb::HttpBody::reader> reqReader;
Ed Tanous89cda632024-04-16 08:45:54 -070056 std::string accept;
Ed Tanousb2539062024-03-12 16:58:35 -070057 std::string acceptEnc;
Ed Tanous47f29342024-03-19 12:18:06 -070058 Response res;
Ed Tanousb2896142024-01-31 15:25:47 -080059 std::optional<bmcweb::HttpBody::writer> writer;
Ed Tanousfca2cbe2021-01-28 14:49:59 -080060};
61
62template <typename Adaptor, typename Handler>
63class HTTP2Connection :
64 public std::enable_shared_from_this<HTTP2Connection<Adaptor, Handler>>
65{
66 using self_type = HTTP2Connection<Adaptor, Handler>;
67
68 public:
Ed Tanous2e3cdf82025-08-01 09:49:35 -070069 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) :
Ed Tanousebe4c572025-02-08 14:29:53 -080073 httpType(httpTypeIn), adaptor(std::move(adaptorIn)),
74 ngSession(initializeNghttp2Session()), handler(handlerIn),
Ed Tanous2e3cdf82025-08-01 09:49:35 -070075 getCachedDateStr(getCachedDateStrF), mtlsSession(mtlsSessionIn)
Ed Tanousfca2cbe2021-01-28 14:49:59 -080076 {}
77
78 void start()
79 {
80 // Create the control stream
Ed Tanousf42e8592023-08-25 10:47:44 -070081 streams[0];
Ed Tanousfca2cbe2021-01-28 14:49:59 -080082
83 if (sendServerConnectionHeader() != 0)
84 {
Ed Tanous62598e32023-07-17 17:06:25 -070085 BMCWEB_LOG_ERROR("send_server_connection_header failed");
Ed Tanousfca2cbe2021-01-28 14:49:59 -080086 return;
87 }
88 doRead();
89 }
90
Ed Tanouscd7dbb32025-02-01 12:37:56 -080091 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
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800111 int sendServerConnectionHeader()
112 {
Ed Tanous62598e32023-07-17 17:06:25 -0700113 BMCWEB_LOG_DEBUG("send_server_connection_header()");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800114
115 uint32_t maxStreams = 4;
116 std::array<nghttp2_settings_entry, 2> iv = {
117 {{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, maxStreams},
118 {NGHTTP2_SETTINGS_ENABLE_PUSH, 0}}};
119 int rv = ngSession.submitSettings(iv);
120 if (rv != 0)
121 {
Ed Tanous62598e32023-07-17 17:06:25 -0700122 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800123 return -1;
124 }
Ed Tanousd0882182024-01-26 23:45:25 -0800125 writeBuffer();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800126 return 0;
127 }
128
Patrick Williams504af5a2025-02-03 14:29:03 -0500129 static ssize_t fileReadCallback(
130 nghttp2_session* /* session */, int32_t streamId, uint8_t* buf,
131 size_t length, uint32_t* dataFlags, nghttp2_data_source* /*source*/,
132 void* userPtr)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800133 {
Ed Tanousf42e8592023-08-25 10:47:44 -0700134 self_type& self = userPtrToSelf(userPtr);
135
136 auto streamIt = self.streams.find(streamId);
137 if (streamIt == self.streams.end())
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800138 {
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800139 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
140 }
Ed Tanousf42e8592023-08-25 10:47:44 -0700141 Http2StreamData& stream = streamIt->second;
Ed Tanous62598e32023-07-17 17:06:25 -0700142 BMCWEB_LOG_DEBUG("File read callback length: {}", length);
Ed Tanousd547d8d2024-03-16 18:04:41 -0700143 if (!stream.writer)
144 {
145 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
146 }
Ed Tanous52e31622024-01-23 16:31:11 -0800147 boost::beast::error_code ec;
148 boost::optional<std::pair<boost::asio::const_buffer, bool>> out =
149 stream.writer->getWithMaxSize(ec, length);
150 if (ec)
Ed Tanous27b0cf92023-08-07 12:02:40 -0700151 {
Ed Tanous325310d2024-03-15 09:05:04 -0700152 BMCWEB_LOG_CRITICAL("Failed to get buffer");
Ed Tanous27b0cf92023-08-07 12:02:40 -0700153 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
154 }
Ed Tanous52e31622024-01-23 16:31:11 -0800155 if (!out)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800156 {
Ed Tanous325310d2024-03-15 09:05:04 -0700157 BMCWEB_LOG_ERROR("Empty file, setting EOF");
Ed Tanous52e31622024-01-23 16:31:11 -0800158 *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
159 return 0;
160 }
161
162 BMCWEB_LOG_DEBUG("Send chunk of size: {}", out->first.size());
163 if (length < out->first.size())
164 {
Ed Tanous325310d2024-03-15 09:05:04 -0700165 BMCWEB_LOG_CRITICAL(
166 "Buffer overflow that should never happen happened");
Ed Tanous52e31622024-01-23 16:31:11 -0800167 // Should never happen because of length limit on get() above
168 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
169 }
Ed Tanous325310d2024-03-15 09:05:04 -0700170 boost::asio::mutable_buffer writeableBuf(buf, length);
Ed Tanous52e31622024-01-23 16:31:11 -0800171 BMCWEB_LOG_DEBUG("Copying {} bytes to buf", out->first.size());
Ed Tanous325310d2024-03-15 09:05:04 -0700172 size_t copied = boost::asio::buffer_copy(writeableBuf, out->first);
173 if (copied != out->first.size())
174 {
175 BMCWEB_LOG_ERROR(
176 "Couldn't copy all {} bytes into buffer, only copied {}",
177 out->first.size(), copied);
178 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
179 }
Ed Tanous52e31622024-01-23 16:31:11 -0800180
181 if (!out->second)
182 {
Ed Tanous325310d2024-03-15 09:05:04 -0700183 BMCWEB_LOG_DEBUG("Setting EOF flag");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800184 *dataFlags |= NGHTTP2_DATA_FLAG_EOF;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800185 }
Ed Tanous325310d2024-03-15 09:05:04 -0700186 return static_cast<ssize_t>(copied);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800187 }
188
189 nghttp2_nv headerFromStringViews(std::string_view name,
Ed Tanous52e31622024-01-23 16:31:11 -0800190 std::string_view value, uint8_t flags)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800191 {
192 uint8_t* nameData = std::bit_cast<uint8_t*>(name.data());
193 uint8_t* valueData = std::bit_cast<uint8_t*>(value.data());
Ed Tanous52e31622024-01-23 16:31:11 -0800194 return {nameData, valueData, name.size(), value.size(), flags};
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800195 }
196
197 int sendResponse(Response& completedRes, int32_t streamId)
198 {
Ed Tanous62598e32023-07-17 17:06:25 -0700199 BMCWEB_LOG_DEBUG("send_response stream_id:{}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800200
201 auto it = streams.find(streamId);
202 if (it == streams.end())
203 {
204 close();
205 return -1;
206 }
Ed Tanous499b5b42024-04-06 08:39:18 -0700207 Http2StreamData& stream = it->second;
208 Response& res = stream.res;
209 res = std::move(completedRes);
Ed Tanous499b5b42024-04-06 08:39:18 -0700210
Ed Tanousb2539062024-03-12 16:58:35 -0700211 completeResponseFields(stream.accept, stream.acceptEnc, res);
Ed Tanous499b5b42024-04-06 08:39:18 -0700212 res.addHeader(boost::beast::http::field::date, getCachedDateStr());
Abiola Asojod23d6342025-06-18 20:15:24 +0000213 boost::urls::url_view urlView;
214 if (stream.req != nullptr)
215 {
216 urlView = stream.req->url();
217 }
218 res.preparePayload(urlView);
Ed Tanous499b5b42024-04-06 08:39:18 -0700219
220 boost::beast::http::fields& fields = res.fields();
221 std::string code = std::to_string(res.resultInt());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800222 std::vector<nghttp2_nv> hdr;
Ed Tanous52e31622024-01-23 16:31:11 -0800223 hdr.emplace_back(
224 headerFromStringViews(":status", code, NGHTTP2_NV_FLAG_NONE));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800225 for (const boost::beast::http::fields::value_type& header : fields)
226 {
Ed Tanous52e31622024-01-23 16:31:11 -0800227 hdr.emplace_back(headerFromStringViews(
Ed Tanousd0882182024-01-26 23:45:25 -0800228 header.name_string(), header.value(), NGHTTP2_NV_FLAG_NONE));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800229 }
Ed Tanousb2896142024-01-31 15:25:47 -0800230 http::response<bmcweb::HttpBody>& fbody = res.response;
Ed Tanous52e31622024-01-23 16:31:11 -0800231 stream.writer.emplace(fbody.base(), fbody.body());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800232
233 nghttp2_data_provider dataPrd{
Ed Tanousf42e8592023-08-25 10:47:44 -0700234 .source = {.fd = 0},
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800235 .read_callback = fileReadCallback,
236 };
237
238 int rv = ngSession.submitResponse(streamId, hdr, &dataPrd);
239 if (rv != 0)
240 {
Ed Tanous62598e32023-07-17 17:06:25 -0700241 BMCWEB_LOG_ERROR("Fatal error: {}", nghttp2_strerror(rv));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800242 close();
243 return -1;
244 }
Ed Tanousd0882182024-01-26 23:45:25 -0800245 writeBuffer();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800246
247 return 0;
248 }
249
250 nghttp2_session initializeNghttp2Session()
251 {
252 nghttp2_session_callbacks callbacks;
253 callbacks.setOnFrameRecvCallback(onFrameRecvCallbackStatic);
254 callbacks.setOnStreamCloseCallback(onStreamCloseCallbackStatic);
255 callbacks.setOnHeaderCallback(onHeaderCallbackStatic);
256 callbacks.setOnBeginHeadersCallback(onBeginHeadersCallbackStatic);
Ed Tanous325310d2024-03-15 09:05:04 -0700257 callbacks.setOnDataChunkRecvCallback(onDataChunkRecvStatic);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800258
259 nghttp2_session session(callbacks);
260 session.setUserData(this);
261
262 return session;
263 }
264
265 int onRequestRecv(int32_t streamId)
266 {
Ed Tanous62598e32023-07-17 17:06:25 -0700267 BMCWEB_LOG_DEBUG("on_request_recv");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800268
269 auto it = streams.find(streamId);
270 if (it == streams.end())
271 {
272 close();
273 return -1;
274 }
Ed Tanousd547d8d2024-03-16 18:04:41 -0700275 auto& reqReader = it->second.reqReader;
276 if (reqReader)
Ed Tanous325310d2024-03-15 09:05:04 -0700277 {
278 boost::beast::error_code ec;
Ed Tanousdaadfb22024-12-20 09:25:54 -0800279 bmcweb::HttpBody::reader::finish(ec);
Ed Tanous325310d2024-03-15 09:05:04 -0700280 if (ec)
281 {
282 BMCWEB_LOG_CRITICAL("Failed to finalize payload");
283 close();
284 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
285 }
286 }
Ed Tanous05c27352024-10-09 17:00:37 -0700287 Request& thisReq = *it->second.req;
Ed Tanousb2539062024-03-12 16:58:35 -0700288 using boost::beast::http::field;
289 it->second.accept = thisReq.getHeaderValue(field::accept);
290 it->second.acceptEnc = thisReq.getHeaderValue(field::accept_encoding);
Ed Tanous89cda632024-04-16 08:45:54 -0700291
Ed Tanous62598e32023-07-17 17:06:25 -0700292 BMCWEB_LOG_DEBUG("Handling {} \"{}\"", logPtr(&thisReq),
293 thisReq.url().encoded_path());
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800294
Ed Tanous05c27352024-10-09 17:00:37 -0700295 Response& thisRes = it->second.res;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800296
297 thisRes.setCompleteRequestHandler(
298 [this, streamId](Response& completeRes) {
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400299 BMCWEB_LOG_DEBUG("res.completeRequestHandler called");
300 if (sendResponse(completeRes, streamId) != 0)
301 {
302 close();
303 return;
304 }
305 });
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800306 auto asyncResp =
Ed Tanousf42e8592023-08-25 10:47:44 -0700307 std::make_shared<bmcweb::AsyncResp>(std::move(it->second.res));
Ed Tanous83328312024-05-09 15:48:09 -0700308 if constexpr (!BMCWEB_INSECURE_DISABLE_AUTH)
Ed Tanous325310d2024-03-15 09:05:04 -0700309 {
Ed Tanous05c27352024-10-09 17:00:37 -0700310 thisReq.session = authentication::authenticate(
Ed Tanous2e3cdf82025-08-01 09:49:35 -0700311 {}, asyncResp->res, thisReq.method(), thisReq.req, mtlsSession);
Ed Tanous05c27352024-10-09 17:00:37 -0700312 if (!authentication::isOnAllowlist(thisReq.url().path(),
313 thisReq.method()) &&
Ed Tanous83328312024-05-09 15:48:09 -0700314 thisReq.session == nullptr)
Ed Tanous499b5b42024-04-06 08:39:18 -0700315 {
Ed Tanous83328312024-05-09 15:48:09 -0700316 BMCWEB_LOG_WARNING("Authentication failed");
317 forward_unauthorized::sendUnauthorized(
318 thisReq.url().encoded_path(),
319 thisReq.getHeaderValue("X-Requested-With"),
320 thisReq.getHeaderValue("Accept"), asyncResp->res);
321 return 0;
Ed Tanous499b5b42024-04-06 08:39:18 -0700322 }
Ed Tanous325310d2024-03-15 09:05:04 -0700323 }
Corey Ethington08fad5d2025-07-31 12:14:27 -0400324 std::string_view expectedEtag =
Ed Tanous83328312024-05-09 15:48:09 -0700325 thisReq.getHeaderValue(boost::beast::http::field::if_none_match);
Corey Ethington08fad5d2025-07-31 12:14:27 -0400326 BMCWEB_LOG_DEBUG("Setting expected etag {}", expectedEtag);
327 if (!expectedEtag.empty())
Ed Tanous83328312024-05-09 15:48:09 -0700328 {
Corey Ethington08fad5d2025-07-31 12:14:27 -0400329 asyncResp->res.setExpectedEtag(expectedEtag);
Ed Tanous83328312024-05-09 15:48:09 -0700330 }
331 handler->handle(it->second.req, asyncResp);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800332 return 0;
333 }
334
Ed Tanous325310d2024-03-15 09:05:04 -0700335 int onDataChunkRecvCallback(uint8_t /*flags*/, int32_t streamId,
336 const uint8_t* data, size_t len)
337 {
338 auto thisStream = streams.find(streamId);
339 if (thisStream == streams.end())
340 {
341 BMCWEB_LOG_ERROR("Unknown stream{}", streamId);
342 close();
343 return -1;
344 }
Ed Tanousd547d8d2024-03-16 18:04:41 -0700345
346 std::optional<bmcweb::HttpBody::reader>& reqReader =
347 thisStream->second.reqReader;
348 if (!reqReader)
Ed Tanous325310d2024-03-15 09:05:04 -0700349 {
Ed Tanous8e5cc7b2024-04-02 11:00:54 -0700350 reqReader.emplace(
Jonathan Doman102a4cd2024-04-15 16:56:23 -0700351 bmcweb::HttpBody::reader(thisStream->second.req->req.base(),
352 thisStream->second.req->req.body()));
Ed Tanous325310d2024-03-15 09:05:04 -0700353 }
354 boost::beast::error_code ec;
Ed Tanousd547d8d2024-03-16 18:04:41 -0700355 reqReader->put(boost::asio::const_buffer(data, len), ec);
Ed Tanous325310d2024-03-15 09:05:04 -0700356 if (ec)
357 {
358 BMCWEB_LOG_CRITICAL("Failed to write payload");
359 return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
360 }
361 return 0;
362 }
363
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400364 static int onDataChunkRecvStatic(
365 nghttp2_session* /* session */, uint8_t flags, int32_t streamId,
366 const uint8_t* data, size_t len, void* userData)
Ed Tanous325310d2024-03-15 09:05:04 -0700367 {
368 BMCWEB_LOG_DEBUG("on_frame_recv_callback");
369 if (userData == nullptr)
370 {
371 BMCWEB_LOG_CRITICAL("user data was null?");
372 return NGHTTP2_ERR_CALLBACK_FAILURE;
373 }
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400374 return userPtrToSelf(userData).onDataChunkRecvCallback(
375 flags, streamId, data, len);
Ed Tanous325310d2024-03-15 09:05:04 -0700376 }
377
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800378 int onFrameRecvCallback(const nghttp2_frame& frame)
379 {
Ed Tanous62598e32023-07-17 17:06:25 -0700380 BMCWEB_LOG_DEBUG("frame type {}", static_cast<int>(frame.hd.type));
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800381 switch (frame.hd.type)
382 {
383 case NGHTTP2_DATA:
384 case NGHTTP2_HEADERS:
385 // Check that the client request has finished
386 if ((frame.hd.flags & NGHTTP2_FLAG_END_STREAM) != 0)
387 {
388 return onRequestRecv(frame.hd.stream_id);
389 }
390 break;
391 default:
392 break;
393 }
394 return 0;
395 }
396
397 static int onFrameRecvCallbackStatic(nghttp2_session* /* session */,
398 const nghttp2_frame* frame,
399 void* userData)
400 {
Ed Tanous62598e32023-07-17 17:06:25 -0700401 BMCWEB_LOG_DEBUG("on_frame_recv_callback");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800402 if (userData == nullptr)
403 {
Ed Tanous62598e32023-07-17 17:06:25 -0700404 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800405 return NGHTTP2_ERR_CALLBACK_FAILURE;
406 }
407 if (frame == nullptr)
408 {
Ed Tanous62598e32023-07-17 17:06:25 -0700409 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800410 return NGHTTP2_ERR_CALLBACK_FAILURE;
411 }
412 return userPtrToSelf(userData).onFrameRecvCallback(*frame);
413 }
414
415 static self_type& userPtrToSelf(void* userData)
416 {
417 // This method exists to keep the unsafe reinterpret cast in one
418 // place.
419 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
420 return *reinterpret_cast<self_type*>(userData);
421 }
422
423 static int onStreamCloseCallbackStatic(nghttp2_session* /* session */,
424 int32_t streamId,
425 uint32_t /*unused*/, void* userData)
426 {
Ed Tanous62598e32023-07-17 17:06:25 -0700427 BMCWEB_LOG_DEBUG("on_stream_close_callback stream {}", streamId);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800428 if (userData == nullptr)
429 {
Ed Tanous62598e32023-07-17 17:06:25 -0700430 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800431 return NGHTTP2_ERR_CALLBACK_FAILURE;
432 }
Ed Tanousf42e8592023-08-25 10:47:44 -0700433 if (userPtrToSelf(userData).streams.erase(streamId) <= 0)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800434 {
435 return -1;
436 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800437 return 0;
438 }
439
440 int onHeaderCallback(const nghttp2_frame& frame,
441 std::span<const uint8_t> name,
442 std::span<const uint8_t> value)
443 {
444 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
445 std::string_view nameSv(reinterpret_cast<const char*>(name.data()),
446 name.size());
447 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
448 std::string_view valueSv(reinterpret_cast<const char*>(value.data()),
449 value.size());
450
Ed Tanous62598e32023-07-17 17:06:25 -0700451 BMCWEB_LOG_DEBUG("on_header_callback name: {} value {}", nameSv,
452 valueSv);
Ed Tanousa07e9812024-03-19 10:31:13 -0700453 if (frame.hd.type != NGHTTP2_HEADERS)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800454 {
Ed Tanousa07e9812024-03-19 10:31:13 -0700455 return 0;
456 }
457 if (frame.headers.cat != NGHTTP2_HCAT_REQUEST)
458 {
459 return 0;
460 }
461 auto thisStream = streams.find(frame.hd.stream_id);
462 if (thisStream == streams.end())
463 {
464 BMCWEB_LOG_ERROR("Unknown stream{}", frame.hd.stream_id);
465 close();
466 return -1;
467 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800468
Ed Tanous05c27352024-10-09 17:00:37 -0700469 Request& thisReq = *thisStream->second.req;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800470
Ed Tanousa07e9812024-03-19 10:31:13 -0700471 if (nameSv == ":path")
472 {
473 thisReq.target(valueSv);
474 }
475 else if (nameSv == ":method")
476 {
477 boost::beast::http::verb verb =
478 boost::beast::http::string_to_verb(valueSv);
479 if (verb == boost::beast::http::verb::unknown)
480 {
481 BMCWEB_LOG_ERROR("Unknown http verb {}", valueSv);
Ed Tanous50bfc912024-07-29 14:20:50 -0700482 verb = boost::beast::http::verb::trace;
Ed Tanousa07e9812024-03-19 10:31:13 -0700483 }
Myung Bae1873a042024-04-01 09:27:39 -0500484 thisReq.method(verb);
Ed Tanousa07e9812024-03-19 10:31:13 -0700485 }
486 else if (nameSv == ":scheme")
487 {
488 // Nothing to check on scheme
489 }
490 else
491 {
Myung Bae1873a042024-04-01 09:27:39 -0500492 thisReq.addHeader(nameSv, valueSv);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800493 }
494 return 0;
495 }
496
Patrick Williamsbd79bce2024-08-16 15:22:20 -0400497 static int onHeaderCallbackStatic(
498 nghttp2_session* /* session */, const nghttp2_frame* frame,
499 const uint8_t* name, size_t namelen, const uint8_t* value,
500 size_t vallen, uint8_t /* flags */, void* userData)
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800501 {
502 if (userData == nullptr)
503 {
Ed Tanous62598e32023-07-17 17:06:25 -0700504 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800505 return NGHTTP2_ERR_CALLBACK_FAILURE;
506 }
507 if (frame == nullptr)
508 {
Ed Tanous62598e32023-07-17 17:06:25 -0700509 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800510 return NGHTTP2_ERR_CALLBACK_FAILURE;
511 }
512 if (name == nullptr)
513 {
Ed Tanous62598e32023-07-17 17:06:25 -0700514 BMCWEB_LOG_CRITICAL("name was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800515 return NGHTTP2_ERR_CALLBACK_FAILURE;
516 }
517 if (value == nullptr)
518 {
Ed Tanous62598e32023-07-17 17:06:25 -0700519 BMCWEB_LOG_CRITICAL("value was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800520 return NGHTTP2_ERR_CALLBACK_FAILURE;
521 }
522 return userPtrToSelf(userData).onHeaderCallback(*frame, {name, namelen},
523 {value, vallen});
524 }
525
526 int onBeginHeadersCallback(const nghttp2_frame& frame)
527 {
528 if (frame.hd.type == NGHTTP2_HEADERS &&
529 frame.headers.cat == NGHTTP2_HCAT_REQUEST)
530 {
Ed Tanous62598e32023-07-17 17:06:25 -0700531 BMCWEB_LOG_DEBUG("create stream for id {}", frame.hd.stream_id);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800532
Ed Tanousb2539062024-03-12 16:58:35 -0700533 streams[frame.hd.stream_id];
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800534 }
535 return 0;
536 }
537
538 static int onBeginHeadersCallbackStatic(nghttp2_session* /* session */,
539 const nghttp2_frame* frame,
540 void* userData)
541 {
Ed Tanous62598e32023-07-17 17:06:25 -0700542 BMCWEB_LOG_DEBUG("on_begin_headers_callback");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800543 if (userData == nullptr)
544 {
Ed Tanous62598e32023-07-17 17:06:25 -0700545 BMCWEB_LOG_CRITICAL("user data was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800546 return NGHTTP2_ERR_CALLBACK_FAILURE;
547 }
548 if (frame == nullptr)
549 {
Ed Tanous62598e32023-07-17 17:06:25 -0700550 BMCWEB_LOG_CRITICAL("frame was null?");
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800551 return NGHTTP2_ERR_CALLBACK_FAILURE;
552 }
553 return userPtrToSelf(userData).onBeginHeadersCallback(*frame);
554 }
555
556 static void afterWriteBuffer(const std::shared_ptr<self_type>& self,
557 const boost::system::error_code& ec,
558 size_t sendLength)
559 {
560 self->isWriting = false;
Ed Tanous62598e32023-07-17 17:06:25 -0700561 BMCWEB_LOG_DEBUG("Sent {}", sendLength);
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800562 if (ec)
563 {
564 self->close();
565 return;
566 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800567 self->writeBuffer();
568 }
569
570 void writeBuffer()
571 {
572 if (isWriting)
573 {
574 return;
575 }
Ed Tanousd0882182024-01-26 23:45:25 -0800576 std::span<const uint8_t> data = ngSession.memSend();
577 if (data.empty())
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800578 {
579 return;
580 }
581 isWriting = true;
Ed Tanousebe4c572025-02-08 14:29:53 -0800582 if (httpType == HttpType::HTTPS)
583 {
584 boost::asio::async_write(
585 adaptor, boost::asio::const_buffer(data.data(), data.size()),
586 std::bind_front(afterWriteBuffer, shared_from_this()));
587 }
588 else if (httpType == HttpType::HTTP)
589 {
590 boost::asio::async_write(
591 adaptor.next_layer(),
592 boost::asio::const_buffer(data.data(), data.size()),
593 std::bind_front(afterWriteBuffer, shared_from_this()));
594 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800595 }
596
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800597 void close()
598 {
Ed Tanousebe4c572025-02-08 14:29:53 -0800599 adaptor.next_layer().close();
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800600 }
601
Ed Tanousd0882182024-01-26 23:45:25 -0800602 void afterDoRead(const std::shared_ptr<self_type>& /*self*/,
603 const boost::system::error_code& ec,
604 size_t bytesTransferred)
605 {
606 BMCWEB_LOG_DEBUG("{} async_read_some {} Bytes", logPtr(this),
607 bytesTransferred);
608
609 if (ec)
610 {
611 BMCWEB_LOG_ERROR("{} Error while reading: {}", logPtr(this),
612 ec.message());
613 close();
614 BMCWEB_LOG_DEBUG("{} from read(1)", logPtr(this));
615 return;
616 }
617 std::span<uint8_t> bufferSpan{inBuffer.data(), bytesTransferred};
618
619 ssize_t readLen = ngSession.memRecv(bufferSpan);
620 if (readLen < 0)
621 {
622 BMCWEB_LOG_ERROR("nghttp2_session_mem_recv returned {}", readLen);
623 close();
624 return;
625 }
626 writeBuffer();
627
628 doRead();
629 }
630
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800631 void doRead()
632 {
Ed Tanous62598e32023-07-17 17:06:25 -0700633 BMCWEB_LOG_DEBUG("{} doRead", logPtr(this));
Ed Tanousebe4c572025-02-08 14:29:53 -0800634 if (httpType == HttpType::HTTPS)
635 {
636 adaptor.async_read_some(boost::asio::buffer(inBuffer),
637 std::bind_front(&self_type::afterDoRead,
638 this, shared_from_this()));
639 }
640 else if (httpType == HttpType::HTTP)
641 {
642 adaptor.next_layer().async_read_some(
643 boost::asio::buffer(inBuffer),
644 std::bind_front(&self_type::afterDoRead, this,
645 shared_from_this()));
646 }
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800647 }
648
649 // A mapping from http2 stream ID to Stream Data
Ed Tanous52e31622024-01-23 16:31:11 -0800650 std::map<int32_t, Http2StreamData> streams;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800651
Ed Tanousd0882182024-01-26 23:45:25 -0800652 std::array<uint8_t, 8192> inBuffer{};
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800653
Ed Tanousebe4c572025-02-08 14:29:53 -0800654 HttpType httpType = HttpType::BOTH;
655 boost::asio::ssl::stream<Adaptor> adaptor;
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800656 bool isWriting = false;
657
658 nghttp2_session ngSession;
659
660 Handler* handler;
661 std::function<std::string()>& getCachedDateStr;
662
Ed Tanous2e3cdf82025-08-01 09:49:35 -0700663 std::shared_ptr<persistent_data::UserSession> mtlsSession;
664
Ed Tanousfca2cbe2021-01-28 14:49:59 -0800665 using std::enable_shared_from_this<
666 HTTP2Connection<Adaptor, Handler>>::shared_from_this;
667
668 using std::enable_shared_from_this<
669 HTTP2Connection<Adaptor, Handler>>::weak_from_this;
670};
671} // namespace crow